From 76dd591dd044baca50843c4b962f6476bf255d9d Mon Sep 17 00:00:00 2001 From: Yann Nicolas Date: Tue, 16 May 2023 23:06:51 -0400 Subject: [PATCH 001/162] Add dynamic template support to Twilio Sendgrid binding Signed-off-by: Yann Nicolas --- .../supported-bindings/sendgrid.md | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/sendgrid.md b/daprdocs/content/en/reference/components-reference/supported-bindings/sendgrid.md index 8a82f2f61c7..f4300652c02 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/sendgrid.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/sendgrid.md @@ -35,6 +35,10 @@ spec: value: "jill@dapr.io" # optional - name: emailBcc value: "bob@dapr.io" # optional + - name: dynamicTemplateId + value: "d-123456789" # optional + - name: dynamicTemplateData + value: '{"customer":{"name":"John Smith"}}' # optional - name: apiKey value: "YOUR_API_KEY" # required, this is your SendGrid key ``` @@ -55,7 +59,8 @@ The above example uses secrets as plain strings. It is recommended to use a secr | emailCc | N | Output | If set this specifies the 'cc' email address of the email message. Only a single email address is allowed. Optional field, see [below](#example-request-payload) | `"me@example.com"` | | emailBcc | N | Output | If set this specifies the 'bcc' email address of the email message. Only a single email address is allowed. Optional field, see [below](#example-request-payload) | `"me@example.com"` | | subject | N | Output | If set this specifies the subject of the email message. Optional field, see [below](#example-request-payload) | `"subject of the email"` | - +| dynamicTemplateId | N | Output | If set this specifies the dynamic template id. Optional field, see [below](#example-request-payload) | `"d-123456789"` | +| dynamicTemplateData | N | Output | If set this specifies the dynamic template data. Optional field, see [below](#example-request-payload) | `'{"customer":{"name":"John Smith"}}'` | ## Binding support @@ -78,6 +83,19 @@ You can specify any of the optional metadata properties on the output binding re } ``` +If a dynamic template is used, the template id needs to be used and if provided, the template data is used: + +```json +{ + "operation": "create", + "metadata": { + "emailTo": "changeme@example.net", + "subject": "An template email from Dapr SendGrid binding", + "dynamicTemplateId": "d-123456789", + "dynamicTemplateData": "{\"customer\":{\"name\":\"John Smith\"}}" + } +} + ## Related links - [Basic schema for a Dapr component]({{< ref component-schema >}}) From c8df8ec12a0a5c237172ba658f73ea4c6b03716c Mon Sep 17 00:00:00 2001 From: Yann Nicolas Date: Wed, 17 May 2023 00:13:26 -0400 Subject: [PATCH 002/162] Put dynamic template in its own section per code review suggestion Signed-off-by: Yann Nicolas --- .../components-reference/supported-bindings/sendgrid.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/sendgrid.md b/daprdocs/content/en/reference/components-reference/supported-bindings/sendgrid.md index f4300652c02..a7a68f32386 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/sendgrid.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/sendgrid.md @@ -83,7 +83,8 @@ You can specify any of the optional metadata properties on the output binding re } ``` -If a dynamic template is used, the template id needs to be used and if provided, the template data is used: +## Dynamic templates +If a dynamic template is used, a `dynamicTemplateId` needs to be provided and then the `dynamicTemplateData` is used: ```json { From 17a8c84349b7658545697b3e2f4835a18feff607 Mon Sep 17 00:00:00 2001 From: Yann Nicolas Date: Thu, 18 May 2023 00:00:24 +0200 Subject: [PATCH 003/162] Fix JSON example Signed-off-by: Yann Nicolas --- .../components-reference/supported-bindings/sendgrid.md | 1 + 1 file changed, 1 insertion(+) diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/sendgrid.md b/daprdocs/content/en/reference/components-reference/supported-bindings/sendgrid.md index a7a68f32386..a2eb4961f73 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/sendgrid.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/sendgrid.md @@ -96,6 +96,7 @@ If a dynamic template is used, a `dynamicTemplateId` needs to be provided and th "dynamicTemplateData": "{\"customer\":{\"name\":\"John Smith\"}}" } } +``` ## Related links From 790b7e416727115565672fdf17f48866aad1fc32 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Mon, 12 Jun 2023 10:35:10 -0400 Subject: [PATCH 004/162] update config toml for 1.12 Signed-off-by: Hannah Hunter --- daprdocs/config.toml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/daprdocs/config.toml b/daprdocs/config.toml index ca415054a66..5e4d6264d92 100644 --- a/daprdocs/config.toml +++ b/daprdocs/config.toml @@ -1,5 +1,5 @@ # Site Configuration -baseURL = "https://v1-11.docs.dapr.io" +baseURL = "https://v1-12.docs.dapr.io" title = "Dapr Docs" theme = "docsy" disableFastRender = true @@ -177,11 +177,14 @@ archived_version = false url_latest_version = "https://docs.dapr.io" [[params.versions]] - version = "v1.11 (preview)" + version = "v1.12 (preview)" url = "#" [[params.versions]] - version = "v1.10 (latest)" + version = "v1.11 (latest)" url = "https://docs.dapr.io" +[[params.versions]] + version = "v1.10" + url = "https://v1-10.docs.dapr.io" [[params.versions]] version = "v1.9" url = "https://v1-9.docs.dapr.io" From e737b9a30c27a43666549290f8584f16a3742192 Mon Sep 17 00:00:00 2001 From: Artur Souza Date: Tue, 13 Jun 2023 10:13:32 -0700 Subject: [PATCH 005/162] Create workflow for 1.12 Signed-off-by: Artur Souza --- .github/workflows/{website-v1-11.yml => website-v1-12.yml} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename .github/workflows/{website-v1-11.yml => website-v1-12.yml} (98%) diff --git a/.github/workflows/website-v1-11.yml b/.github/workflows/website-v1-12.yml similarity index 98% rename from .github/workflows/website-v1-11.yml rename to .github/workflows/website-v1-12.yml index f9df31cf3f1..afd6a60fb4e 100644 --- a/.github/workflows/website-v1-11.yml +++ b/.github/workflows/website-v1-12.yml @@ -3,11 +3,11 @@ name: Azure Static Web App v1.9 on: push: branches: - - v1.11 + - v1.12 pull_request: types: [opened, synchronize, reopened, closed] branches: - - v1.11 + - v1.12 jobs: build_and_deploy_job: From dd379f51d06f3806a3759d37a5a36363aac2ed51 Mon Sep 17 00:00:00 2001 From: Shubham Sharma Date: Wed, 21 Jun 2023 10:29:36 +0530 Subject: [PATCH 006/162] Add new properties to the metadata API (#3562) * Update metadata API docs Signed-off-by: Shubham Sharma * Make type consistent Signed-off-by: Shubham Sharma * Add docs for enabledFeatures Signed-off-by: Shubham Sharma * Update docs Signed-off-by: Shubham Sharma * Update daprdocs/content/en/reference/api/metadata_api.md Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Signed-off-by: Shubham Sharma * Update daprdocs/content/en/reference/api/metadata_api.md Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Signed-off-by: Shubham Sharma * Update daprdocs/content/en/reference/api/metadata_api.md Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Signed-off-by: Shubham Sharma * Update daprdocs/content/en/reference/api/metadata_api.md Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Signed-off-by: Shubham Sharma * Update daprdocs/content/en/reference/api/metadata_api.md Co-authored-by: Mark Fussell Signed-off-by: Shubham Sharma --------- Signed-off-by: Shubham Sharma Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Co-authored-by: Mark Fussell --- .../content/en/reference/api/metadata_api.md | 201 +++++++++++++----- 1 file changed, 145 insertions(+), 56 deletions(-) diff --git a/daprdocs/content/en/reference/api/metadata_api.md b/daprdocs/content/en/reference/api/metadata_api.md index b0c39c85513..737e98671aa 100644 --- a/daprdocs/content/en/reference/api/metadata_api.md +++ b/daprdocs/content/en/reference/api/metadata_api.md @@ -6,9 +6,17 @@ description: "Detailed documentation on the Metadata API" weight: 1100 --- -Dapr has a metadata API that returns information about the sidecar allowing runtime discoverability. The metadata endpoint returns a list of the resources (components and HttpEndpoints loaded), the activated actors (if present), and attributes with information attached. +Dapr has a metadata API that returns information about the sidecar allowing runtime discoverability. The metadata endpoint returns the following information. +- Runtime version +- List of the loaded resources (`components`, `subscriptions` and `HttpEndpoints`) +- Registered actor types +- Features enabled +- Application connection details +- Custom, ephemeral attributes with information. -## Components +## Metadata API + +### Components Each loaded component provides its name, type and version and also information about supported features in the form of component capabilities. These features are available for the [state store]({{< ref supported-state-stores.md >}}) and [binding]({{< ref supported-bindings.md >}}) component types. The table below shows the component type and the list of capabilities for a given version. This list might grow in future and only represents the capabilities of the loaded components. @@ -17,12 +25,21 @@ Component type | Capabilities State Store | ETAG, TRANSACTION, ACTOR, QUERY_API Binding | INPUT_BINDING, OUTPUT_BINDING -## HTTPEndpoints +### HTTPEndpoints Each loaded `HttpEndpoint` provides a name to easily identify the Dapr resource associated with the runtime. -## Attributes +### Subscriptions +The metadata API returns a list of pub/sub subscriptions that the app has registered with the Dapr runtime. This includes the pub/sub name, topic, routes, dead letter topic, and the metadata associated with the subscription. + +### Enabled features +A list of features enabled via Configuration spec (including build-time overrides). + +### App connection details +The metadata API returns information related to Dapr's connection to the app. This includes the app port, protocol, host, max concurrency, along with health check details. -The metadata API allows you to store additional attribute information in the format of key-value pairs. These are ephemeral in-memory and are not persisted if a sidecar is reloaded. This information should be added at the time of a sidecar creation, for example, after the application has started. +### Attributes + +The metadata API allows you to store additional attribute information in the format of key-value pairs. These are ephemeral in-memory and are not persisted if a sidecar is reloaded. This information should be added at the time of a sidecar creation (for example, after the application has started). ## Get the Dapr sidecar information @@ -57,9 +74,13 @@ Code | Description Name | Type | Description ---- | ---- | ----------- id | string | Application ID +runtimeVersion | string | Version of the Dapr runtime actors | [Metadata API Response Registered Actor](#metadataapiresponseactor)[] | A json encoded array of registered actors metadata. extended.attributeName | string | List of custom attributes as key-value pairs, where key is the attribute name. components | [Metadata API Response Component](#metadataapiresponsecomponent)[] | A json encoded array of loaded components metadata. +httpEndpoints | [Metadata API Response HttpEndpoint](#metadataapiresponsehttpendpoint)[] | A json encoded array of loaded HttpEndpoints metadata. +subscriptions | [Metadata API Response Subscription](#metadataapiresponsesubscription)[] | A json encoded array of pub/sub subscriptions metadata. +appConnectionProperties| [Metadata API Response AppConnectionProperties](#metadataapiresponseappconnectionproperties) | A json encoded object of app connection properties. **Metadata API Response Registered Actor** @@ -75,7 +96,50 @@ Name | Type | Description name | string | Name of the component. type | string | Component type. version | string | Component version. -capabilities | array | Supported capabilities for this component type and version. +capabilities | array | Supported capabilities for this component type and version. + +**Metadata API Response HttpEndpoint** + +Name | Type | Description +---- | ---- | ----------- +name | string | Name of the HttpEndpoint. + +**Metadata API Response Subscription** + +Name | Type | Description +---- | ---- | ----------- +pubsubname | string | Name of the pub/sub. +topic | string | Topic name. +metadata | object | Metadata associated with the subscription. +rules | [Metadata API Response Subscription Rules](metadataapiresponsesubscriptionrules)[] | List of rules associated with the subscription. +deadLetterTopic | string | Dead letter topic name. + +**Metadata API Response Subscription Rules** + +Name | Type | Description +---- | ---- | ----------- +match | string | CEL expression to match the message. +path | string | Path to route the message if the match expression is true. + +**Metadata API Response AppConnectionProperties** + +Name | Type | Description +---- | ---- | ----------- +port | integer| Port on which the app is listening. +protocol | string | Protocol used by the app. +channelAddress| string | Host address on which the app is listening. +maxConcurrency| integer| Maximum number of concurrent requests the app can handle. +health | [Metadata API Response AppConnectionProperties Health](#metadataapiresponseappconnectionpropertieshealth) | Health check details of the app. + +**Metadata API Response AppConnectionProperties Health** + +Name | Type | Description +---- | ---- | ----------- +healthCheckPath | string | Health check path, applicable for HTTP protocol. +healthProbeInterval | string | Time between each health probe, in go duration format. +healthProbeTimeout | string | Timeout for each health probe, in go duration format. +healthThreshold | integer | Max number of failed health probes before the app is considered unhealthy. + ### Examples @@ -87,32 +151,44 @@ curl http://localhost:3500/v1.0/metadata ```json { - "id":"demo-actor", - "actors":[ - { - "type":"DemoActor", - "count":1 - } - ], - "extended": { - "cliPID":"1031040", - "appCommand":"uvicorn --port 3000 demo_actor_service:app", - "daprRuntimeVersion": "1.10.0" + "id": "demo-actor", + "runtimeVersion": "1.12.0", + "enabledFeatures": [ + "ServiceInvocationStreaming" + ], + "actors": [ + { + "type": "DemoActor" + } + ], + "components": [ + { + "name": "pubsub", + "type": "pubsub.redis", + "version": "v1" }, - "components":[ - { - "name":"pubsub", - "type":"pubsub.redis", - "version":"v1", - "capabilities": [""] - }, - { - "name":"statestore", - "type":"state.redis", - "version":"v1", - "capabilities": ["ETAG", "TRANSACTION", "ACTOR", "QUERY_API"] - } - ] + { + "name": "statestore", + "type": "state.redis", + "version": "v1", + "capabilities": [ + "ETAG", + "TRANSACTIONAL", + "ACTOR" + ] + } + ], + "extended": { + "appCommand": "uvicorn --port 3000 demo_actor_service:app", + "appPID": "98121", + "cliPID": "98114", + "daprRuntimeVersion": "1.12.0" + }, + "appConnectionProperties": { + "port": 3000, + "protocol": "http", + "channelAddress": "127.0.0.1" + } } ``` @@ -172,32 +248,45 @@ Get the metadata information to confirm your custom attribute was added: ```json { - "id":"demo-actor", - "actors":[ - { - "type":"DemoActor", - "count":1 - } - ], - "extended": { - "myDemoAttribute": "myDemoAttributeValue", - "cliPID":"1031040", - "appCommand":"uvicorn --port 3000 demo_actor_service:app" + "id": "demo-actor", + "runtimeVersion": "1.12.0", + "enabledFeatures": [ + "ServiceInvocationStreaming" + ], + "actors": [ + { + "type": "DemoActor" + } + ], + "components": [ + { + "name": "pubsub", + "type": "pubsub.redis", + "version": "v1" }, - "components":[ - { - "name":"pubsub", - "type":"pubsub.redis", - "version":"v1", - "capabilities": [""] - }, - { - "name":"statestore", - "type":"state.redis", - "version":"v1", - "capabilities": ["ETAG", "TRANSACTION", "ACTOR", "QUERY_API"] - } - ] + { + "name": "statestore", + "type": "state.redis", + "version": "v1", + "capabilities": [ + "ETAG", + "TRANSACTIONAL", + "ACTOR" + ] + } + ], + "extended": { + "myDemoAttribute": "myDemoAttributeValue", + "appCommand": "uvicorn --port 3000 demo_actor_service:app", + "appPID": "98121", + "cliPID": "98114", + "daprRuntimeVersion": "1.12.0" + }, + "appConnectionProperties": { + "port": 3000, + "protocol": "http", + "channelAddress": "127.0.0.1" + } } ``` From 14b4712806eacf779323fb09c68c74c2b1f9839d Mon Sep 17 00:00:00 2001 From: joshvanl Date: Tue, 27 Jun 2023 11:48:25 +0100 Subject: [PATCH 007/162] Adds ETCD v2 Signed-off-by: joshvanl --- daprdocs/content/en/reference/api/actors_api.md | 2 +- .../supported-state-stores/setup-etcd.md | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/daprdocs/content/en/reference/api/actors_api.md b/daprdocs/content/en/reference/api/actors_api.md index 5e4bbd579c1..e2c9920a10d 100644 --- a/daprdocs/content/en/reference/api/actors_api.md +++ b/daprdocs/content/en/reference/api/actors_api.md @@ -77,7 +77,7 @@ Persists the change to the state for an actor as a multi-item transaction. #### TTL -With the [`ActorStateTTL` feature enabled]]({{< ref +With the [`ActorStateTTL` feature enabled]({{< ref "support-preview-features.md" >}}), actor clients can set the `ttlInSeconds` field in the transaction metadata to have the state expire after that many seconds. If the `ttlInSeconds` field is not set, the state will not expire. diff --git a/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-etcd.md b/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-etcd.md index 35834d1c4ce..25def61705b 100644 --- a/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-etcd.md +++ b/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-etcd.md @@ -18,7 +18,9 @@ metadata: name: spec: type: state.etcd - version: v1 + # Supports v1 and v2. Users should always use v2 by default. There is no + # migration path from v1 to v2, see `versioning` below. + version: v2 metadata: - name: endpoints value: # Required. Example: 192.168.0.1:2379,192.168.0.2:2379,192.168.0.3:2379 @@ -38,6 +40,16 @@ spec: The above example uses secrets as plain strings. It is recommended to use a secret store for the secrets as described [here]({{< ref component-secrets.md >}}). {{% /alert %}} + +### Versioning + +Dapr has 2 versions of the Etcd state store component: `v1` and `v2`. + +`v1` and `v2` have the same metadata fields, however `v1` will cause data inconsistencies in apps when using [Actor TTLs]({{< ref "actors_api.md#ttl" >}}) from Dapr `v1.12`. +Users should always use `v2` over `v1` as `v1` is deprecated. +`v1` and `v2` are incompatible and there is no data migration path for `v1` to `v2` on an existing active Etcd cluster and `keyPrefixPath`. +If you are using `v1`, you should continue to use `v1` until you create a new Etcd cluster or use a different `keyPrefixPath`, then use `v2`. + ## Spec metadata fields | Field | Required | Details | Example | From ea499d301f669b604f1909eb71ddeafb3173149b Mon Sep 17 00:00:00 2001 From: Josh van Leeuwen Date: Wed, 28 Jun 2023 09:46:22 +0100 Subject: [PATCH 008/162] Update daprdocs/content/en/reference/components-reference/supported-state-stores/setup-etcd.md Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Signed-off-by: Josh van Leeuwen --- .../components-reference/supported-state-stores/setup-etcd.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-etcd.md b/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-etcd.md index 25def61705b..bf10fb146ff 100644 --- a/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-etcd.md +++ b/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-etcd.md @@ -45,7 +45,7 @@ The above example uses secrets as plain strings. It is recommended to use a secr Dapr has 2 versions of the Etcd state store component: `v1` and `v2`. -`v1` and `v2` have the same metadata fields, however `v1` will cause data inconsistencies in apps when using [Actor TTLs]({{< ref "actors_api.md#ttl" >}}) from Dapr `v1.12`. +While `v1` and `v2` have the same metadata fields, `v1` causes data inconsistencies in apps when using [Actor TTLs]({{< ref "actors_api.md#ttl" >}}) from Dapr v1.12. Users should always use `v2` over `v1` as `v1` is deprecated. `v1` and `v2` are incompatible and there is no data migration path for `v1` to `v2` on an existing active Etcd cluster and `keyPrefixPath`. If you are using `v1`, you should continue to use `v1` until you create a new Etcd cluster or use a different `keyPrefixPath`, then use `v2`. From 8434534f3551f056f7cc874e8fb0642835945d9a Mon Sep 17 00:00:00 2001 From: Josh van Leeuwen Date: Wed, 28 Jun 2023 09:46:35 +0100 Subject: [PATCH 009/162] Update daprdocs/content/en/reference/components-reference/supported-state-stores/setup-etcd.md Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Signed-off-by: Josh van Leeuwen --- .../components-reference/supported-state-stores/setup-etcd.md | 1 - 1 file changed, 1 deletion(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-etcd.md b/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-etcd.md index bf10fb146ff..be1ac128999 100644 --- a/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-etcd.md +++ b/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-etcd.md @@ -46,7 +46,6 @@ The above example uses secrets as plain strings. It is recommended to use a secr Dapr has 2 versions of the Etcd state store component: `v1` and `v2`. While `v1` and `v2` have the same metadata fields, `v1` causes data inconsistencies in apps when using [Actor TTLs]({{< ref "actors_api.md#ttl" >}}) from Dapr v1.12. -Users should always use `v2` over `v1` as `v1` is deprecated. `v1` and `v2` are incompatible and there is no data migration path for `v1` to `v2` on an existing active Etcd cluster and `keyPrefixPath`. If you are using `v1`, you should continue to use `v1` until you create a new Etcd cluster or use a different `keyPrefixPath`, then use `v2`. From e3abfa85ae1fb88a0bd8222cc3465672f6443f27 Mon Sep 17 00:00:00 2001 From: Josh van Leeuwen Date: Wed, 28 Jun 2023 09:46:47 +0100 Subject: [PATCH 010/162] Update daprdocs/content/en/reference/components-reference/supported-state-stores/setup-etcd.md Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Signed-off-by: Josh van Leeuwen --- .../components-reference/supported-state-stores/setup-etcd.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-etcd.md b/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-etcd.md index be1ac128999..a3026d74943 100644 --- a/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-etcd.md +++ b/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-etcd.md @@ -43,7 +43,7 @@ The above example uses secrets as plain strings. It is recommended to use a secr ### Versioning -Dapr has 2 versions of the Etcd state store component: `v1` and `v2`. +Dapr has 2 versions of the Etcd state store component: `v1` and `v2`. It is recommended to use `v2`, as `v1` is deprecated. While `v1` and `v2` have the same metadata fields, `v1` causes data inconsistencies in apps when using [Actor TTLs]({{< ref "actors_api.md#ttl" >}}) from Dapr v1.12. `v1` and `v2` are incompatible and there is no data migration path for `v1` to `v2` on an existing active Etcd cluster and `keyPrefixPath`. From efb3468d8882f7aa581800df83bd6c91a0657ef1 Mon Sep 17 00:00:00 2001 From: Josh van Leeuwen Date: Wed, 28 Jun 2023 09:46:56 +0100 Subject: [PATCH 011/162] Update daprdocs/content/en/reference/components-reference/supported-state-stores/setup-etcd.md Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Signed-off-by: Josh van Leeuwen --- .../components-reference/supported-state-stores/setup-etcd.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-etcd.md b/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-etcd.md index a3026d74943..f700af0eb9d 100644 --- a/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-etcd.md +++ b/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-etcd.md @@ -46,7 +46,7 @@ The above example uses secrets as plain strings. It is recommended to use a secr Dapr has 2 versions of the Etcd state store component: `v1` and `v2`. It is recommended to use `v2`, as `v1` is deprecated. While `v1` and `v2` have the same metadata fields, `v1` causes data inconsistencies in apps when using [Actor TTLs]({{< ref "actors_api.md#ttl" >}}) from Dapr v1.12. -`v1` and `v2` are incompatible and there is no data migration path for `v1` to `v2` on an existing active Etcd cluster and `keyPrefixPath`. +`v1` and `v2` are incompatible with no data migration path for `v1` to `v2` on an existing active Etcd cluster and `keyPrefixPath`. If you are using `v1`, you should continue to use `v1` until you create a new Etcd cluster or use a different `keyPrefixPath`, then use `v2`. ## Spec metadata fields From 4a58c18e2ac04a1e68ae3aa090b486dc07222949 Mon Sep 17 00:00:00 2001 From: Josh van Leeuwen Date: Wed, 28 Jun 2023 09:47:02 +0100 Subject: [PATCH 012/162] Update daprdocs/content/en/reference/components-reference/supported-state-stores/setup-etcd.md Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Signed-off-by: Josh van Leeuwen --- .../components-reference/supported-state-stores/setup-etcd.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-etcd.md b/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-etcd.md index f700af0eb9d..63ee61ac2d0 100644 --- a/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-etcd.md +++ b/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-etcd.md @@ -47,7 +47,7 @@ Dapr has 2 versions of the Etcd state store component: `v1` and `v2`. It is reco While `v1` and `v2` have the same metadata fields, `v1` causes data inconsistencies in apps when using [Actor TTLs]({{< ref "actors_api.md#ttl" >}}) from Dapr v1.12. `v1` and `v2` are incompatible with no data migration path for `v1` to `v2` on an existing active Etcd cluster and `keyPrefixPath`. -If you are using `v1`, you should continue to use `v1` until you create a new Etcd cluster or use a different `keyPrefixPath`, then use `v2`. +If you are using `v1`, you should continue to use `v1` until you create a new Etcd cluster or use a different `keyPrefixPath`. ## Spec metadata fields From 4e6c0d176ad8c475aeecc39d060d82a344d09f74 Mon Sep 17 00:00:00 2001 From: Aaron Crawfis Date: Thu, 13 Jul 2023 16:11:16 -0700 Subject: [PATCH 013/162] Update build steps Signed-off-by: Aaron Crawfis --- .github/workflows/website-root.yml | 95 +++++++++++++++++++++++------ .github/workflows/website-v1-12.yml | 10 +-- 2 files changed, 82 insertions(+), 23 deletions(-) diff --git a/.github/workflows/website-root.yml b/.github/workflows/website-root.yml index 0265713f715..1f8e503e4c2 100644 --- a/.github/workflows/website-root.yml +++ b/.github/workflows/website-root.yml @@ -1,6 +1,7 @@ name: Azure Static Web App Root on: + workflow_dispatch: push: branches: - v1.11 @@ -9,35 +10,66 @@ on: branches: - v1.11 +concurrency: + # Cancel the previously triggered build for only PR build. + group: website-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + jobs: build_and_deploy_job: - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') + name: Build Hugo Website + if: github.event.action != 'closed' runs-on: ubuntu-latest - name: Build and Deploy Job + env: + SWA_BASE: 'proud-bay-0e9e0e81e' + HUGO_ENV: production steps: - - uses: actions/checkout@v3 + - name: Checkout docs repo + uses: actions/checkout@v3 + with: + submodules: true + - name: Setup Node + uses: actions/setup-node@v2 + with: + node-version: '14' + - name: Setup Hugo + uses: peaceiris/actions-hugo@v2.5.0 with: - submodules: recursive - fetch-depth: 0 + hugo-version: 0.102.3 + extended: true - name: Setup Docsy - run: cd daprdocs && git submodule update --init --recursive && sudo npm install -D --save autoprefixer && sudo npm install -D --save postcss-cli - - name: Build And Deploy - id: builddeploy + run: | + cd daprdocs + git submodule update --init --recursive + sudo npm install -D --save autoprefixer + sudo npm install -D --save postcss-cli + - name: Build Hugo Website + run: | + cd daprdocs + git config --global --add safe.directory /github/workspace + if [ $GITHUB_EVENT_NAME == 'pull_request' ]; then + STAGING_URL="https://${SWA_BASE}-${{github.event.number}}.westus2.azurestaticapps.net/" + fi + hugo ${STAGING_URL+-b "$STAGING_URL"} + - name: Deploy docs site uses: Azure/static-web-apps-deploy@v1 - env: - HUGO_ENV: production - HUGO_VERSION: "0.100.2" with: azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_PROUD_BAY_0E9E0E81E }} - skip_deploy_on_missing_secrets: true - repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) + repo_token: ${{ secrets.GITHUB_TOKEN }} action: "upload" - app_location: "/daprdocs" - app_build_command: "git config --global --add safe.directory /github/workspace && hugo" - output_location: "public" - skip_api_build: true + app_location: "daprdocs/public/" + api_location: "daprdocs/public/" + output_location: "" + skip_app_build: true + skip_deploy_on_missing_secrets: true + - name: Upload Hugo artifacts + uses: actions/upload-artifact@v3 + with: + name: hugo_build + path: ./daprdocs/public/ + if-no-files-found: error - close_pull_request_job: + close_staging_site: if: github.event_name == 'pull_request' && github.event.action == 'closed' runs-on: ubuntu-latest name: Close Pull Request Job @@ -48,3 +80,30 @@ jobs: with: azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_PROUD_BAY_0E9E0E81E }} action: "close" + skip_deploy_on_missing_secrets: true + + algolia_index: + name: Index site for Algolia + if: github.event_name == 'push' + needs: ['build_and_deploy_job'] + runs-on: ubuntu-latest + env: + ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }} + ALGOLIA_API_WRITE_KEY: ${{ secrets.ALGOLIA_API_WRITE_KEY }} + ALGOLIA_INDEX_NAME: daprdocs + steps: + - name: Checkout docs repo + uses: actions/checkout@v2 + with: + submodules: false + - name: Download Hugo artifacts + uses: actions/download-artifact@v3 + with: + name: hugo_build + path: site/ + - name: Install Python packages + run: | + pip install --upgrade bs4 + pip install --upgrade 'algoliasearch>=2.0,<3.0' + - name: Index site + run: python ./.github/scripts/algolia.py ./site diff --git a/.github/workflows/website-v1-12.yml b/.github/workflows/website-v1-12.yml index afd6a60fb4e..a16d54978ef 100644 --- a/.github/workflows/website-v1-12.yml +++ b/.github/workflows/website-v1-12.yml @@ -1,4 +1,4 @@ -name: Azure Static Web App v1.9 +name: Azure Static Web App v1.12 on: push: @@ -23,12 +23,12 @@ jobs: run: cd daprdocs && git submodule update --init --recursive && sudo npm install -D --save autoprefixer && sudo npm install -D --save postcss-cli - name: Build And Deploy id: builddeploy - uses: Azure/static-web-apps-deploy@v0.0.1-preview + uses: Azure/static-web-apps-deploy@v1 env: HUGO_ENV: production HUGO_VERSION: "0.100.2" with: - azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_V1_11 }} + azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_V1_12 }} repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) skip_deploy_on_missing_secrets: true action: "upload" @@ -47,8 +47,8 @@ jobs: steps: - name: Close Pull Request id: closepullrequest - uses: Azure/static-web-apps-deploy@v0.0.1-preview + uses: Azure/static-web-apps-deploy@v1 with: - azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_V1_11 }} + azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_V1_12 }} skip_deploy_on_missing_secrets: true action: "close" From 69b95b790d8dc4f0f85593572ac0a8e1bda533ec Mon Sep 17 00:00:00 2001 From: Roman Koval Date: Sun, 23 Jul 2023 16:19:45 -0400 Subject: [PATCH 014/162] document protected pub/sub topics Signed-off-by: Roman Koval --- .../building-blocks/pubsub/pubsub-scopes.md | 49 ++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-scopes.md b/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-scopes.md index a81c7d7d9b5..2e3cd9fecd9 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-scopes.md +++ b/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-scopes.md @@ -11,7 +11,7 @@ description: "Use scopes to limit pub/sub topics to specific applications" [Namespaces or component scopes]({{< ref component-scopes.md >}}) can be used to limit component access to particular applications. These application scopes added to a component limit only the applications with specific IDs to be able to use the component. In addition to this general component scope, the following can be limited for pub/sub components: -- Which topics which can be used (published or subscribed) +- Which topics can be used (published or subscribed) - Which applications are allowed to publish to specific topics - Which applications are allowed to subscribe to specific topics @@ -33,6 +33,9 @@ To use this topic scoping three metadata properties can be set for a pub/sub com - A comma-separated list of allowed topics for all applications. - If `allowedTopics` is not set (default behavior), all topics are valid. `subscriptionScopes` and `publishingScopes` still take place if present. - `publishingScopes` or `subscriptionScopes` can be used in conjunction with `allowedTopics` to add granular limitations +- `spec.metadata.protectedTopics` + - A comma-separated list of protected topics for all applications. + - If a topic is marked as protected then an application must be explicitly granted publish or subscribe permissions through `publishingScopes` or `subscriptionScopes` to publish/subscribe to it. These metadata properties can be used for all pub/sub components. The following examples use Redis as pub/sub component. @@ -152,6 +155,50 @@ The table below shows which application is allowed to subscribe to the topics: | app2 | X | | | | app3 | X | X | | +## Example 4: Mark topics as protected + +If your topic involves sensitive data, each new application must be explicitly listed in the `publishingScopes` and `subscriptionScopes` to ensure it cannot read from or write to that topic. Alternatively, you can designate the topic as 'protected' and grant access only to specific applications that genuinely require it. + +Here is an example of three applications and three topics, two of which are protected: +```yaml +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: pubsub +spec: + type: pubsub.redis + version: v1 + metadata: + - name: redisHost + value: "localhost:6379" + - name: redisPassword + value: "" + - name: protectedTopics + value: "A,B" + - name: publishingScopes + value: "app1=A,B;app2=B" + - name: subscriptionScopes + value: "app1=A,B;app2=B" +``` + +> Note that topics A and B are marked as protected. As a result, even though app3 is not listed under `publishingScopes` or `subscriptionScopes` it cannot interact with these topics. + +The table below shows which application is allowed to publish into the topics: + +| | A | B | C | +|------|---|---|---| +| app1 | X | X | | +| app2 | | X | | +| app3 | | | X | + +The table below shows which application is allowed to subscribe to the topics: + +| | A | B | C | +|------|---|---|---| +| app1 | X | X | | +| app2 | | X | | +| app3 | | | X | + ## Demo From 2a418ed7fe7fd7ed6c04960c591b015c95f17ef7 Mon Sep 17 00:00:00 2001 From: kovalromank Date: Mon, 24 Jul 2023 16:00:47 -0400 Subject: [PATCH 015/162] Update daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-scopes.md Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Signed-off-by: kovalromank --- .../building-blocks/pubsub/pubsub-scopes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-scopes.md b/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-scopes.md index 2e3cd9fecd9..82bc6a28a87 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-scopes.md +++ b/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-scopes.md @@ -157,7 +157,7 @@ The table below shows which application is allowed to subscribe to the topics: ## Example 4: Mark topics as protected -If your topic involves sensitive data, each new application must be explicitly listed in the `publishingScopes` and `subscriptionScopes` to ensure it cannot read from or write to that topic. Alternatively, you can designate the topic as 'protected' and grant access only to specific applications that genuinely require it. +If your topic involves sensitive data, each new application must be explicitly listed in the `publishingScopes` and `subscriptionScopes` to ensure it cannot read from or write to that topic. Alternatively, you can designate the topic as 'protected' (using `protectedTopics`) and grant access only to specific applications that genuinely require it. Here is an example of three applications and three topics, two of which are protected: ```yaml From f05808af11c9ea4068819e14cfaefdbb48e3bdf1 Mon Sep 17 00:00:00 2001 From: kovalromank Date: Mon, 24 Jul 2023 16:00:56 -0400 Subject: [PATCH 016/162] Update daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-scopes.md Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Signed-off-by: kovalromank --- .../building-blocks/pubsub/pubsub-scopes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-scopes.md b/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-scopes.md index 82bc6a28a87..30072424738 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-scopes.md +++ b/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-scopes.md @@ -181,7 +181,7 @@ spec: value: "app1=A,B;app2=B" ``` -> Note that topics A and B are marked as protected. As a result, even though app3 is not listed under `publishingScopes` or `subscriptionScopes` it cannot interact with these topics. +In the example above, topics A and B are marked as protected. As a result, even though `app3` is not listed under `publishingScopes` or `subscriptionScopes`, it cannot interact with these topics. The table below shows which application is allowed to publish into the topics: From 0211dfef6e424d042695e5588ec828c63605c99e Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 2 Aug 2023 14:56:59 -0400 Subject: [PATCH 017/162] scaffold out docs for workflow java sdk Signed-off-by: Hannah Hunter --- .../workflow/howto-author-workflow.md | 253 ++++++---- .../workflow/howto-manage-workflow.md | 89 ++-- .../workflow/workflow-overview.md | 7 +- .../quickstarts/workflow-quickstart.md | 443 +++++++++++------- 4 files changed, 483 insertions(+), 309 deletions(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md index 6caa7f3ffc7..3ca68fde59a 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md @@ -30,7 +30,25 @@ The Dapr sidecar doesn’t load any workflow definitions. Rather, the sidecar si [Workflow activities]({{< ref "workflow-features-concepts.md#workflow-activites" >}}) are the basic unit of work in a workflow and are the tasks that get orchestrated in the business process. -{{< tabs ".NET" Python >}} +{{< tabs Python ".NET" Java >}} + +{{% codetab %}} + + + +Define the workflow activities you'd like your workflow to perform. Activities are a function definition and can take inputs and outputs. The following example creates a counter (activity) called `hello_act` that notifies users of the current counter value. `hello_act` is a function derived from a class called `WorkflowActivityContext`. + +```python +def hello_act(ctx: WorkflowActivityContext, input): + global counter + counter += input + print(f'New counter value is: {counter}!', flush=True) +``` + +[See the `hello_act` workflow activity in context.](https://github.com/dapr/python-sdk/blob/master/examples/demo_workflow/app.py#LL40C1-L43C59) + + +{{% /codetab %}} {{% codetab %}} @@ -102,29 +120,67 @@ public class ProcessPaymentActivity : WorkflowActivity {{% codetab %}} - + -Define the workflow activities you'd like your workflow to perform. Activities are a function definition and can take inputs and outputs. The following example creates a counter (activity) called `hello_act` that notifies users of the current counter value. `hello_act` is a function derived from a class called `WorkflowActivityContext`. +Define the workflow activities you'd like your workflow to perform. -```python -def hello_act(ctx: WorkflowActivityContext, input): - global counter - counter += input - print(f'New counter value is: {counter}!', flush=True) +The activities called in the example below are: +- `need`: Receive notification of a new order. + +### [activity] + +```java +todo ``` -[See the `hello_act` workflow activity in context.](https://github.com/dapr/python-sdk/blob/master/examples/demo_workflow/app.py#LL40C1-L43C59) +[See the full `todo` workflow activity example.](todo) + +### [activity] + +```java +todo +``` +[See the full `todo` workflow activity example.](todo) + +### [todo] + +```java +todo +``` +[See the full `todo` workflow activity example.](todo) {{% /codetab %}} + {{< /tabs >}} ## Write the workflow Next, register and call the activites in a workflow. -{{< tabs ".NET" Python >}} +{{< tabs Python ".NET" Java >}} + +{{% codetab %}} + + + +The `hello_world_wf` function is derived from a class called `DaprWorkflowContext` with input and output parameter types. It also includes a `yield` statement that does the heavy lifting of the workflow and calls the workflow activities. + +```python +def hello_world_wf(ctx: DaprWorkflowContext, input): + print(f'{input}') + yield ctx.call_activity(hello_act, input=1) + yield ctx.call_activity(hello_act, input=10) + yield ctx.wait_for_external_event("event1") + yield ctx.call_activity(hello_act, input=100) + yield ctx.call_activity(hello_act, input=1000) +``` + +[See the `hello_world_wf` workflow in context.](https://github.com/dapr/python-sdk/blob/master/examples/demo_workflow/app.py#LL32C1-L38C51) + + +{{% /codetab %}} {{% codetab %}} @@ -171,21 +227,15 @@ The `OrderProcessingWorkflow` class is derived from a base class called `Workflo {{% codetab %}} - + -The `hello_world_wf` function is derived from a class called `DaprWorkflowContext` with input and output parameter types. It also includes a `yield` statement that does the heavy lifting of the workflow and calls the workflow activities. +Intro -```python -def hello_world_wf(ctx: DaprWorkflowContext, input): - print(f'{input}') - yield ctx.call_activity(hello_act, input=1) - yield ctx.call_activity(hello_act, input=10) - yield ctx.wait_for_external_event("event1") - yield ctx.call_activity(hello_act, input=100) - yield ctx.call_activity(hello_act, input=1000) +```java +todo ``` -[See the `hello_world_wf` workflow in context.](https://github.com/dapr/python-sdk/blob/master/examples/demo_workflow/app.py#LL32C1-L38C51) +[See the `todo` workflow in context.](todo) {{% /codetab %}} @@ -196,78 +246,7 @@ def hello_world_wf(ctx: DaprWorkflowContext, input): Finally, compose the application using the workflow. -{{< tabs ".NET" Python >}} - -{{% codetab %}} - - - -[In the following `Program.cs` example](https://github.com/dapr/dotnet-sdk/blob/master/examples/Workflow/WorkflowConsoleApp/Program.cs), for a basic ASP.NET order processing application using the .NET SDK, your project code would include: - -- A NuGet package called `Dapr.Workflow` to receive the .NET SDK capabilities -- A builder with an extension method called `AddDaprWorkflow` - - This will allow you to register workflows and workflow activities (tasks that workflows can schedule) -- HTTP API calls - - One for submitting a new order - - One for checking the status of an existing order - -```csharp -using Dapr.Workflow; -//... - -// Dapr Workflows are registered as part of the service configuration -builder.Services.AddDaprWorkflow(options => -{ - // Note that it's also possible to register a lambda function as the workflow - // or activity implementation instead of a class. - options.RegisterWorkflow(); - - // These are the activities that get invoked by the workflow(s). - options.RegisterActivity(); - options.RegisterActivity(); - options.RegisterActivity(); -}); - -WebApplication app = builder.Build(); - -// POST starts new order workflow instance -app.MapPost("/orders", async (WorkflowEngineClient client, [FromBody] OrderPayload orderInfo) => -{ - if (orderInfo?.Name == null) - { - return Results.BadRequest(new - { - message = "Order data was missing from the request", - example = new OrderPayload("Paperclips", 99.95), - }); - } - -//... -}); - -// GET fetches state for order workflow to report status -app.MapGet("/orders/{orderId}", async (string orderId, WorkflowEngineClient client) => -{ - WorkflowState state = await client.GetWorkflowStateAsync(orderId, true); - if (!state.Exists) - { - return Results.NotFound($"No order with ID = '{orderId}' was found."); - } - - var httpResponsePayload = new - { - details = state.ReadInputAs(), - status = state.RuntimeStatus.ToString(), - result = state.ReadOutputAs(), - }; - -//... -}).WithName("GetOrderInfoEndpoint"); - -app.Run(); -``` - -{{% /codetab %}} +{{< tabs Python ".NET" Java >}} {{% codetab %}} @@ -356,6 +335,91 @@ if __name__ == '__main__': ``` +{{% /codetab %}} + +{{% codetab %}} + + + +[In the following `Program.cs` example](https://github.com/dapr/dotnet-sdk/blob/master/examples/Workflow/WorkflowConsoleApp/Program.cs), for a basic ASP.NET order processing application using the .NET SDK, your project code would include: + +- A NuGet package called `Dapr.Workflow` to receive the .NET SDK capabilities +- A builder with an extension method called `AddDaprWorkflow` + - This will allow you to register workflows and workflow activities (tasks that workflows can schedule) +- HTTP API calls + - One for submitting a new order + - One for checking the status of an existing order + +```csharp +using Dapr.Workflow; +//... + +// Dapr Workflows are registered as part of the service configuration +builder.Services.AddDaprWorkflow(options => +{ + // Note that it's also possible to register a lambda function as the workflow + // or activity implementation instead of a class. + options.RegisterWorkflow(); + + // These are the activities that get invoked by the workflow(s). + options.RegisterActivity(); + options.RegisterActivity(); + options.RegisterActivity(); +}); + +WebApplication app = builder.Build(); + +// POST starts new order workflow instance +app.MapPost("/orders", async (WorkflowEngineClient client, [FromBody] OrderPayload orderInfo) => +{ + if (orderInfo?.Name == null) + { + return Results.BadRequest(new + { + message = "Order data was missing from the request", + example = new OrderPayload("Paperclips", 99.95), + }); + } + +//... +}); + +// GET fetches state for order workflow to report status +app.MapGet("/orders/{orderId}", async (string orderId, WorkflowEngineClient client) => +{ + WorkflowState state = await client.GetWorkflowStateAsync(orderId, true); + if (!state.Exists) + { + return Results.NotFound($"No order with ID = '{orderId}' was found."); + } + + var httpResponsePayload = new + { + details = state.ReadInputAs(), + status = state.RuntimeStatus.ToString(), + result = state.ReadOutputAs(), + }; + +//... +}).WithName("GetOrderInfoEndpoint"); + +app.Run(); +``` + +{{% /codetab %}} + +{{% codetab %}} + + + +[In the following example](todo), for a basic Java hello world application using the Java SDK, your project code would include: + +- A Java package called `todo` to receive the Java SDK capabilities. + +```java +todo +``` + {{% /codetab %}} @@ -377,5 +441,6 @@ Now that you've authored a workflow, learn how to manage it. - [Workflow overview]({{< ref workflow-overview.md >}}) - [Workflow API reference]({{< ref workflow_api.md >}}) - Try out the full SDK examples: - - [.NET example](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow) - [Python example](https://github.com/dapr/python-sdk/tree/master/examples/demo_workflow) + - [.NET example](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow) + - [Java example](todo) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md index 99cb87c9b58..27d5d75cb05 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md @@ -8,43 +8,7 @@ description: Manage and run workflows Now that you've [authored the workflow and its activities in your application]({{< ref howto-author-workflow.md >}}), you can start, terminate, and get information about the workflow using HTTP API calls. For more information, read the [workflow API reference]({{< ref workflow_api.md >}}). -{{< tabs ".NET" Python HTTP >}} - - -{{% codetab %}} - -Manage your workflow within your code. In the `OrderProcessingWorkflow` example from the [Author a workflow]({{< ref "howto-author-workflow.md#write-the-application" >}}) guide, the workflow is registered in the code. You can now start, terminate, and get information about a running workflow: - -```csharp -string orderId = "exampleOrderId"; -string workflowComponent = "dapr"; -string workflowName = "OrderProcessingWorkflow"; -OrderPayload input = new OrderPayload("Paperclips", 99.95); -Dictionary workflowOptions; // This is an optional parameter - -// Start the workflow. This returns back a "StartWorkflowResponse" which contains the instance ID for the particular workflow instance. -StartWorkflowResponse startResponse = await daprClient.StartWorkflowAsync(orderId, workflowComponent, workflowName, input, workflowOptions); - -// Get information on the workflow. This response contains information such as the status of the workflow, when it started, and more! -GetWorkflowResponse getResponse = await daprClient.GetWorkflowAsync(orderId, workflowComponent, workflowName); - -// Terminate the workflow -await daprClient.TerminateWorkflowAsync(orderId, workflowComponent); - -// Raise an event (an incoming purchase order) that your workflow will wait for. This returns the item waiting to be purchased. -await daprClient.RaiseWorkflowEventAsync(orderId, workflowComponent, workflowName, input); - -// Pause -await daprClient.PauseWorkflowAsync(orderId, workflowComponent); - -// Resume -await daprClient.ResumeWorkflowAsync(orderId, workflowComponent); - -// Purge -await daprClient.PurgeWorkflowAsync(orderId, workflowComponent); -``` - -{{% /codetab %}} +{{< tabs Python ".NET" Java HTTP >}} {{% codetab %}} @@ -95,6 +59,53 @@ d.terminate_workflow(instance_id=instanceId, workflow_component=workflowComponen {{% /codetab %}} + +{{% codetab %}} + +Manage your workflow within your code. In the `OrderProcessingWorkflow` example from the [Author a workflow]({{< ref "howto-author-workflow.md#write-the-application" >}}) guide, the workflow is registered in the code. You can now start, terminate, and get information about a running workflow: + +```csharp +string orderId = "exampleOrderId"; +string workflowComponent = "dapr"; +string workflowName = "OrderProcessingWorkflow"; +OrderPayload input = new OrderPayload("Paperclips", 99.95); +Dictionary workflowOptions; // This is an optional parameter + +// Start the workflow. This returns back a "StartWorkflowResponse" which contains the instance ID for the particular workflow instance. +StartWorkflowResponse startResponse = await daprClient.StartWorkflowAsync(orderId, workflowComponent, workflowName, input, workflowOptions); + +// Get information on the workflow. This response contains information such as the status of the workflow, when it started, and more! +GetWorkflowResponse getResponse = await daprClient.GetWorkflowAsync(orderId, workflowComponent, workflowName); + +// Terminate the workflow +await daprClient.TerminateWorkflowAsync(orderId, workflowComponent); + +// Raise an event (an incoming purchase order) that your workflow will wait for. This returns the item waiting to be purchased. +await daprClient.RaiseWorkflowEventAsync(orderId, workflowComponent, workflowName, input); + +// Pause +await daprClient.PauseWorkflowAsync(orderId, workflowComponent); + +// Resume +await daprClient.ResumeWorkflowAsync(orderId, workflowComponent); + +// Purge +await daprClient.PurgeWorkflowAsync(orderId, workflowComponent); +``` + +{{% /codetab %}} + + +{{% codetab %}} + +Manage your workflow within your code. In the workflow example from the [Author a workflow]({{< ref "howto-author-workflow.md#write-the-application" >}}) guide, the workflow is registered in the code using the following APIs: + +```java +todo +``` + +{{% /codetab %}} + {{% codetab %}} @@ -172,6 +183,8 @@ Learn more about these HTTP calls in the [workflow API reference guide]({{< ref ## Next steps - [Try out the Workflow quickstart]({{< ref workflow-quickstart.md >}}) - Try out the full SDK examples: - - [.NET example](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow) - [Python example](https://github.com/dapr/python-sdk/blob/master/examples/demo_workflow/app.py) + - [.NET example](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow) + - [Java example](todo) + - [Workflow API reference]({{< ref workflow_api.md >}}) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md index 9f70500c01f..998e0cccc3a 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md @@ -83,9 +83,9 @@ You can use the following SDKs to author a workflow. | Language stack | Package | | - | - | -| .NET | [Dapr.Workflow](https://www.nuget.org/profiles/dapr.io) | | Python | [dapr-ext-workflow](https://github.com/dapr/python-sdk/tree/master/ext/dapr-ext-workflow) | - +| .NET | [Dapr.Workflow](https://www.nuget.org/profiles/dapr.io) | +| Java | need | ## Try out workflows @@ -96,8 +96,9 @@ Want to put workflows to the test? Walk through the following quickstart and tut | Quickstart/tutorial | Description | | ------------------- | ----------- | | [Workflow quickstart]({{< ref workflow-quickstart.md >}}) | Run a .NET workflow application with four workflow activities to see Dapr Workflow in action | -| [Workflow .NET SDK example](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow) | Learn how to create a Dapr Workflow and invoke it using ASP.NET Core web APIs. | | [Workflow Python SDK example](https://github.com/dapr/python-sdk/tree/master/examples/demo_workflow) | Learn how to create a Dapr Workflow and invoke it using the Python `DaprClient` package. | +| [Workflow .NET SDK example](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow) | Learn how to create a Dapr Workflow and invoke it using ASP.NET Core web APIs. | +| [Workflow Java SDK example](todo) | Learn how to create a Dapr Workflow and invoke it using the Java `need` package. | ### Start using workflows directly in your app diff --git a/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md index d139561cb04..69d94afea3f 100644 --- a/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md @@ -21,7 +21,245 @@ In this guide, you'll: -{{< tabs ".NET" "Python" >}} +{{< tabs "Python" ".NET" "Java" >}} + + +{{% codetab %}} + +The `order-processor` console app starts and manages the `order_processing_workflow`, which simulates purchasing items from a store. The workflow consists of five unique workflow activities, or tasks: + +- `notify_activity`: Utilizes a logger to print out messages throughout the workflow. These messages notify you when: + - You have insufficient inventory + - Your payment couldn't be processed, etc. +- `process_payment_activity`: Processes and authorizes the payment. +- `verify_inventory_activity`: Checks the state store to ensure there is enough inventory present for purchase. +- `update_inventory_activity`: Removes the requested items from the state store and updates the store with the new remaining inventory value. +- `request_approval_activity`: Seeks approval from the manager if payment is greater than 50,000 USD. + +### Step 1: Pre-requisites + +For this example, you will need: + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). +- [Python 3.7+ installed](https://www.python.org/downloads/). + +- [Docker Desktop](https://www.docker.com/products/docker-desktop) + + +### Step 2: Set up the environment + +Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/workflows). + +```bash +git clone https://github.com/dapr/quickstarts.git +``` + +In a new terminal window, navigate to the `order-processor` directory: + +```bash +cd workflows/python/sdk/order-processor +``` + +Install the Dapr Python SDK package: + +```bash +pip3 install -r requirements.txt +``` + +### Step 3: Run the order processor app + +In the terminal, start the order processor app alongside a Dapr sidecar: + +```bash +dapr run --app-id order-processor --resources-path ../../../components/ -- python3 app.py +``` + +> **Note:** Since Python3.exe is not defined in Windows, you may need to use `python app.py` instead of `python3 app.py`. + +This starts the `order-processor` app with unique workflow ID and runs the workflow activities. + +Expected output: + +```bash +== APP == Starting order workflow, purchasing 10 of cars +== APP == 2023-06-06 09:35:52.945 durabletask-worker INFO: Successfully connected to 127.0.0.1:65406. Waiting for work items... +== APP == INFO:NotifyActivity:Received order f4e1926e-3721-478d-be8a-f5bebd1995da for 10 cars at $150000 ! +== APP == INFO:VerifyInventoryActivity:Verifying inventory for order f4e1926e-3721-478d-be8a-f5bebd1995da of 10 cars +== APP == INFO:VerifyInventoryActivity:There are 100 Cars available for purchase +== APP == INFO:RequestApprovalActivity:Requesting approval for payment of 165000 USD for 10 cars +== APP == 2023-06-06 09:36:05.969 durabletask-worker INFO: f4e1926e-3721-478d-be8a-f5bebd1995da Event raised: manager_approval +== APP == INFO:NotifyActivity:Payment for order f4e1926e-3721-478d-be8a-f5bebd1995da has been approved! +== APP == INFO:ProcessPaymentActivity:Processing payment: f4e1926e-3721-478d-be8a-f5bebd1995da for 10 cars at 150000 USD +== APP == INFO:ProcessPaymentActivity:Payment for request ID f4e1926e-3721-478d-be8a-f5bebd1995da processed successfully +== APP == INFO:UpdateInventoryActivity:Checking inventory for order f4e1926e-3721-478d-be8a-f5bebd1995da for 10 cars +== APP == INFO:UpdateInventoryActivity:There are now 90 cars left in stock +== APP == INFO:NotifyActivity:Order f4e1926e-3721-478d-be8a-f5bebd1995da has completed! +== APP == 2023-06-06 09:36:06.106 durabletask-worker INFO: f4e1926e-3721-478d-be8a-f5bebd1995da: Orchestration completed with status: COMPLETED +== APP == Workflow completed! Result: Completed +== APP == Purchase of item is Completed +``` + +### (Optional) Step 4: View in Zipkin + +If you have Zipkin configured for Dapr locally on your machine, you can view the workflow trace spans in the Zipkin web UI (typically at `http://localhost:9411/zipkin/`). + + + +### What happened? + +When you ran `dapr run --app-id order-processor --resources-path ../../../components/ -- python3 app.py`: + +1. A unique order ID for the workflow is generated (in the above example, `f4e1926e-3721-478d-be8a-f5bebd1995da`) and the workflow is scheduled. +1. The `NotifyActivity` workflow activity sends a notification saying an order for 10 cars has been received. +1. The `ReserveInventoryActivity` workflow activity checks the inventory data, determines if you can supply the ordered item, and responds with the number of cars in stock. +1. Your workflow starts and notifies you of its status. +1. The `ProcessPaymentActivity` workflow activity begins processing payment for order `f4e1926e-3721-478d-be8a-f5bebd1995da` and confirms if successful. +1. The `UpdateInventoryActivity` workflow activity updates the inventory with the current available cars after the order has been processed. +1. The `NotifyActivity` workflow activity sends a notification saying that order `f4e1926e-3721-478d-be8a-f5bebd1995da` has completed. +1. The workflow terminates as completed. + +#### `order-processor/app.py` + +In the application's program file: +- The unique workflow order ID is generated +- The workflow is scheduled +- The workflow status is retrieved +- The workflow and the workflow activities it invokes are registered + +```python +class WorkflowConsoleApp: + def main(self): + # Register workflow and activities + workflowRuntime = WorkflowRuntime(settings.DAPR_RUNTIME_HOST, settings.DAPR_GRPC_PORT) + workflowRuntime.register_workflow(order_processing_workflow) + workflowRuntime.register_activity(notify_activity) + workflowRuntime.register_activity(requst_approval_activity) + workflowRuntime.register_activity(verify_inventory_activity) + workflowRuntime.register_activity(process_payment_activity) + workflowRuntime.register_activity(update_inventory_activity) + workflowRuntime.start() + + print("==========Begin the purchase of item:==========", flush=True) + item_name = default_item_name + order_quantity = 10 + + total_cost = int(order_quantity) * baseInventory[item_name].per_item_cost + order = OrderPayload(item_name=item_name, quantity=int(order_quantity), total_cost=total_cost) + + # Start Workflow + print(f'Starting order workflow, purchasing {order_quantity} of {item_name}', flush=True) + start_resp = daprClient.start_workflow(workflow_component=workflow_component, + workflow_name=workflow_name, + input=order) + _id = start_resp.instance_id + + def prompt_for_approval(daprClient: DaprClient): + daprClient.raise_workflow_event(instance_id=_id, workflow_component=workflow_component, + event_name="manager_approval", event_data={'approval': True}) + + approval_seeked = False + start_time = datetime.now() + while True: + time_delta = datetime.now() - start_time + state = daprClient.get_workflow(instance_id=_id, workflow_component=workflow_component) + if not state: + print("Workflow not found!") # not expected + elif state.runtime_status == "Completed" or\ + state.runtime_status == "Failed" or\ + state.runtime_status == "Terminated": + print(f'Workflow completed! Result: {state.runtime_status}', flush=True) + break + if time_delta.total_seconds() >= 10: + state = daprClient.get_workflow(instance_id=_id, workflow_component=workflow_component) + if total_cost > 50000 and ( + state.runtime_status != "Completed" or + state.runtime_status != "Failed" or + state.runtime_status != "Terminated" + ) and not approval_seeked: + approval_seeked = True + threading.Thread(target=prompt_for_approval(daprClient), daemon=True).start() + + print("Purchase of item is ", state.runtime_status, flush=True) + + def restock_inventory(self, daprClient: DaprClient, baseInventory): + for key, item in baseInventory.items(): + print(f'item: {item}') + item_str = f'{{"name": "{item.item_name}", "quantity": {item.quantity},\ + "per_item_cost": {item.per_item_cost}}}' + daprClient.save_state("statestore-actors", key, item_str) + +if __name__ == '__main__': + app = WorkflowConsoleApp() + app.main() +``` + +#### `order-processor/workflow.py` + +In `workflow.py`, the workflow is defined as a class with all of its associated tasks (determined by workflow activities). + +```python + def order_processing_workflow(ctx: DaprWorkflowContext, order_payload_str: OrderPayload): + """Defines the order processing workflow. + When the order is received, the inventory is checked to see if there is enough inventory to + fulfill the order. If there is enough inventory, the payment is processed and the inventory is + updated. If there is not enough inventory, the order is rejected. + If the total order is greater than $50,000, the order is sent to a manager for approval. + """ + order_id = ctx.instance_id + order_payload=json.loads(order_payload_str) + yield ctx.call_activity(notify_activity, + input=Notification(message=('Received order ' +order_id+ ' for ' + +f'{order_payload["quantity"]}' +' ' +f'{order_payload["item_name"]}' + +' at $'+f'{order_payload["total_cost"]}' +' !'))) + result = yield ctx.call_activity(verify_inventory_activity, + input=InventoryRequest(request_id=order_id, + item_name=order_payload["item_name"], + quantity=order_payload["quantity"])) + if not result.success: + yield ctx.call_activity(notify_activity, + input=Notification(message='Insufficient inventory for ' + +f'{order_payload["item_name"]}'+'!')) + return OrderResult(processed=False) + + if order_payload["total_cost"] > 50000: + yield ctx.call_activity(requst_approval_activity, input=order_payload) + approval_task = ctx.wait_for_external_event("manager_approval") + timeout_event = ctx.create_timer(timedelta(seconds=200)) + winner = yield when_any([approval_task, timeout_event]) + if winner == timeout_event: + yield ctx.call_activity(notify_activity, + input=Notification(message='Payment for order '+order_id + +' has been cancelled due to timeout!')) + return OrderResult(processed=False) + approval_result = yield approval_task + if approval_result["approval"]: + yield ctx.call_activity(notify_activity, input=Notification( + message=f'Payment for order {order_id} has been approved!')) + else: + yield ctx.call_activity(notify_activity, input=Notification( + message=f'Payment for order {order_id} has been rejected!')) + return OrderResult(processed=False) + + yield ctx.call_activity(process_payment_activity, input=PaymentRequest( + request_id=order_id, item_being_purchased=order_payload["item_name"], + amount=order_payload["total_cost"], quantity=order_payload["quantity"])) + + try: + yield ctx.call_activity(update_inventory_activity, + input=PaymentRequest(request_id=order_id, + item_being_purchased=order_payload["item_name"], + amount=order_payload["total_cost"], + quantity=order_payload["quantity"])) + except Exception: + yield ctx.call_activity(notify_activity, + input=Notification(message=f'Order {order_id} Failed!')) + return OrderResult(processed=False) + + yield ctx.call_activity(notify_activity, input=Notification( + message=f'Order {order_id} has completed!')) + return OrderResult(processed=True) +``` +{{% /codetab %}} {{% codetab %}} @@ -256,25 +494,22 @@ The `Activities` directory holds the four workflow activities used by the workfl {{% /codetab %}} - + {{% codetab %}} -The `order-processor` console app starts and manages the `order_processing_workflow`, which simulates purchasing items from a store. The workflow consists of five unique workflow activities, or tasks: +The `order-processor` console app starts and manages the lifecycle of an order processing workflow that stores and retrieves data in a state store. The workflow consists of four workflow activities, or tasks: +- -- `notify_activity`: Utilizes a logger to print out messages throughout the workflow. These messages notify you when: - - You have insufficient inventory - - Your payment couldn't be processed, etc. -- `process_payment_activity`: Processes and authorizes the payment. -- `verify_inventory_activity`: Checks the state store to ensure there is enough inventory present for purchase. -- `update_inventory_activity`: Removes the requested items from the state store and updates the store with the new remaining inventory value. -- `request_approval_activity`: Seeks approval from the manager if payment is greater than 50,000 USD. ### Step 1: Pre-requisites For this example, you will need: - [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). -- [Python 3.7+ installed](https://www.python.org/downloads/). +- Java JDK 11 (or greater): + - [Oracle JDK](https://www.oracle.com/java/technologies/downloads), or + - OpenJDK +- [Apache Maven](https://maven.apache.org/install.html), version 3.x. - [Docker Desktop](https://www.docker.com/products/docker-desktop) @@ -290,13 +525,7 @@ git clone https://github.com/dapr/quickstarts.git In a new terminal window, navigate to the `order-processor` directory: ```bash -cd workflows/python/sdk/order-processor -``` - -Install the Dapr Python SDK package: - -```bash -pip3 install -r requirements.txt +need ``` ### Step 3: Run the order processor app @@ -304,54 +533,37 @@ pip3 install -r requirements.txt In the terminal, start the order processor app alongside a Dapr sidecar: ```bash -dapr run --app-id order-processor --resources-path ../../../components/ -- python3 app.py +need ``` -> **Note:** Since Python3.exe is not defined in Windows, you may need to use `python app.py` instead of `python3 app.py`. - This starts the `order-processor` app with unique workflow ID and runs the workflow activities. Expected output: -```bash -== APP == Starting order workflow, purchasing 10 of cars -== APP == 2023-06-06 09:35:52.945 durabletask-worker INFO: Successfully connected to 127.0.0.1:65406. Waiting for work items... -== APP == INFO:NotifyActivity:Received order f4e1926e-3721-478d-be8a-f5bebd1995da for 10 cars at $150000 ! -== APP == INFO:VerifyInventoryActivity:Verifying inventory for order f4e1926e-3721-478d-be8a-f5bebd1995da of 10 cars -== APP == INFO:VerifyInventoryActivity:There are 100 Cars available for purchase -== APP == INFO:RequestApprovalActivity:Requesting approval for payment of 165000 USD for 10 cars -== APP == 2023-06-06 09:36:05.969 durabletask-worker INFO: f4e1926e-3721-478d-be8a-f5bebd1995da Event raised: manager_approval -== APP == INFO:NotifyActivity:Payment for order f4e1926e-3721-478d-be8a-f5bebd1995da has been approved! -== APP == INFO:ProcessPaymentActivity:Processing payment: f4e1926e-3721-478d-be8a-f5bebd1995da for 10 cars at 150000 USD -== APP == INFO:ProcessPaymentActivity:Payment for request ID f4e1926e-3721-478d-be8a-f5bebd1995da processed successfully -== APP == INFO:UpdateInventoryActivity:Checking inventory for order f4e1926e-3721-478d-be8a-f5bebd1995da for 10 cars -== APP == INFO:UpdateInventoryActivity:There are now 90 cars left in stock -== APP == INFO:NotifyActivity:Order f4e1926e-3721-478d-be8a-f5bebd1995da has completed! -== APP == 2023-06-06 09:36:06.106 durabletask-worker INFO: f4e1926e-3721-478d-be8a-f5bebd1995da: Orchestration completed with status: COMPLETED -== APP == Workflow completed! Result: Completed -== APP == Purchase of item is Completed +``` +need ``` ### (Optional) Step 4: View in Zipkin If you have Zipkin configured for Dapr locally on your machine, you can view the workflow trace spans in the Zipkin web UI (typically at `http://localhost:9411/zipkin/`). - + ### What happened? -When you ran `dapr run --app-id order-processor --resources-path ../../../components/ -- python3 app.py`: +When you ran `need`: -1. A unique order ID for the workflow is generated (in the above example, `f4e1926e-3721-478d-be8a-f5bebd1995da`) and the workflow is scheduled. -1. The `NotifyActivity` workflow activity sends a notification saying an order for 10 cars has been received. -1. The `ReserveInventoryActivity` workflow activity checks the inventory data, determines if you can supply the ordered item, and responds with the number of cars in stock. +1. A unique order ID for the workflow is generated (in the above example, `need`) and the workflow is scheduled. +1. The `need` workflow activity sends a notification saying an order for 10 cars has been received. +1. The `need` workflow activity checks the inventory data, determines if you can supply the ordered item, and responds with the number of cars in stock. 1. Your workflow starts and notifies you of its status. -1. The `ProcessPaymentActivity` workflow activity begins processing payment for order `f4e1926e-3721-478d-be8a-f5bebd1995da` and confirms if successful. -1. The `UpdateInventoryActivity` workflow activity updates the inventory with the current available cars after the order has been processed. -1. The `NotifyActivity` workflow activity sends a notification saying that order `f4e1926e-3721-478d-be8a-f5bebd1995da` has completed. +1. The `need` workflow activity begins processing payment for order `need` and confirms if successful. +1. The `need` workflow activity updates the inventory with the current available cars after the order has been processed. +1. The `need` workflow activity sends a notification saying that order `need` has completed. 1. The workflow terminates as completed. -#### `order-processor/app.py` +#### `order-processor/... need` In the application's program file: - The unique workflow order ID is generated @@ -359,142 +571,25 @@ In the application's program file: - The workflow status is retrieved - The workflow and the workflow activities it invokes are registered -```python -class WorkflowConsoleApp: - def main(self): - # Register workflow and activities - workflowRuntime = WorkflowRuntime(settings.DAPR_RUNTIME_HOST, settings.DAPR_GRPC_PORT) - workflowRuntime.register_workflow(order_processing_workflow) - workflowRuntime.register_activity(notify_activity) - workflowRuntime.register_activity(requst_approval_activity) - workflowRuntime.register_activity(verify_inventory_activity) - workflowRuntime.register_activity(process_payment_activity) - workflowRuntime.register_activity(update_inventory_activity) - workflowRuntime.start() - - print("==========Begin the purchase of item:==========", flush=True) - item_name = default_item_name - order_quantity = 10 - - total_cost = int(order_quantity) * baseInventory[item_name].per_item_cost - order = OrderPayload(item_name=item_name, quantity=int(order_quantity), total_cost=total_cost) - - # Start Workflow - print(f'Starting order workflow, purchasing {order_quantity} of {item_name}', flush=True) - start_resp = daprClient.start_workflow(workflow_component=workflow_component, - workflow_name=workflow_name, - input=order) - _id = start_resp.instance_id - - def prompt_for_approval(daprClient: DaprClient): - daprClient.raise_workflow_event(instance_id=_id, workflow_component=workflow_component, - event_name="manager_approval", event_data={'approval': True}) +```java +need +``` - approval_seeked = False - start_time = datetime.now() - while True: - time_delta = datetime.now() - start_time - state = daprClient.get_workflow(instance_id=_id, workflow_component=workflow_component) - if not state: - print("Workflow not found!") # not expected - elif state.runtime_status == "Completed" or\ - state.runtime_status == "Failed" or\ - state.runtime_status == "Terminated": - print(f'Workflow completed! Result: {state.runtime_status}', flush=True) - break - if time_delta.total_seconds() >= 10: - state = daprClient.get_workflow(instance_id=_id, workflow_component=workflow_component) - if total_cost > 50000 and ( - state.runtime_status != "Completed" or - state.runtime_status != "Failed" or - state.runtime_status != "Terminated" - ) and not approval_seeked: - approval_seeked = True - threading.Thread(target=prompt_for_approval(daprClient), daemon=True).start() - - print("Purchase of item is ", state.runtime_status, flush=True) +#### `order-processor/... need` - def restock_inventory(self, daprClient: DaprClient, baseInventory): - for key, item in baseInventory.items(): - print(f'item: {item}') - item_str = f'{{"name": "{item.item_name}", "quantity": {item.quantity},\ - "per_item_cost": {item.per_item_cost}}}' - daprClient.save_state("statestore-actors", key, item_str) +In `need`, the workflow is defined as a class with all of its associated tasks (determined by workflow activities). -if __name__ == '__main__': - app = WorkflowConsoleApp() - app.main() +```java +need ``` -#### `order-processor/workflow.py` - -In `workflow.py`, the workflow is defined as a class with all of its associated tasks (determined by workflow activities). +#### `order-processor/... need` directory -```python - def order_processing_workflow(ctx: DaprWorkflowContext, order_payload_str: OrderPayload): - """Defines the order processing workflow. - When the order is received, the inventory is checked to see if there is enough inventory to - fulfill the order. If there is enough inventory, the payment is processed and the inventory is - updated. If there is not enough inventory, the order is rejected. - If the total order is greater than $50,000, the order is sent to a manager for approval. - """ - order_id = ctx.instance_id - order_payload=json.loads(order_payload_str) - yield ctx.call_activity(notify_activity, - input=Notification(message=('Received order ' +order_id+ ' for ' - +f'{order_payload["quantity"]}' +' ' +f'{order_payload["item_name"]}' - +' at $'+f'{order_payload["total_cost"]}' +' !'))) - result = yield ctx.call_activity(verify_inventory_activity, - input=InventoryRequest(request_id=order_id, - item_name=order_payload["item_name"], - quantity=order_payload["quantity"])) - if not result.success: - yield ctx.call_activity(notify_activity, - input=Notification(message='Insufficient inventory for ' - +f'{order_payload["item_name"]}'+'!')) - return OrderResult(processed=False) - - if order_payload["total_cost"] > 50000: - yield ctx.call_activity(requst_approval_activity, input=order_payload) - approval_task = ctx.wait_for_external_event("manager_approval") - timeout_event = ctx.create_timer(timedelta(seconds=200)) - winner = yield when_any([approval_task, timeout_event]) - if winner == timeout_event: - yield ctx.call_activity(notify_activity, - input=Notification(message='Payment for order '+order_id - +' has been cancelled due to timeout!')) - return OrderResult(processed=False) - approval_result = yield approval_task - if approval_result["approval"]: - yield ctx.call_activity(notify_activity, input=Notification( - message=f'Payment for order {order_id} has been approved!')) - else: - yield ctx.call_activity(notify_activity, input=Notification( - message=f'Payment for order {order_id} has been rejected!')) - return OrderResult(processed=False) - - yield ctx.call_activity(process_payment_activity, input=PaymentRequest( - request_id=order_id, item_being_purchased=order_payload["item_name"], - amount=order_payload["total_cost"], quantity=order_payload["quantity"])) - - try: - yield ctx.call_activity(update_inventory_activity, - input=PaymentRequest(request_id=order_id, - item_being_purchased=order_payload["item_name"], - amount=order_payload["total_cost"], - quantity=order_payload["quantity"])) - except Exception: - yield ctx.call_activity(notify_activity, - input=Notification(message=f'Order {order_id} Failed!')) - return OrderResult(processed=False) +The `Activities` directory holds the four workflow activities used by the workflow, defined in the following files: +- `need` - yield ctx.call_activity(notify_activity, input=Notification( - message=f'Order {order_id} has completed!')) - return OrderResult(processed=True) -``` {{% /codetab %}} - {{< /tabs >}} ## Watch the demo From 3fdf6d598bee302c2b64b04711a3bd8c36bb873e Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 2 Aug 2023 15:09:07 -0400 Subject: [PATCH 018/162] add java tabs Signed-off-by: Hannah Hunter --- .../workflow/workflow-architecture.md | 5 +- .../workflow/workflow-patterns.md | 374 ++++++++++-------- 2 files changed, 212 insertions(+), 167 deletions(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-architecture.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-architecture.md index da8bf0a44e1..9835725c434 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-architecture.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-architecture.md @@ -189,4 +189,7 @@ See the [Reminder usage and execution guarantees section]({{< ref "workflow-arch - [Workflow overview]({{< ref workflow-overview.md >}}) - [Workflow API reference]({{< ref workflow_api.md >}}) - [Try out the Workflow quickstart]({{< ref workflow-quickstart.md >}}) -- [Try out the .NET example](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow) +- Try out the following examples: + - [Python](https://github.com/dapr/python-sdk/tree/master/examples/demo_workflow) + - [.NET](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow) + - [Java](todo) \ No newline at end of file diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md index 4ff10782be4..29048c683ae 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md @@ -25,42 +25,7 @@ While the pattern is simple, there are many complexities hidden in the implement Dapr Workflow solves these complexities by allowing you to implement the task chaining pattern concisely as a simple function in the programming language of your choice, as shown in the following example. -{{< tabs ".NET" Python >}} - -{{% codetab %}} - - -```csharp -// Expotential backoff retry policy that survives long outages -var retryOptions = new WorkflowTaskOptions -{ - RetryPolicy = new WorkflowRetryPolicy( - firstRetryInterval: TimeSpan.FromMinutes(1), - backoffCoefficient: 2.0, - maxRetryInterval: TimeSpan.FromHours(1), - maxNumberOfAttempts: 10), -}; - -try -{ - var result1 = await context.CallActivityAsync("Step1", wfInput, retryOptions); - var result2 = await context.CallActivityAsync("Step2", result1, retryOptions); - var result3 = await context.CallActivityAsync("Step3", result2, retryOptions); - return string.Join(", ", result4); -} -catch (TaskFailedException) // Task failures are surfaced as TaskFailedException -{ - // Retries expired - apply custom compensation logic - await context.CallActivityAsync("MyCompensation", options: retryOptions); - throw; -} -``` - -{{% alert title="Note" color="primary" %}} -In the example above, `"Step1"`, `"Step2"`, `"Step3"`, and `"MyCompensation"` represent workflow activities, which are functions in your code that actually implement the steps of the workflow. For brevity, these activity implementations are left out of this example. -{{% /alert %}} - -{{% /codetab %}} +{{< tabs Python ".NET" Java >}} {{% codetab %}} @@ -103,9 +68,49 @@ def error_handler(ctx, error): # Do some compensating work ``` -{{% alert title="Note" color="primary" %}} -Workflow retry policies will be available in a future version of the Python SDK. -{{% /alert %}} +> **Note** Workflow retry policies will be available in a future version of the Python SDK. + +{{% /codetab %}} + +{{% codetab %}} + + +```csharp +// Expotential backoff retry policy that survives long outages +var retryOptions = new WorkflowTaskOptions +{ + RetryPolicy = new WorkflowRetryPolicy( + firstRetryInterval: TimeSpan.FromMinutes(1), + backoffCoefficient: 2.0, + maxRetryInterval: TimeSpan.FromHours(1), + maxNumberOfAttempts: 10), +}; + +try +{ + var result1 = await context.CallActivityAsync("Step1", wfInput, retryOptions); + var result2 = await context.CallActivityAsync("Step2", result1, retryOptions); + var result3 = await context.CallActivityAsync("Step3", result2, retryOptions); + return string.Join(", ", result4); +} +catch (TaskFailedException) // Task failures are surfaced as TaskFailedException +{ + // Retries expired - apply custom compensation logic + await context.CallActivityAsync("MyCompensation", options: retryOptions); + throw; +} +``` + +> **Note** In the example above, `"Step1"`, `"Step2"`, `"Step3"`, and `"MyCompensation"` represent workflow activities, which are functions in your code that actually implement the steps of the workflow. For brevity, these activity implementations are left out of this example. + +{{% /codetab %}} + +{{% codetab %}} + + +```java +todo +``` {{% /codetab %}} @@ -135,32 +140,7 @@ In addition to the challenges mentioned in [the previous pattern]({{< ref "workf Dapr Workflows provides a way to express the fan-out/fan-in pattern as a simple function, as shown in the following example: -{{< tabs ".NET" Python >}} - -{{% codetab %}} - - -```csharp -// Get a list of N work items to process in parallel. -object[] workBatch = await context.CallActivityAsync("GetWorkBatch", null); - -// Schedule the parallel tasks, but don't wait for them to complete yet. -var parallelTasks = new List>(workBatch.Length); -for (int i = 0; i < workBatch.Length; i++) -{ - Task task = context.CallActivityAsync("ProcessWorkItem", workBatch[i]); - parallelTasks.Add(task); -} - -// Everything is scheduled. Wait here until all parallel tasks have completed. -await Task.WhenAll(parallelTasks); - -// Aggregate all N outputs and publish the result. -int sum = parallelTasks.Sum(t => t.Result); -await context.CallActivityAsync("PostResults", sum); -``` - -{{% /codetab %}} +{{< tabs Python ".NET" Java >}} {{% codetab %}} @@ -202,6 +182,40 @@ def process_results(ctx, final_result: int): {{% /codetab %}} +{{% codetab %}} + + +```csharp +// Get a list of N work items to process in parallel. +object[] workBatch = await context.CallActivityAsync("GetWorkBatch", null); + +// Schedule the parallel tasks, but don't wait for them to complete yet. +var parallelTasks = new List>(workBatch.Length); +for (int i = 0; i < workBatch.Length; i++) +{ + Task task = context.CallActivityAsync("ProcessWorkItem", workBatch[i]); + parallelTasks.Add(task); +} + +// Everything is scheduled. Wait here until all parallel tasks have completed. +await Task.WhenAll(parallelTasks); + +// Aggregate all N outputs and publish the result. +int sum = parallelTasks.Sum(t => t.Result); +await context.CallActivityAsync("PostResults", sum); +``` + +{{% /codetab %}} + +{{% codetab %}} + + +```java +todo +``` + +{{% /codetab %}} + {{< /tabs >}} The key takeaways from this example are: @@ -302,48 +316,7 @@ Depending on the business needs, there may be a single monitor or there may be m Dapr Workflow supports this pattern natively by allowing you to implement _eternal workflows_. Rather than writing infinite while-loops ([which is an anti-pattern]({{< ref "workflow-features-concepts.md#infinite-loops-and-eternal-workflows" >}})), Dapr Workflow exposes a _continue-as-new_ API that workflow authors can use to restart a workflow function from the beginning with a new input. -{{< tabs ".NET" Python >}} - -{{% codetab %}} - - -```csharp -public override async Task RunAsync(WorkflowContext context, MyEntityState myEntityState) -{ - TimeSpan nextSleepInterval; - - var status = await context.CallActivityAsync("GetStatus"); - if (status == "healthy") - { - myEntityState.IsHealthy = true; - - // Check less frequently when in a healthy state - nextSleepInterval = TimeSpan.FromMinutes(60); - } - else - { - if (myEntityState.IsHealthy) - { - myEntityState.IsHealthy = false; - await context.CallActivityAsync("SendAlert", myEntityState); - } - - // Check more frequently when in an unhealthy state - nextSleepInterval = TimeSpan.FromMinutes(5); - } - - // Put the workflow to sleep until the determined time - await context.CreateTimer(nextSleepInterval); - - // Restart from the beginning with the updated state - context.ContinueAsNew(myEntityState); - return null; -} -``` - -> This example assumes you have a predefined `MyEntityState` class with a boolean `IsHealthy` property. - -{{% /codetab %}} +{{< tabs Python ".NET" Java >}} {{% codetab %}} @@ -392,6 +365,56 @@ def send_alert(ctx, message: str): {{% /codetab %}} +{{% codetab %}} + + +```csharp +public override async Task RunAsync(WorkflowContext context, MyEntityState myEntityState) +{ + TimeSpan nextSleepInterval; + + var status = await context.CallActivityAsync("GetStatus"); + if (status == "healthy") + { + myEntityState.IsHealthy = true; + + // Check less frequently when in a healthy state + nextSleepInterval = TimeSpan.FromMinutes(60); + } + else + { + if (myEntityState.IsHealthy) + { + myEntityState.IsHealthy = false; + await context.CallActivityAsync("SendAlert", myEntityState); + } + + // Check more frequently when in an unhealthy state + nextSleepInterval = TimeSpan.FromMinutes(5); + } + + // Put the workflow to sleep until the determined time + await context.CreateTimer(nextSleepInterval); + + // Restart from the beginning with the updated state + context.ContinueAsNew(myEntityState); + return null; +} +``` + +> This example assumes you have a predefined `MyEntityState` class with a boolean `IsHealthy` property. + +{{% /codetab %}} + +{{% codetab %}} + + +```java +todo +``` + +{{% /codetab %}} + {{< /tabs >}} A workflow implementing the monitor pattern can loop forever or it can terminate itself gracefully by not calling _continue-as-new_. @@ -420,53 +443,7 @@ The following diagram illustrates this flow. The following example code shows how this pattern can be implemented using Dapr Workflow. -{{< tabs ".NET" Python >}} - -{{% codetab %}} - - -```csharp -public override async Task RunAsync(WorkflowContext context, OrderPayload order) -{ - // ...(other steps)... - - // Require orders over a certain threshold to be approved - if (order.TotalCost > OrderApprovalThreshold) - { - try - { - // Request human approval for this order - await context.CallActivityAsync(nameof(RequestApprovalActivity), order); - - // Pause and wait for a human to approve the order - ApprovalResult approvalResult = await context.WaitForExternalEventAsync( - eventName: "ManagerApproval", - timeout: TimeSpan.FromDays(3)); - if (approvalResult == ApprovalResult.Rejected) - { - // The order was rejected, end the workflow here - return new OrderResult(Processed: false); - } - } - catch (TaskCanceledException) - { - // An approval timeout results in automatic order cancellation - return new OrderResult(Processed: false); - } - } - - // ...(other steps)... - - // End the workflow with a success result - return new OrderResult(Processed: true); -} -``` - -{{% alert title="Note" color="primary" %}} -In the example above, `RequestApprovalActivity` is the name of a workflow activity to invoke and `ApprovalResult` is an enumeration defined by the workflow app. For brevity, these definitions were left out of the example code. -{{% /alert %}} - -{{% /codetab %}} +{{< tabs Python ".NET" Java >}} {{% codetab %}} @@ -527,26 +504,65 @@ def place_order(_, order: Order) -> None: {{% /codetab %}} -{{< /tabs >}} +{{% codetab %}} + -The code that delivers the event to resume the workflow execution is external to the workflow. Workflow events can be delivered to a waiting workflow instance using the [raise event]({{< ref "howto-manage-workflow.md#raise-an-event" >}}) workflow management API, as shown in the following example: +```csharp +public override async Task RunAsync(WorkflowContext context, OrderPayload order) +{ + // ...(other steps)... + + // Require orders over a certain threshold to be approved + if (order.TotalCost > OrderApprovalThreshold) + { + try + { + // Request human approval for this order + await context.CallActivityAsync(nameof(RequestApprovalActivity), order); + + // Pause and wait for a human to approve the order + ApprovalResult approvalResult = await context.WaitForExternalEventAsync( + eventName: "ManagerApproval", + timeout: TimeSpan.FromDays(3)); + if (approvalResult == ApprovalResult.Rejected) + { + // The order was rejected, end the workflow here + return new OrderResult(Processed: false); + } + } + catch (TaskCanceledException) + { + // An approval timeout results in automatic order cancellation + return new OrderResult(Processed: false); + } + } -{{< tabs ".NET" Python >}} + // ...(other steps)... + + // End the workflow with a success result + return new OrderResult(Processed: true); +} +``` + +> **Note** In the example above, `RequestApprovalActivity` is the name of a workflow activity to invoke and `ApprovalResult` is an enumeration defined by the workflow app. For brevity, these definitions were left out of the example code. + +{{% /codetab %}} {{% codetab %}} - + -```csharp -// Raise the workflow event to the waiting workflow -await daprClient.RaiseWorkflowEventAsync( - instanceId: orderId, - workflowComponent: "dapr", - eventName: "ManagerApproval", - eventData: ApprovalResult.Approved); +```java +todo ``` {{% /codetab %}} +{{< /tabs >}} + +The code that delivers the event to resume the workflow execution is external to the workflow. Workflow events can be delivered to a waiting workflow instance using the [raise event]({{< ref "howto-manage-workflow.md#raise-an-event" >}}) workflow management API, as shown in the following example: + +{{< tabs Python ".NET" Java >}} + {{% codetab %}} @@ -564,6 +580,29 @@ with DaprClient() as d: {{% /codetab %}} +{{% codetab %}} + + +```csharp +// Raise the workflow event to the waiting workflow +await daprClient.RaiseWorkflowEventAsync( + instanceId: orderId, + workflowComponent: "dapr", + eventName: "ManagerApproval", + eventData: ApprovalResult.Approved); +``` + +{{% /codetab %}} + +{{% codetab %}} + + +```java +todo +``` + +{{% /codetab %}} + {{< /tabs >}} External events don't have to be directly triggered by humans. They can also be triggered by other systems. For example, a workflow may need to pause and wait for a payment to be received. In this case, a payment system might publish an event to a pub/sub topic on receipt of a payment, and a listener on that topic can raise an event to the workflow using the raise event workflow API. @@ -577,4 +616,7 @@ External events don't have to be directly triggered by humans. They can also be - [Try out Dapr Workflows using the quickstart]({{< ref workflow-quickstart.md >}}) - [Workflow overview]({{< ref workflow-overview.md >}}) - [Workflow API reference]({{< ref workflow_api.md >}}) -- [Try out the .NET example](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow) +- Try out the following examples: + - [Python](https://github.com/dapr/python-sdk/tree/master/examples/demo_workflow) + - [.NET](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow) + - [Java](todo) \ No newline at end of file From dd6730849d7d5f1e51a4cc421fda989a52853ba7 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Fri, 4 Aug 2023 12:08:12 -0700 Subject: [PATCH 019/162] Update wasm.md to include some HTTP instructions (#3649) * Update wasm.md to include some HTTP instructions Signed-off-by: Brendan Burns * Update daprdocs/content/en/reference/components-reference/supported-bindings/wasm.md Co-authored-by: Mark Fussell Signed-off-by: Brendan Burns * Update daprdocs/content/en/reference/components-reference/supported-bindings/wasm.md Co-authored-by: Mark Fussell Signed-off-by: Brendan Burns * Update daprdocs/content/en/reference/components-reference/supported-bindings/wasm.md Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Signed-off-by: Brendan Burns * Update daprdocs/content/en/reference/components-reference/supported-bindings/wasm.md Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Signed-off-by: Brendan Burns --------- Signed-off-by: Brendan Burns Co-authored-by: Mark Fussell Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> --- .../components-reference/supported-bindings/wasm.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/wasm.md b/daprdocs/content/en/reference/components-reference/supported-bindings/wasm.md index 0ca9a69d450..3479adcd814 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/wasm.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/wasm.md @@ -41,6 +41,17 @@ Dapr uses [wazero](https://wazero.io) to run these binaries, because it has no dependencies. This allows use of WebAssembly with no installation process except Dapr itself. +The Wasm output binding supports making HTTP client calls using the [wasi-http](https://github.com/WebAssembly/wasi-http) specification. +You can find example code for making HTTP calls in a variety of languages here: +* [Golang](https://github.com/dev-wasm/dev-wasm-go/tree/main/http) +* [C](https://github.com/dev-wasm/dev-wasm-c/tree/main/http) +* [.NET](https://github.com/dev-wasm/dev-wasm-dotnet/tree/main/http) +* [TypeScript](https://github.com/dev-wasm/dev-wasm-ts/tree/main/http) + +{{% alert title="Note" color="primary" %}} +If you just want to make an HTTP call, it is simpler to use the [service invocation API]({{< ref howto-invoke-non-dapr-endpoints.md >}}). However, if you need to add your own logic - for example, filtering or calling to multiple API endpoints - consider using Wasm. +{{% /alert %}} + ## Component format To configure a Wasm binding, create a component of type From 7e1fa65c0e17ccf76e33cac5157dae054798b4fc Mon Sep 17 00:00:00 2001 From: Shivam Kumar Singh Date: Tue, 8 Aug 2023 23:12:18 +0530 Subject: [PATCH 020/162] Add documentation for get embedding operation in Azure OpenAI (#3658) * Add documentation for get embedding Signed-off-by: Shivam Singh * Resolve comments Signed-off-by: Shivam Singh --------- Signed-off-by: Shivam Singh --- .../supported-bindings/openai.md | 62 ++++++++++++++++--- 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/openai.md b/daprdocs/content/en/reference/components-reference/supported-bindings/openai.md index 08dd948fe91..f62950c04b6 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/openai.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/openai.md @@ -26,8 +26,6 @@ spec: value: "1234567890abcdef" - name: endpoint # Required value: "https://myopenai.openai.azure.com" - - name: deploymentId # Required - value: "my-model" ``` {{% alert title="Warning" color="warning" %}} The above example uses `apiKey` as a plain string. It is recommended to use a secret store for the secrets as described [here]({{< ref component-secrets.md >}}). @@ -39,7 +37,6 @@ The above example uses `apiKey` as a plain string. It is recommended to use a s |--------------------|:--------:|--------|---------|---------| | `endpoint` | Y | Output | Azure OpenAI service endpoint URL. | `"https://myopenai.openai.azure.com"` | | `apiKey` | Y* | Output | The access key of the Azure OpenAI service. Only required when not using Azure AD authentication. | `"1234567890abcdef"` | -| `deploymentId` | Y | Output | The name of the model deployment. | `"my-model"` | | `azureTenantId` | Y* | Input | The tenant ID of the Azure OpenAI resource. Only required when `apiKey` is not provided. | `"tenentID"` | | `azureClientId` | Y* | Input | The client ID that should be used by the binding to create or update the Azure OpenAI Subscription and to authenticate incoming messages. Only required when `apiKey` is not provided.| `"clientId"` | | `azureClientSecret` | Y* | Input | The client secret that should be used by the binding to create or update the Azure OpenAI Subscription and to authenticate incoming messages. Only required when `apiKey` is not provided. | `"clientSecret"` | @@ -61,8 +58,6 @@ spec: metadata: - name: endpoint value: "https://myopenai.openai.azure.com" - - name: deploymentId - value: "my-model" - name: azureTenantId value: "***" - name: azureClientId @@ -76,6 +71,7 @@ This component supports **output binding** with the following operations: - `completion` : [Completion API](#completion-api) - `chat-completion` : [Chat Completion API](#chat-completion-api) +- `get-embedding` : [Embedding API](#get-embedding-api) ### Completion API @@ -83,8 +79,9 @@ To call the completion API with a prompt, invoke the Azure OpenAI binding with a ```json { - "operation": "create", + "operation": "completion", "data": { + "deploymentId": "my-model", "prompt": "A dog is", "maxTokens":5 } @@ -93,6 +90,7 @@ To call the completion API with a prompt, invoke the Azure OpenAI binding with a The data parameters are: +- `deploymentId` - string that specifies the model deployment ID to use. - `prompt` - string that specifies the prompt to generate completions for. - `maxTokens` - (optional) defines the max number of tokens to generate. Defaults to 16 for completion API. - `temperature` - (optional) defines the sampling temperature between 0 and 2. Higher values like 0.8 make the output more random, while lower values like 0.2 make it more focused and deterministic. Defaults to 1.0 for completion API. @@ -107,7 +105,7 @@ Read more about the importance and usage of these parameters in the [Azure OpenA {{< tabs Linux >}} {{% codetab %}} ```bash - curl -d '{ "data": {"prompt": "A dog is ", "maxTokens":15}, "operation": "completion" }' \ + curl -d '{ "data": {"deploymentId: "my-model" , "prompt": "A dog is ", "maxTokens":15}, "operation": "completion" }' \ http://localhost:/v1.0/bindings/ ``` {{% /codetab %}} @@ -142,6 +140,7 @@ To perform a chat-completion operation, invoke the Azure OpenAI binding with a ` { "operation": "chat-completion", "data": { + "deploymentId": "my-model", "messages": [ { "role": "system", @@ -161,6 +160,7 @@ To perform a chat-completion operation, invoke the Azure OpenAI binding with a ` The data parameters are: +- `deploymentId` - string that specifies the model deployment ID to use. - `messages` - array of messages that will be used to generate chat completions. Each message is of the form: - `role` - string that specifies the role of the message. Can be either `user`, `system` or `assistant`. @@ -180,6 +180,7 @@ Each message is of the form: ```bash curl -d '{ "data": { + "deploymentId": "my-model", "messages": [ { "role": "system", @@ -228,6 +229,53 @@ The response body contains the following JSON: ``` +### Get Embedding API + +The `get-embedding` operation returns a vector representation of a given input that can be easily consumed by machine learning models and other algorithms. +To perform a `get-embedding` operation, invoke the Azure OpenAI binding with a `POST` method and the following JSON body: + +```json +{ + "operation": "get-embedding", + "data": { + "deploymentId": "my-model", + "message": "The capital of France is Paris." + } +} +``` + +The data parameters are: + +- `deploymentId` - string that specifies the model deployment ID to use. +- `message` - string that specifies the text to embed. + +#### Example + +{{< tabs Linux >}} + +{{% codetab %}} + ```bash +curl -d '{ + "data": { + "deploymentId": "embeddings", + "message": "The capital of France is Paris." + }, + "operation": "get-embedding" +}' \ +http://localhost:/v1.0/bindings/ + ``` +{{% /codetab %}} + +{{< /tabs >}} + +#### Response + +The response body contains the following JSON: + +```json +[0.018574921,-0.00023652936,-0.0057790717,.... (1536 floats total for ada)] +``` + ## Related links From fe86f32e793a05fe03872638352897fbca1d19b9 Mon Sep 17 00:00:00 2001 From: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> Date: Mon, 11 Sep 2023 10:39:35 +0530 Subject: [PATCH 021/162] adding k8s fields for dev/test env Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> --- .../multi-app-dapr-run/multi-app-template.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md index 8bca3008036..7ecff8d2eea 100644 --- a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md +++ b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md @@ -86,6 +86,8 @@ apps: command: ["python3", "app.py"] appLogDestination: file # (optional), can be file, console or fileAndConsole. default is fileAndConsole. daprdLogDestination: file # (optional), can be file, console or fileAndConsole. default is file. + containerImage: ghcr.io/dapr/samples/hello-k8s-node:latest # (optional) URI of the container image to be used when deploying to Kubernetes dev/test environment. + createService: true # (optional) Create a Kubernetes service for the application when deploying to dev/test environment. - appID: backend # optional appDirPath: .dapr/backend/ # REQUIRED appProtocol: grpc @@ -145,6 +147,8 @@ The properties for the Multi-App Run template align with the `dapr run` CLI flag | `env` | N | Map to environment variable; environment variables applied per application will overwrite environment variables shared across applications | `DEBUG`, `DAPR_HOST_ADD` | | `appLogDestination` | N | Log destination for outputting app logs; Its value can be file, console or fileAndConsole. Default is fileAndConsole | `file`, `console`, `fileAndConsole` | | `daprdLogDestination` | N | Log destination for outputting daprd logs; Its value can be file, console or fileAndConsole. Default is file | `file`, `console`, `fileAndConsole` | +| `containerImage`| N | URI of the container image to be used when deploying to Kubernetes dev/test environment. | `ghcr.io/dapr/samples/hello-k8s-python:latest` +| `createService`| N | Create a Kubernetes service for the application when deploying to dev/test environment. | `true`, `false` | ## Next steps From 7a8861c674e5d1a47c799d35be2ac16582261ffd Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Mon, 11 Sep 2023 16:58:58 -0400 Subject: [PATCH 022/162] add table.html and work on multi app run overview and how to for k8s Signed-off-by: Hannah Hunter --- .../multi-app-dapr-run/multi-app-overview.md | 47 ++++- .../multi-app-dapr-run/multi-app-template.md | 161 +++++++++++++++++- daprdocs/layouts/shortcodes/table.html | 6 + 3 files changed, 210 insertions(+), 4 deletions(-) create mode 100644 daprdocs/layouts/shortcodes/table.html diff --git a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md index 5fac41131e7..78fd3c27c74 100644 --- a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md +++ b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md @@ -19,11 +19,15 @@ Instead, you simply want to run them as local executables in self-hosted mode. - Remember the resources folders and configuration files that each application refers to. - Recall all of the additional flags you used to tweak the `dapr run` command behavior (`--app-health-check-path`, `--dapr-grpc-port`, `--unix-domain-socket`, etc.) +{{< tabs Self-hosted Kubernetes>}} + +{{% codetab %}} + With Multi-App Run, you can start multiple applications in self-hosted mode using a single `dapr run -f` command using a template file. The template file describes how to start multiple applications as if you had run many separate CLI `run`commands. By default, this template file is called `dapr.yaml`. ## Multi-App Run template file -When you execute `dapr run -f .`, it uses the multi-app template file (named `dapr.yaml`) present in the current directory to run all the applications. +When you execute `dapr run -f .`, it generates the multi-app template file (named `dapr.yaml`) present in the current directory to run all the applications. You can name template file with preferred name other than the default. For example `dapr run -f ./.yaml`. @@ -47,6 +51,47 @@ apps: For a more in-depth example and explanation of the template properties, see [Multi-app template]({{< ref multi-app-template.md >}}). +{{% /codetab %}} + +{{% codetab %}} + +With Multi-App Run, you can start multiple applications in a Kubernetes development or test environment using a single `dapr run -f -k` command using a template file. The template file describes how to start multiple applications as if you had run many separate CLI `run`commands. By default, this template file is called `dapr.yaml`. + +## Multi-App Run template file + +When you execute `dapr run -f .`, Dapr generates the multi-app template file (named `dapr.yaml`) in the `.dapr/deploy` directory within each application. Each time you run `dapr run -f -k .`, the generated Kubernetes Deployment YAML file is overwritten. run all the applications. + +You can name template file with preferred name other than the default. For example `dapr run -f -k ./.yaml`. + +The following example includes some of the template properties you can customize for your applications. In the example, you can simultaneously launch 2 applications with app IDs of `nodeapp` and `pythonapp`. + +```yaml +version: 1 +common: +apps: + - appID: nodeapp + appDirPath: ./nodeapp/ + appPort: 3000 + containerImage: ghcr.io/dapr/samples/hello-k8s-node:latest + createService: true + env: + APP_PORT: 3000 + - appID: pythonapp + appDirPath: ./pythonapp/ + containerImage: ghcr.io/dapr/samples/hello-k8s-python:latest +``` + +> **Note:** +> - If the `containerImage` field is not specified, `dapr run -f -k` produces an error. +> - The `createService` field defines a basic service in Kubernetes (ClusterIP or LoadBalancer) that targets the `--app-port` specified in the template. If `createService` isn't specified, the application is not accessible from outside the cluster. + +For a more in-depth example and explanation of the template properties, see [Multi-app template]({{< ref multi-app-template.md >}}). + +{{% /codetab %}} + +{{< /tabs >}} + + ## Locations for resources and configuration files You have options on where to place your applications' resources and configuration files when using Multi-App Run. diff --git a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md index 7ecff8d2eea..63a291e59cd 100644 --- a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md +++ b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md @@ -26,20 +26,53 @@ When you provide a directory path, the CLI will try to locate the Multi-App Run Execute the following CLI command to read the Multi-App Run template file, named `dapr.yaml` by default: +{{< tabs Self-hosted Kubernetes>}} + +{{% codetab %}} + + ```cmd # the template file needs to be called `dapr.yaml` by default if a directory path is given dapr run -f ``` +{{% /codetab %}} + +{{% codetab %}} + + +```cmd +dapr run -f -k +``` +{{% /codetab %}} + +{{< /tabs >}} ### Execute by providing a file path If the Multi-App Run template file is named something other than `dapr.yaml`, then you can provide the relative or absolute file path to the command: +{{< tabs Self-hosted Kubernetes>}} + +{{% codetab %}} + + ```cmd dapr run -f ./path/to/.yaml ``` +{{% /codetab %}} + +{{% codetab %}} + + +```cmd +dapr run -f -k ./path/to/.yaml +``` +{{% /codetab %}} + +{{< /tabs >}} + ## View the started applications Once the multi-app template is running, you can view the started applications with the following command: @@ -52,6 +85,11 @@ dapr list Stop the multi-app run template anytime with either of the following commands: +{{< tabs Self-hosted Kubernetes>}} + +{{% codetab %}} + + ```cmd # the template file needs to be called `dapr.yaml` by default if a directory path is given @@ -63,10 +101,36 @@ or: dapr stop -f ./path/to/.yaml ``` +{{% /codetab %}} + +{{% codetab %}} + + +```cmd +# the template file needs to be called `dapr.yaml` by default if a directory path is given + +dapr stop -f -k +``` +or: + +```cmd +dapr stop -f -k ./path/to/.yaml +``` + +{{% /codetab %}} + +{{< /tabs >}} + + ## Template file structure The Multi-App Run template file can include the following properties. Below is an example template showing two applications that are configured with some of the properties. +{{< tabs Self-hosted Kubernetes>}} + +{{% codetab %}} + + ```yaml version: 1 common: # optional section for variables shared across apps @@ -86,6 +150,41 @@ apps: command: ["python3", "app.py"] appLogDestination: file # (optional), can be file, console or fileAndConsole. default is fileAndConsole. daprdLogDestination: file # (optional), can be file, console or fileAndConsole. default is file. + - appID: backend # optional + appDirPath: .dapr/backend/ # REQUIRED + appProtocol: grpc + appPort: 3000 + unixDomainSocket: "/tmp/test-socket" + env: + - DEBUG: false + command: ["./backend"] +``` + +The following rules apply for all the paths present in the template file: + - If the path is absolute, it is used as is. + - All relative paths under comman section should be provided relative to the template file path. + - `appDirPath` under apps section should be provided relative to the template file path. + - All relative paths under app section should be provided relative to the appDirPath. + +{{% /codetab %}} + +{{% codetab %}} + + +```yaml +version: 1 +common: # optional section for variables shared across apps + env: # any environment variable shared across apps + DEBUG: true +apps: + - appID: webapp # optional + appDirPath: .dapr/webapp/ # REQUIRED + appChannelAddress: 127.0.0.1 # network address where the app listens on. (optional) can be left to default value by convention. + appProtocol: http + appPort: 8080 + appHealthCheckPath: "/healthz" + appLogDestination: file # (optional), can be file, console or fileAndConsole. default is fileAndConsole. + daprdLogDestination: file # (optional), can be file, console or fileAndConsole. default is file. containerImage: ghcr.io/dapr/samples/hello-k8s-node:latest # (optional) URI of the container image to be used when deploying to Kubernetes dev/test environment. createService: true # (optional) Create a Kubernetes service for the application when deploying to dev/test environment. - appID: backend # optional @@ -95,22 +194,28 @@ apps: unixDomainSocket: "/tmp/test-socket" env: - DEBUG: false - command: ["./backend"] ``` -{{% alert title="Important" color="warning" %}} The following rules apply for all the paths present in the template file: - If the path is absolute, it is used as is. - All relative paths under comman section should be provided relative to the template file path. - `appDirPath` under apps section should be provided relative to the template file path. - All relative paths under app section should be provided relative to the appDirPath. -{{% /alert %}} +{{% /codetab %}} + +{{< /tabs >}} ## Template properties +{{< tabs Self-hosted Kubernetes>}} + +{{% codetab %}} + + The properties for the Multi-App Run template align with the `dapr run` CLI flags, [listed in the CLI reference documentation]({{< ref "dapr-run.md#flags" >}}). +{{< table "table table-white table-striped table-bordered" >}} | Properties | Required | Details | Example | |--------------------------|:--------:|--------|---------| @@ -147,9 +252,59 @@ The properties for the Multi-App Run template align with the `dapr run` CLI flag | `env` | N | Map to environment variable; environment variables applied per application will overwrite environment variables shared across applications | `DEBUG`, `DAPR_HOST_ADD` | | `appLogDestination` | N | Log destination for outputting app logs; Its value can be file, console or fileAndConsole. Default is fileAndConsole | `file`, `console`, `fileAndConsole` | | `daprdLogDestination` | N | Log destination for outputting daprd logs; Its value can be file, console or fileAndConsole. Default is file | `file`, `console`, `fileAndConsole` | + +{{< /table >}} + + +{{% /codetab %}} + +{{% codetab %}} + + +The properties for the Multi-App Run template align with the `dapr run -k` CLI flags, [listed in the CLI reference documentation]({{< ref "dapr-run.md#flags" >}}). + +{{< table "table table-white table-striped table-bordered" >}} + +| Properties | Required | Details | Example | +|--------------------------|:--------:|--------|---------| +| `appDirPath` | Y | Path to the your application code | `./webapp/`, `./backend/` | +| `appID` | N | Application's app ID. If not provided, will be derived from `appDirPath` | `webapp`, `backend` | +| `appChannelAddress` | N | The network address the application listens on. Can be left to the default value by convention. | `127.0.0.1` | `localhost` | +| `appProtocol` | N | The protocol Dapr uses to talk to the application. | `http`, `grpc` | +| `appPort` | N | The port your application is listening on | `8080`, `3000` | +| `daprHTTPPort` | N | Dapr HTTP port | | +| `daprGRPCPort` | N | Dapr GRPC port | | +| `daprInternalGRPCPort` | N | gRPC port for the Dapr Internal API to listen on; used when parsing the value from a local DNS component | | +| `metricsPort` | N | The port that Dapr sends its metrics information to | | +| `unixDomainSocket` | N | Path to a unix domain socket dir mount. If specified, communication with the Dapr sidecar uses unix domain sockets for lower latency and greater throughput when compared to using TCP ports. Not available on Windows. | `/tmp/test-socket` | +| `profilePort` | N | The port for the profile server to listen on | | +| `enableProfiling` | N | Enable profiling via an HTTP endpoint | | +| `apiListenAddresses` | N | Dapr API listen addresses | | +| `logLevel` | N | The log verbosity. | | +| `appMaxConcurrency` | N | The concurrency level of the application; default is unlimited | | +| `placementHostAddress` | N | | | +| `appSSL` | N | Enable https when Dapr invokes the application | | +| `daprHTTPMaxRequestSize` | N | Max size of the request body in MB. | | +| `daprHTTPReadBufferSize` | N | Max size of the HTTP read buffer in KB. This also limits the maximum size of HTTP headers. The default 4 KB | | +| `enableAppHealthCheck` | N | Enable the app health check on the application | `true`, `false` | +| `appHealthCheckPath` | N | Path to the health check file | `/healthz` | +| `appHealthProbeInterval` | N | Interval to probe for the health of the app in seconds + | | +| `appHealthProbeTimeout` | N | Timeout for app health probes in milliseconds | | +| `appHealthThreshold` | N | Number of consecutive failures for the app to be considered unhealthy | | +| `enableApiLogging` | N | Enable the logging of all API calls from application to Dapr | | +| `env` | N | Map to environment variable; environment variables applied per application will overwrite environment variables shared across applications | `DEBUG`, `DAPR_HOST_ADD` | +| `appLogDestination` | N | Log destination for outputting app logs; Its value can be file, console or fileAndConsole. Default is fileAndConsole | `file`, `console`, `fileAndConsole` | +| `daprdLogDestination` | N | Log destination for outputting daprd logs; Its value can be file, console or fileAndConsole. Default is file | `file`, `console`, `fileAndConsole` | | `containerImage`| N | URI of the container image to be used when deploying to Kubernetes dev/test environment. | `ghcr.io/dapr/samples/hello-k8s-python:latest` | `createService`| N | Create a Kubernetes service for the application when deploying to dev/test environment. | `true`, `false` | +{{< /table >}} + +{{% /codetab %}} + +{{< /tabs >}} + ## Next steps Watch [this video for an overview on Multi-App Run](https://youtu.be/s1p9MNl4VGo?t=2456): diff --git a/daprdocs/layouts/shortcodes/table.html b/daprdocs/layouts/shortcodes/table.html new file mode 100644 index 00000000000..66e1df62edc --- /dev/null +++ b/daprdocs/layouts/shortcodes/table.html @@ -0,0 +1,6 @@ +{{ $htmlTable := .Inner | markdownify }} +{{ $class := .Get 0 | default "" }} +{{ $old := "" }} +{{ $new := printf "
" $class }} +{{ $htmlTable := replace $htmlTable $old $new }} +{{ $htmlTable | safeHTML }} From feec0af44fbdc59a0d9243a5f2a98a7d6ef2c362 Mon Sep 17 00:00:00 2001 From: Cassie Coyle Date: Mon, 11 Sep 2023 19:21:14 -0500 Subject: [PATCH 023/162] update zeebe components to stable (#3715) Signed-off-by: Cassandra Coyle --- daprdocs/data/components/bindings/zeebe.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daprdocs/data/components/bindings/zeebe.yaml b/daprdocs/data/components/bindings/zeebe.yaml index fb7d714269e..729c052cfce 100644 --- a/daprdocs/data/components/bindings/zeebe.yaml +++ b/daprdocs/data/components/bindings/zeebe.yaml @@ -1,6 +1,6 @@ - component: Zeebe Command link: zeebe-command - state: Alpha + state: Stable version: v1 since: "1.2" features: @@ -8,7 +8,7 @@ output: true - component: Zeebe Job Worker link: zeebe-jobworker - state: Alpha + state: Stable version: v1 since: "1.2" features: From 51a97d8000ff9f16cc4df0876736a0eea84f0293 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Tue, 12 Sep 2023 14:17:00 -0400 Subject: [PATCH 024/162] start adding content for java how to Signed-off-by: Hannah Hunter --- .../workflow/howto-author-workflow.md | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md index 3ca68fde59a..eaf16c8cf1a 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md @@ -122,33 +122,36 @@ public class ProcessPaymentActivity : WorkflowActivity -Define the workflow activities you'd like your workflow to perform. - -The activities called in the example below are: -- `need`: Receive notification of a new order. - -### [activity] - -```java -todo -``` - -[See the full `todo` workflow activity example.](todo) - -### [activity] +Define the workflow activities you'd like your workflow to perform. Activities are wrapped in an `ActivityWrapper` class for the `TaskActivityFactory`. In the following example, the activity wrapper and its constructor are defined: ```java -todo +public class ActivityWrapper implements TaskActivityFactory { + private final Constructor activityConstructor; + private final String name; + + public ActivityWrapper(Class clazz) { + this.name = clazz.getCanonicalName(); + try { + this.activityConstructor = clazz.getDeclaredConstructor(); + } catch (NoSuchMethodException e) { + throw new RuntimeException( + String.format("No constructor found for activity class '%s'.", this.name), e + ); + } + } ``` -[See the full `todo` workflow activity example.](todo) -### [todo] +Register a workflow activity object: ```java -todo + public void registerActivity(Class clazz) { + this.builder = this.builder.addActivity( + new ActivityWrapper<>(clazz) + ); + } ``` -[See the full `todo` workflow activity example.](todo) +[See a full Java SDK workflow activity example.](https://github.com/dapr/java-sdk/blob/master/sdk-workflows/src/main/java/io/dapr/workflows) {{% /codetab %}} From 23c45f52ca631725ddd8c7d5ecf0ceb39b4636c4 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Tue, 12 Sep 2023 15:29:19 -0400 Subject: [PATCH 025/162] mark review and add demo video Signed-off-by: Hannah Hunter --- .../multi-app-dapr-run/multi-app-overview.md | 2 +- .../multi-app-dapr-run/multi-app-template.md | 21 ++++++++++++------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md index 78fd3c27c74..2322aa341cf 100644 --- a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md +++ b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md @@ -59,7 +59,7 @@ With Multi-App Run, you can start multiple applications in a Kubernetes developm ## Multi-App Run template file -When you execute `dapr run -f .`, Dapr generates the multi-app template file (named `dapr.yaml`) in the `.dapr/deploy` directory within each application. Each time you run `dapr run -f -k .`, the generated Kubernetes Deployment YAML file is overwritten. run all the applications. +When you execute `dapr run -f .`, Dapr generates the multi-app template file (named `dapr.yaml`) in the `.dapr/deploy` directory within each application. Each time you run `dapr run -f -k .`, the generated Kubernetes Deployment YAML file is overwritten. You can name template file with preferred name other than the default. For example `dapr run -f -k ./.yaml`. diff --git a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md index 63a291e59cd..f8f198702d6 100644 --- a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md +++ b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md @@ -162,9 +162,9 @@ apps: The following rules apply for all the paths present in the template file: - If the path is absolute, it is used as is. - - All relative paths under comman section should be provided relative to the template file path. + - All relative paths under command section should be provided relative to the template file path. - `appDirPath` under apps section should be provided relative to the template file path. - - All relative paths under app section should be provided relative to the appDirPath. + - All relative paths under app section should be provided relative to the `appDirPath`. {{% /codetab %}} @@ -198,9 +198,8 @@ apps: The following rules apply for all the paths present in the template file: - If the path is absolute, it is used as is. - - All relative paths under comman section should be provided relative to the template file path. - `appDirPath` under apps section should be provided relative to the template file path. - - All relative paths under app section should be provided relative to the appDirPath. + - All relative paths under app section should be provided relative to the `appDirPath`. {{% /codetab %}} @@ -255,7 +254,11 @@ The properties for the Multi-App Run template align with the `dapr run` CLI flag {{< /table >}} +## Next steps + +Watch [this video for an overview on Multi-App Run](https://youtu.be/s1p9MNl4VGo?t=2456): + {{% /codetab %}} {{% codetab %}} @@ -301,12 +304,14 @@ The properties for the Multi-App Run template align with the `dapr run -k` CLI f {{< /table >}} +## Next steps + +Watch [this video for an overview on Multi-App Run in Kubernetes](https://youtu.be/nWatANwaAik?si=O8XR-TUaiY0gclgO&t=1024): + + + {{% /codetab %}} {{< /tabs >}} -## Next steps - -Watch [this video for an overview on Multi-App Run](https://youtu.be/s1p9MNl4VGo?t=2456): - From d030a7bf4d1d9da521a76a0e9d97e7adcb49ced8 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Tue, 12 Sep 2023 17:25:32 -0400 Subject: [PATCH 026/162] continue adding Java Signed-off-by: Hannah Hunter --- .../workflow/howto-author-workflow.md | 168 +++++++++++++++--- 1 file changed, 144 insertions(+), 24 deletions(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md index eaf16c8cf1a..7c90990faee 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md @@ -122,36 +122,27 @@ public class ProcessPaymentActivity : WorkflowActivity -Define the workflow activities you'd like your workflow to perform. Activities are wrapped in an `ActivityWrapper` class for the `TaskActivityFactory`. In the following example, the activity wrapper and its constructor are defined: +Define the workflow activities you'd like your workflow to perform. Activities are wrapped in an wrapper class, which is called `OrchestrationWrapper` in the following exmaple. ```java -public class ActivityWrapper implements TaskActivityFactory { - private final Constructor activityConstructor; +class OrchestratorWrapper implements TaskOrchestrationFactory { + private final Constructor workflowConstructor; private final String name; - public ActivityWrapper(Class clazz) { + public OrchestratorWrapper(Class clazz) { this.name = clazz.getCanonicalName(); try { - this.activityConstructor = clazz.getDeclaredConstructor(); + this.workflowConstructor = clazz.getDeclaredConstructor(); } catch (NoSuchMethodException e) { throw new RuntimeException( - String.format("No constructor found for activity class '%s'.", this.name), e + String.format("No constructor found for workflow class '%s'.", this.name), e ); } } +} ``` -Register a workflow activity object: - -```java - public void registerActivity(Class clazz) { - this.builder = this.builder.addActivity( - new ActivityWrapper<>(clazz) - ); - } -``` - -[See a full Java SDK workflow activity example.](https://github.com/dapr/java-sdk/blob/master/sdk-workflows/src/main/java/io/dapr/workflows) +[See a full Java SDK workflow activity example.](https://github.com/dapr/java-sdk/blob/master/sdk-workflows/src/main/java/io/dapr/workflows/runtime/OrchestratorWrapper.java) {{% /codetab %}} @@ -232,13 +223,19 @@ The `OrderProcessingWorkflow` class is derived from a base class called `Workflo -Intro - +The `WorkflowActivity` interface executes the activity logic and returns a value to be serialized and returned to the caller. The following code registers a workflow activity object: + ```java -todo +public WorkflowRuntimeBuilder registerWorkflow(Class clazz) { + this.builder = this.builder.addOrchestration( + new OrchestratorWrapper<>(clazz) + ); + + return this; +} ``` -[See the `todo` workflow in context.](todo) +[See the Java SDK workflow in context.](https://github.com/dapr/java-sdk/blob/master/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntimeBuilder.java) {{% /codetab %}} @@ -415,12 +412,135 @@ app.Run(); -[In the following example](todo), for a basic Java hello world application using the Java SDK, your project code would include: +[In the following example](https://github.com/dapr/java-sdk/blob/master/sdk-workflows/src/main/java/io/dapr/workflows/client/DaprWorkflowClient.java), for a basic Java application using workflows for Java SDK, your project code would include: -- A Java package called `todo` to receive the Java SDK capabilities. +- A Java package called `io.dapr.workflows.client` to receive the Java SDK client capabilities. +- An import of `io.dapr.workflows.Workflow` +- A wrapper extending `Workflow` and implementing tasks/activities +- A declared `WorkflowRuntimeBuilder` builder +- API calls. In the example below, these calls start and terminate the workflow. ```java -todo +package io.dapr.workflows.client; + +import com.microsoft.durabletask.DurableTaskClient; +import com.microsoft.durabletask.DurableTaskGrpcClientBuilder; +import io.dapr.utils.NetworkUtils; +import io.dapr.workflows.Workflow; +import io.grpc.ManagedChannel; + +import javax.annotation.Nullable; +import java.util.concurrent.TimeUnit; + +public class DaprWorkflowClient implements AutoCloseable { + + private DurableTaskClient innerClient; + private ManagedChannel grpcChannel; + + /** + * Public constructor for DaprWorkflowClient. This layer constructs the GRPC Channel. + */ + public DaprWorkflowClient() { + this(NetworkUtils.buildGrpcManagedChannel()); + } + + /** + * Private Constructor that passes a created DurableTaskClient and the new GRPC channel. + * + * @param grpcChannel ManagedChannel for GRPC channel. + */ + private DaprWorkflowClient(ManagedChannel grpcChannel) { + this(createDurableTaskClient(grpcChannel), grpcChannel); + } + + /** + * Private Constructor for DaprWorkflowClient. + * + * @param innerClient DurableTaskGrpcClient with GRPC Channel set up. + * @param grpcChannel ManagedChannel for instance variable setting. + * + */ + private DaprWorkflowClient(DurableTaskClient innerClient, ManagedChannel grpcChannel) { + this.innerClient = innerClient; + this.grpcChannel = grpcChannel; + } + + /** + * Static method to create the DurableTaskClient. + * + * @param grpcChannel ManagedChannel for GRPC. + * @return a new instance of a DurableTaskClient with a GRPC channel. + */ + private static DurableTaskClient createDurableTaskClient(ManagedChannel grpcChannel) { + return new DurableTaskGrpcClientBuilder() + .grpcChannel(grpcChannel) + .build(); + } + + /** + * Schedules a new workflow using DurableTask client. + * + * @param any Workflow type + * @param clazz Class extending Workflow to start an instance of. + * @return A String with the randomly-generated instance ID for new Workflow instance. + */ + public String scheduleNewWorkflow(Class clazz) { + return this.innerClient.scheduleNewOrchestrationInstance(clazz.getCanonicalName()); + } + + /** + * Schedules a new workflow using DurableTask client. + * + * @param any Workflow type + * @param clazz Class extending Workflow to start an instance of. + * @param input the input to pass to the scheduled orchestration instance. Must be serializable. + * @return A String with the randomly-generated instance ID for new Workflow instance. + */ + public String scheduleNewWorkflow(Class clazz, Object input) { + return this.innerClient.scheduleNewOrchestrationInstance(clazz.getCanonicalName(), input); + } + + /** + * Schedules a new workflow using DurableTask client. + * + * @param any Workflow type + * @param clazz Class extending Workflow to start an instance of. + * @param input the input to pass to the scheduled orchestration instance. Must be serializable. + * @param instanceId the unique ID of the orchestration instance to schedule + * @return A String with the instanceId parameter value. + */ + public String scheduleNewWorkflow(Class clazz, Object input, String instanceId) { + return this.innerClient.scheduleNewOrchestrationInstance(clazz.getCanonicalName(), input, instanceId); + } + + /** + * Terminates the workflow associated with the provided instance id. + * + * @param workflowInstanceId Workflow instance id to terminate. + * @param output the optional output to set for the terminated orchestration instance. + */ + public void terminateWorkflow(String workflowInstanceId, @Nullable Object output) { + this.innerClient.terminate(workflowInstanceId, output); + } + + /** + * Closes the inner DurableTask client and shutdown the GRPC channel. + * + */ + public void close() throws InterruptedException { + try { + if (this.innerClient != null) { + this.innerClient.close(); + this.innerClient = null; + } + } finally { + if (this.grpcChannel != null && !this.grpcChannel.isShutdown()) { + this.grpcChannel.shutdown().awaitTermination(5, TimeUnit.SECONDS); + this.grpcChannel = null; + } + } + } +} ``` {{% /codetab %}} From 448d0de43e603948aaea8eaa58c4a4cab0fdfa47 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Tue, 12 Sep 2023 18:03:42 -0400 Subject: [PATCH 027/162] add activities Signed-off-by: Hannah Hunter --- .../building-blocks/workflow/howto-manage-workflow.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md index 27d5d75cb05..0bc19430295 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md @@ -100,6 +100,14 @@ await daprClient.PurgeWorkflowAsync(orderId, workflowComponent); Manage your workflow within your code. In the workflow example from the [Author a workflow]({{< ref "howto-author-workflow.md#write-the-application" >}}) guide, the workflow is registered in the code using the following APIs: +- **raiseEvent**: Raise an event on a workflow +- **getInstanceMetadata**: Get information on the status of the workflow +- **waitForInstanceStart**: Pauses or suspends a workflow instance that can later be resumed +- **waitForInstanceCompletion**: Resumes a paused workflow instance and waits for completion +- **createTaskHub** +- **deleteTaskHub** +- **purgeInstance**: Removes all metadata related to a specific workflow + ```java todo ``` From 2ac6bab1679609c9093e3c1f070319936ff107f5 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 13 Sep 2023 13:43:49 -0400 Subject: [PATCH 028/162] additions per mukundan Signed-off-by: Hannah Hunter --- .../multi-app-dapr-run/multi-app-overview.md | 72 +++++++++++-------- .../content/en/reference/cli/dapr-init.md | 3 + 2 files changed, 45 insertions(+), 30 deletions(-) diff --git a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md index 2322aa341cf..92aa28d1ab5 100644 --- a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md +++ b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md @@ -10,22 +10,21 @@ description: Run multiple applications with one CLI command Multi-App Run is currently a preview feature only supported in Linux/MacOS. {{% /alert %}} -Let's say you want to run several applications locally to test them together, similar to a production scenario. With a local Kubernetes cluster, you'd be able to do this with helm/deployment YAML files. You'd also have to build them as containers and set up Kubernetes, which can add some complexity. - -Instead, you simply want to run them as local executables in self-hosted mode. However, self-hosted mode requires you to: +Let's say you want to run several applications locally to test them together, similar to a production scenario. Until Multi-App Run, you'd have to: - Run multiple `dapr run` commands - Keep track of all ports opened (you cannot have duplicate ports for different applications). - Remember the resources folders and configuration files that each application refers to. - Recall all of the additional flags you used to tweak the `dapr run` command behavior (`--app-health-check-path`, `--dapr-grpc-port`, `--unix-domain-socket`, etc.) +With Multi-App Run, you can start multiple applications in either self-hosted or Kubernetes mode using a template file and running a single command. The template file describes how to start multiple applications as if you had run many separate CLI `run`commands. By default, this template file is called `dapr.yaml`. + +## Multi-App Run template file + {{< tabs Self-hosted Kubernetes>}} {{% codetab %}} -With Multi-App Run, you can start multiple applications in self-hosted mode using a single `dapr run -f` command using a template file. The template file describes how to start multiple applications as if you had run many separate CLI `run`commands. By default, this template file is called `dapr.yaml`. - -## Multi-App Run template file When you execute `dapr run -f .`, it generates the multi-app template file (named `dapr.yaml`) present in the current directory to run all the applications. @@ -51,13 +50,28 @@ apps: For a more in-depth example and explanation of the template properties, see [Multi-app template]({{< ref multi-app-template.md >}}). +## Locations for resources and configuration files + +You have options on where to place your applications' resources and configuration files when using Multi-App Run. + +### Point to one file location (with convention) + +You can set all of your applications resources and configurations at the `~/.dapr` root. This is helpful when all applications share the same resources path, like when testing on a local machine. + +### Separate file locations for each application (with convention) + +When using Multi-App Run, each application directory can have a `.dapr` folder, which contains a `config.yaml` file and a `resources` directory. Otherwise, if the `.dapr` directory is not present within the app directory, the default `~/.dapr/resources/` and `~/.dapr/config.yaml` locations are used. + +If you decide to add a `.dapr` directory in each application directory, with a `/resources` directory and `config.yaml` file, you can specify different resources paths for each application. This approach remains within convention by using the default `~/.dapr`. + +### Point to separate locations (custom) + +You can also name each app directory's `.dapr` directory something other than `.dapr`, such as, `webapp`, or `backend`. This helps if you'd like to be explicit about resource or application directory paths. + {{% /codetab %}} {{% codetab %}} -With Multi-App Run, you can start multiple applications in a Kubernetes development or test environment using a single `dapr run -f -k` command using a template file. The template file describes how to start multiple applications as if you had run many separate CLI `run`commands. By default, this template file is called `dapr.yaml`. - -## Multi-App Run template file When you execute `dapr run -f .`, Dapr generates the multi-app template file (named `dapr.yaml`) in the `.dapr/deploy` directory within each application. Each time you run `dapr run -f -k .`, the generated Kubernetes Deployment YAML file is overwritten. @@ -91,25 +105,6 @@ For a more in-depth example and explanation of the template properties, see [Mul {{< /tabs >}} - -## Locations for resources and configuration files - -You have options on where to place your applications' resources and configuration files when using Multi-App Run. - -### Point to one file location (with convention) - -You can set all of your applications resources and configurations at the `~/.dapr` root. This is helpful when all applications share the same resources path, like when testing on a local machine. - -### Separate file locations for each application (with convention) - -When using Multi-App Run, each application directory can have a `.dapr` folder, which contains a `config.yaml` file and a `resources` directory. Otherwise, if the `.dapr` directory is not present within the app directory, the default `~/.dapr/resources/` and `~/.dapr/config.yaml` locations are used. - -If you decide to add a `.dapr` directory in each application directory, with a `/resources` directory and `config.yaml` file, you can specify different resources paths for each application. This approach remains within convention by using the default `~/.dapr`. - -### Point to separate locations (custom) - -You can also name each app directory's `.dapr` directory something other than `.dapr`, such as, `webapp`, or `backend`. This helps if you'd like to be explicit about resource or application directory paths. - ## Logs The run template provides two log destination fields for each application and its associated daprd process: @@ -118,7 +113,7 @@ The run template provides two log destination fields for each application and it 2. `daprdLogDestination` : This field configures the log destination for the `daprd` process. The possible values are `console`, `file` and `fileAndConsole`. The default value is `file` where the `daprd` logs are written to a file by default. -#### Log file format +### Log file format Logs for application and `daprd` are captured in separate files. These log files are created automatically under `.dapr/logs` directory under each application directory (`appDirPath` in the template). These log file names follow the pattern seen below: @@ -127,13 +122,30 @@ Logs for application and `daprd` are captured in separate files. These log files Even if you've decided to rename your resources folder to something other than `.dapr`, the log files are written only to the `.dapr/logs` folder (created in the application directory). - ## Watch the demo +{{< tabs Self-hosted Kubernetes>}} + +{{% codetab %}} + + Watch [this video for an overview on Multi-App Run](https://youtu.be/s1p9MNl4VGo?t=2456): +{{% /codetab %}} + +{{% codetab %}} + + +Watch [this video for an overview on Multi-App Run in Kubernetes](https://youtu.be/nWatANwaAik?si=O8XR-TUaiY0gclgO&t=1024): + + + +{{% /codetab %}} + +{{< /tabs >}} + ## Next steps - [Learn the Multi-App Run template file structure and its properties]({{< ref multi-app-template.md >}}) diff --git a/daprdocs/content/en/reference/cli/dapr-init.md b/daprdocs/content/en/reference/cli/dapr-init.md index a94c3e19a40..bdb93bf5d15 100644 --- a/daprdocs/content/en/reference/cli/dapr-init.md +++ b/daprdocs/content/en/reference/cli/dapr-init.md @@ -44,6 +44,9 @@ dapr init [flags] | N/A | DAPR_HELM_REPO_USERNAME | A username for a private Helm chart | The username required to access the private Dapr Helm chart. If it can be accessed publicly, this env variable does not need to be set| | N/A | DAPR_HELM_REPO_PASSWORD | A password for a private Helm chart |The password required to access the private Dapr Helm chart. If it can be accessed publicly, this env variable does not need to be set| | | `--container-runtime` | | `docker` | Used to pass in a different container runtime other than Docker. Supported container runtimes are: `docker`, `podman` | +| `--dev` | | | Creates Redis and Zipkin deployments when run in Kubernetes. | + + ### Examples #### Self hosted environment From eb1b1d0c354a6d36e36bb78cf1668de53165f48b Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 13 Sep 2023 13:53:14 -0400 Subject: [PATCH 029/162] formatting updates Signed-off-by: Hannah Hunter --- .../multi-app-dapr-run/multi-app-overview.md | 49 +++++++++++-------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md index 92aa28d1ab5..164ffe982e5 100644 --- a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md +++ b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md @@ -19,13 +19,13 @@ Let's say you want to run several applications locally to test them together, si With Multi-App Run, you can start multiple applications in either self-hosted or Kubernetes mode using a template file and running a single command. The template file describes how to start multiple applications as if you had run many separate CLI `run`commands. By default, this template file is called `dapr.yaml`. -## Multi-App Run template file - {{< tabs Self-hosted Kubernetes>}} {{% codetab %}} +## Multi-App Run template file + When you execute `dapr run -f .`, it generates the multi-app template file (named `dapr.yaml`) present in the current directory to run all the applications. You can name template file with preferred name other than the default. For example `dapr run -f ./.yaml`. @@ -68,12 +68,37 @@ If you decide to add a `.dapr` directory in each application directory, with a ` You can also name each app directory's `.dapr` directory something other than `.dapr`, such as, `webapp`, or `backend`. This helps if you'd like to be explicit about resource or application directory paths. +## Logs + +The run template provides two log destination fields for each application and its associated daprd process: + +1. `appLogDestination` : This field configures the log destination for the application. The possible values are `console`, `file` and `fileAndConsole`. The default value is `fileAndConsole` where application logs are written to both console and to a file by default. + +1. `daprdLogDestination` : This field configures the log destination for the `daprd` process. The possible values are `console`, `file` and `fileAndConsole`. The default value is `file` where the `daprd` logs are written to a file by default. + +### Log file format + +Logs for application and `daprd` are captured in separate files. These log files are created automatically under `.dapr/logs` directory under each application directory (`appDirPath` in the template). These log file names follow the pattern seen below: + +- `_app_.log` (file name format for `app` log) +- `_daprd_.log` (file name format for `daprd` log) + +Even if you've decided to rename your resources folder to something other than `.dapr`, the log files are written only to the `.dapr/logs` folder (created in the application directory). + +## Watch the demo + +Watch [this video for an overview on Multi-App Run](https://youtu.be/s1p9MNl4VGo?t=2456): + + + {{% /codetab %}} {{% codetab %}} -When you execute `dapr run -f .`, Dapr generates the multi-app template file (named `dapr.yaml`) in the `.dapr/deploy` directory within each application. Each time you run `dapr run -f -k .`, the generated Kubernetes Deployment YAML file is overwritten. +## Multi-App Run template file + +When you execute `dapr run -f -k .`, Dapr generates the multi-app template file (named `dapr.yaml`) in the `.dapr/deploy` directory within each application. Each time you run `dapr run -f -k .`, the generated Kubernetes Deployment YAML file is overwritten. You can name template file with preferred name other than the default. For example `dapr run -f -k ./.yaml`. @@ -101,10 +126,6 @@ apps: For a more in-depth example and explanation of the template properties, see [Multi-app template]({{< ref multi-app-template.md >}}). -{{% /codetab %}} - -{{< /tabs >}} - ## Logs The run template provides two log destination fields for each application and its associated daprd process: @@ -124,20 +145,6 @@ Even if you've decided to rename your resources folder to something other than ` ## Watch the demo -{{< tabs Self-hosted Kubernetes>}} - -{{% codetab %}} - - -Watch [this video for an overview on Multi-App Run](https://youtu.be/s1p9MNl4VGo?t=2456): - - - -{{% /codetab %}} - -{{% codetab %}} - - Watch [this video for an overview on Multi-App Run in Kubernetes](https://youtu.be/nWatANwaAik?si=O8XR-TUaiY0gclgO&t=1024): From 7a1b51b7a3252f270b3928d4977e34444c308b39 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 13 Sep 2023 17:43:02 -0400 Subject: [PATCH 030/162] add ref Signed-off-by: Hannah Hunter --- .../building-blocks/workflow/howto-author-workflow.md | 4 ++++ .../building-blocks/workflow/howto-manage-workflow.md | 4 ++++ .../building-blocks/workflow/workflow-architecture.md | 4 ++++ .../workflow/workflow-features-concepts.md | 4 ++++ .../building-blocks/workflow/workflow-overview.md | 8 +++++++- .../en/getting-started/quickstarts/workflow-quickstart.md | 2 +- daprdocs/content/en/reference/api/workflow_api.md | 4 ++++ 7 files changed, 28 insertions(+), 2 deletions(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md index 7c90990faee..b1a44ed2589 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md @@ -6,6 +6,10 @@ weight: 5000 description: "Learn how to develop and author workflows" --- +{{% alert title="Note" color="primary" %}} +Dapr Workflow is currently in beta. [See known limitations for {{% dapr-latest-version cli="true" %}}]({{< ref "workflow-overview.md#limitations" >}}). +{{% /alert %}} + This article provides a high-level overview of how to author workflows that are executed by the Dapr Workflow engine. {{% alert title="Note" color="primary" %}} diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md index 0bc19430295..1b242f281dd 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md @@ -6,6 +6,10 @@ weight: 6000 description: Manage and run workflows --- +{{% alert title="Note" color="primary" %}} +Dapr Workflow is currently in beta. [See known limitations for {{% dapr-latest-version cli="true" %}}]({{< ref "workflow-overview.md#limitations" >}}). +{{% /alert %}} + Now that you've [authored the workflow and its activities in your application]({{< ref howto-author-workflow.md >}}), you can start, terminate, and get information about the workflow using HTTP API calls. For more information, read the [workflow API reference]({{< ref workflow_api.md >}}). {{< tabs Python ".NET" Java HTTP >}} diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-architecture.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-architecture.md index 9835725c434..1322ea94f5f 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-architecture.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-architecture.md @@ -6,6 +6,10 @@ weight: 4000 description: "The Dapr Workflow engine architecture" --- +{{% alert title="Note" color="primary" %}} +Dapr Workflow is currently in beta. [See known limitations for {{% dapr-latest-version cli="true" %}}]({{< ref "workflow-overview.md#limitations" >}}). +{{% /alert %}} + [Dapr Workflows]({{< ref "workflow-overview.md" >}}) allow developers to define workflows using ordinary code in a variety of programming languages. The workflow engine runs inside of the Dapr sidecar and orchestrates workflow code deployed as part of your application. This article describes: - The architecture of the Dapr Workflow engine diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-features-concepts.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-features-concepts.md index c8e3de13187..e957ade3d56 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-features-concepts.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-features-concepts.md @@ -6,6 +6,10 @@ weight: 2000 description: "Learn more about the Dapr Workflow features and concepts" --- +{{% alert title="Note" color="primary" %}} +Dapr Workflow is currently in beta. [See known limitations for {{% dapr-latest-version cli="true" %}}]({{< ref "workflow-overview.md#limitations" >}}). +{{% /alert %}} + Now that you've learned about the [workflow building block]({{< ref workflow-overview.md >}}) at a high level, let's deep dive into the features and concepts included with the Dapr Workflow engine and SDKs. Dapr Workflow exposes several core features and concepts which are common across all supported languages. {{% alert title="Note" color="primary" %}} diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md index 998e0cccc3a..cf7326497f1 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md @@ -7,7 +7,7 @@ description: "Overview of Dapr Workflow" --- {{% alert title="Note" color="primary" %}} -Dapr Workflow is currently in alpha. +Dapr Workflow is currently in beta. [See known limitations for {{% dapr-latest-version cli="true" %}}]({{< ref "#limitations" >}}). {{% /alert %}} Dapr workflow makes it easy for developers to write business logic and integrations in a reliable way. Since Dapr workflows are stateful, they support long-running and fault-tolerant applications, ideal for orchestrating microservices. Dapr workflow works seamlessly with other Dapr building blocks, such as service invocation, pub/sub, state management, and bindings. @@ -105,6 +105,12 @@ Want to put workflows to the test? Walk through the following quickstart and tut Want to skip the quickstarts? Not a problem. You can try out the workflow building block directly in your application. After [Dapr is installed]({{< ref install-dapr-cli.md >}}), you can begin using workflows, starting with [how to author a workflow]({{< ref howto-author-workflow.md >}}). +## Limitations + +With Dapr Workflow in beta stage comes the following limitation(s): + +- **State stores:** For the {{% dapr-latest-version cli="true" %}} beta release of Dapr Workflow, you're not able to use NoSQL databases. Only SQL databases are supported. NoSQL database support is planned for future minor releases. + ## Watch the demo Watch [this video for an overview on Dapr Workflow](https://youtu.be/s1p9MNl4VGo?t=131): diff --git a/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md index 69d94afea3f..fba93d75d3f 100644 --- a/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md @@ -7,7 +7,7 @@ description: Get started with the Dapr Workflow building block --- {{% alert title="Note" color="primary" %}} -The workflow building block is currently in **alpha**. +Dapr Workflow is currently in beta. [See known limitations for {{% dapr-latest-version cli="true" %}}]({{< ref "workflow-overview.md#limitations" >}}). {{% /alert %}} Let's take a look at the Dapr [Workflow building block]({{< ref workflow >}}). In this Quickstart, you'll create a simple console application to demonstrate Dapr's workflow programming model and the workflow management APIs. diff --git a/daprdocs/content/en/reference/api/workflow_api.md b/daprdocs/content/en/reference/api/workflow_api.md index 80814852e0d..7b74de78a31 100644 --- a/daprdocs/content/en/reference/api/workflow_api.md +++ b/daprdocs/content/en/reference/api/workflow_api.md @@ -6,6 +6,10 @@ description: "Detailed documentation on the workflow API" weight: 900 --- +{{% alert title="Note" color="primary" %}} +Dapr Workflow is currently in beta. [See known limitations for {{% dapr-latest-version cli="true" %}}]({{< ref "workflow-overview.md#limitations" >}}). +{{% /alert %}} + Dapr provides users with the ability to interact with workflows and comes with a built-in `dapr` component. ## Start workflow request From 0887a3405198d870013bde7422fe1df97bef837c Mon Sep 17 00:00:00 2001 From: Christian Kaps <307006+akkie@users.noreply.github.com> Date: Thu, 14 Sep 2023 00:55:43 +0200 Subject: [PATCH 031/162] Update Zeebe documentation (#3712) * Update Zeebe documentation Signed-off-by: Christian Kaps * Add deprecated alias deploy-resource Signed-off-by: Christian Kaps --------- Signed-off-by: Christian Kaps Signed-off-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> --- .../supported-bindings/zeebe-command.md | 227 ++++++++++++++---- .../supported-bindings/zeebe-jobworker.md | 4 + 2 files changed, 180 insertions(+), 51 deletions(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/zeebe-command.md b/daprdocs/content/en/reference/components-reference/supported-bindings/zeebe-command.md index 4db06840155..6ea57ee1dff 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/zeebe-command.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/zeebe-command.md @@ -48,6 +48,7 @@ This component supports **output binding** with the following operations: - `topology` - `deploy-process` +- `deploy-resource` - `create-instance` - `cancel-instance` - `set-variables` @@ -123,9 +124,13 @@ The response values are: #### deploy-process -The `deploy-process` operation deploys a single process to Zeebe. +Deprecated alias of 'deploy-resource'. -To perform a `deploy-process` operation, invoke the Zeebe command binding with a `POST` method, and the following JSON body: +#### deploy-resource + +The `deploy-resource` operation deploys a single resource to Zeebe. A resource can be a process (BPMN) or a decision and a decision requirement (DMN). + +To perform a `deploy-resource` operation, invoke the Zeebe command binding with a `POST` method, and the following JSON body: ```json { @@ -133,41 +138,105 @@ To perform a `deploy-process` operation, invoke the Zeebe command binding with a "metadata": { "fileName": "products-process.bpmn" }, - "operation": "deploy-process" + "operation": "deploy-resource" } ``` The metadata parameters are: -- `fileName` - the name of the process file +- `fileName` - the name of the resource file ##### Response The binding returns a JSON with the following response: +{{< tabs "BPMN" "DMN" >}} + +{{% codetab %}} + ```json { - "key": 2251799813687320, - "processes": [ + "key": 2251799813685252, + "deployments": [ { - "bpmnProcessId": "products-process", - "version": 3, - "processDefinitionKey": 2251799813685895, - "resourceName": "products-process.bpmn" + "Metadata": { + "Process": { + "bpmnProcessId": "products-process", + "version": 2, + "processDefinitionKey": 2251799813685251, + "resourceName": "products-process.bpmn" + } + } } ] } ``` +{{% /codetab %}} + +{{% codetab %}} + +```json +{ + "key": 2251799813685253, + "deployments": [ + { + "Metadata": { + "Decision": { + "dmnDecisionId": "products-approval", + "dmnDecisionName": "Products approval", + "version": 1, + "decisionKey": 2251799813685252, + "dmnDecisionRequirementsId": "Definitions_0c98xne", + "decisionRequirementsKey": 2251799813685251 + } + } + }, + { + "Metadata": { + "DecisionRequirements": { + "dmnDecisionRequirementsId": "Definitions_0c98xne", + "dmnDecisionRequirementsName": "DRD", + "version": 1, + "decisionRequirementsKey": 2251799813685251, + "resourceName": "products-approval.dmn" + } + } + } + ] +} +``` + +{{% /codetab %}} + +{{< /tabs >}} + The response values are: - `key` - the unique key identifying the deployment -- `processes` - a list of deployed processes - - `bpmnProcessId` - the bpmn process ID, as parsed during deployment; together with the version forms a unique identifier for a specific - process definition - - `version` - the assigned process version - - `processDefinitionKey` - the assigned key, which acts as a unique identifier for this process - - `resourceName` - the resource name from which this process was parsed +- `deployments` - a list of deployed resources, e.g. processes + - `metadata` - deployment metadata, each deployment has only one metadata + - `process`- metadata of a deployed process + - `bpmnProcessId` - the bpmn process ID, as parsed during deployment; together with the version forms a unique identifier for a specific + process definition + - `version` - the assigned process version + - `processDefinitionKey` - the assigned key, which acts as a unique identifier for this process + - `resourceName` - the resource name from which this process was parsed + - `decision` - metadata of a deployed decision + - `dmnDecisionId` - the dmn decision ID, as parsed during deployment; together with the versions forms a unique identifier for a specific + decision + - `dmnDecisionName` - the dmn name of the decision, as parsed during deployment + - `version` - the assigned decision version + - `decisionKey` - the assigned decision key, which acts as a unique identifier for this decision + - `dmnDecisionRequirementsId` - the dmn ID of the decision requirements graph that this decision is part of, as parsed during deployment + - `decisionRequirementsKey` - the assigned key of the decision requirements graph that this decision is part of + - `decisionRequirements` - metadata of a deployed decision requirements + - `dmnDecisionRequirementsId` - the dmn decision requirements ID, as parsed during deployment; together with the versions forms a unique + identifier for a specific decision + - `dmnDecisionRequirementsName` - the dmn name of the decision requirements, as parsed during deployment + - `version` - the assigned decision requirements version + - `decisionRequirementsKey` - the assigned decision requirements key, which acts as a unique identifier for this decision requirements + - `resourceName` - the resource name from which this decision requirements was parsed #### create-instance @@ -176,10 +245,19 @@ specified either using its unique key (as returned by the `deploy-process` opera Note that only processes with none start events can be started through this command. -##### By BPMN process ID +Typically, process creation and execution are decoupled. This means that the command creates a new process instance and immediately responds with +the process instance id. The execution of the process occurs after the response is sent. However, there are use cases that need to collect the results +of a process when its execution is complete. By defining the `withResult` property, the command allows to "synchronously" execute processes and receive +the results via a set of variables. The response is sent when the process execution is complete. + +For more information please visit the [official documentation](https://docs.camunda.io/docs/components/concepts/process-instance-creation/). To perform a `create-instance` operation, invoke the Zeebe command binding with a `POST` method, and the following JSON body: +{{< tabs "By BPMN process ID" "By process definition key" "Synchronous execution" >}} + +{{% codetab %}} + ```json { "data": { @@ -194,19 +272,9 @@ To perform a `create-instance` operation, invoke the Zeebe command binding with } ``` -The data parameters are: - -- `bpmnProcessId` - the BPMN process ID of the process definition to instantiate -- `version` - (optional, default: latest version) the version of the process to instantiate -- `variables` - (optional) JSON document that will instantiate the variables for the root variable scope of the - process instance; it must be a JSON object, as variables will be mapped in a - key-value fashion. e.g. { "a": 1, "b": 2 } will create two variables, named "a" and - "b" respectively, with their associated values. [{ "a": 1, "b": 2 }] would not be a - valid argument, as the root of the JSON document is an array and not an object +{{% /codetab %}} -##### By process definition key - -To perform a `create-instance` operation, invoke the Zeebe command binding with a `POST` method, and the following JSON body: +{{% codetab %}} ```json { @@ -222,14 +290,46 @@ To perform a `create-instance` operation, invoke the Zeebe command binding with } ``` +{{% /codetab %}} + +{{% codetab %}} + +```json +{ + "data": { + "bpmnProcessId": "products-process", + "variables": { + "productId": "some-product-id", + "productName": "some-product-name", + "productKey": "some-product-key" + }, + "withResult": true, + "requestTimeout": "30s", + "fetchVariables": ["productId"] + }, + "operation": "create-instance" +} +``` + +{{% /codetab %}} + +{{< /tabs >}} + The data parameters are: +- `bpmnProcessId` - the BPMN process ID of the process definition to instantiate - `processDefinitionKey` - the unique key identifying the process definition to instantiate +- `version` - (optional, default: latest version) the version of the process to instantiate - `variables` - (optional) JSON document that will instantiate the variables for the root variable scope of the process instance; it must be a JSON object, as variables will be mapped in a key-value fashion. e.g. { "a": 1, "b": 2 } will create two variables, named "a" and "b" respectively, with their associated values. [{ "a": 1, "b": 2 }] would not be a valid argument, as the root of the JSON document is an array and not an object +- `withResult` - (optional, default: false) if set to true, the process will be instantiated and executed synchronously +- `requestTimeout` - (optional, only used if withResult=true) timeout the request will be closed if the process is not completed before the + requestTimeout. If requestTimeout = 0, uses the generic requestTimeout configured in the gateway. +- `fetchVariables` - (optional, only used if withResult=true) list of names of variables to be included in `variables` property of the response. + If empty, all visible variables in the root scope will be returned. ##### Response @@ -240,7 +340,8 @@ The binding returns a JSON with the following response: "processDefinitionKey": 2251799813685895, "bpmnProcessId": "products-process", "version": 3, - "processInstanceKey": 2251799813687851 + "processInstanceKey": 2251799813687851, + "variables": "{\"productId\":\"some-product-id\"}" } ``` @@ -250,6 +351,8 @@ The response values are: - `bpmnProcessId` - the BPMN process ID of the process definition which was used to create the process instance - `version` - the version of the process definition which was used to create the process instance - `processInstanceKey` - the unique identifier of the created process instance +- `variables` - (optional, only if withResult=true was used in the request) JSON document consists of visible variables in the root scope; + returned as a serialized JSON document #### cancel-instance @@ -262,7 +365,6 @@ To perform a `cancel-instance` operation, invoke the Zeebe command binding with "data": { "processInstanceKey": 2251799813687851 }, - "metadata": {}, "operation": "cancel-instance" } ``` @@ -291,7 +393,6 @@ To perform a `set-variables` operation, invoke the Zeebe command binding with a "productKey": "some-product-key" } }, - "metadata": {}, "operation": "set-variables" } ``` @@ -334,7 +435,6 @@ To perform a `resolve-incident` operation, invoke the Zeebe command binding with "data": { "incidentKey": 2251799813686123 }, - "metadata": {}, "operation": "resolve-incident" } ``` @@ -355,14 +455,17 @@ To perform a `publish-message` operation, invoke the Zeebe command binding with ```json { - "messageName": "", - "correlationKey": "2", - "timeToLive": "1m", - "variables": { - "productId": "some-product-id", - "productName": "some-product-name", - "productKey": "some-product-key" - } + "data": { + "messageName": "product-message", + "correlationKey": "2", + "timeToLive": "1m", + "variables": { + "productId": "some-product-id", + "productName": "some-product-name", + "productKey": "some-product-key" + }, + }, + "operation": "publish-message" } ``` @@ -408,9 +511,9 @@ To perform a `activate-jobs` operation, invoke the Zeebe command binding with a "productId", "productName", "productKey" - ] + ], + "requestTimeout": "30s" }, - "metadata": {}, "operation": "activate-jobs" } ``` @@ -423,6 +526,8 @@ The data parameters are: - `workerName` - (optional, default: `default`) the name of the worker activating the jobs, mostly used for logging purposes - `fetchVariables` - (optional) a list of variables to fetch as the job variables; if empty, all visible variables at the time of activation for the scope of the job will be returned +- `requestTimeout` - (optional) the request will be completed when at least one job is activated or after the requestTimeout. If the requestTimeout = 0, + a default timeout is used. If the requestTimeout < 0, long polling is disabled and the request is completed immediately, even when no job is activated. ##### Response @@ -431,7 +536,19 @@ The binding returns a JSON with the following response: ```json [ { - + "key": 2251799813685267, + "type": "fetch-products", + "processInstanceKey": 2251799813685260, + "bpmnProcessId": "products", + "processDefinitionVersion": 1, + "processDefinitionKey": 2251799813685249, + "elementId": "Activity_test", + "elementInstanceKey": 2251799813685266, + "customHeaders": "{\"process-header-1\":\"1\",\"process-header-2\":\"2\"}", + "worker": "test", + "retries": 1, + "deadline": 1694091934039, + "variables":"{\"productId\":\"some-product-id\"}" } ] ``` @@ -450,7 +567,7 @@ The response values are: - `worker` - the name of the worker which activated this job - `retries` - the amount of retries left to this job (should always be positive) - `deadline` - when the job can be activated again, sent as a UNIX epoch timestamp -- `variables` - JSON document, computed at activation time, consisting of all visible variables to the task scope +- `variables` - computed at activation time, consisting of all visible variables to the task scope; returned as a serialized JSON document #### complete-job @@ -468,7 +585,6 @@ To perform a `complete-job` operation, invoke the Zeebe command binding with a ` "productKey": "some-product-key" } }, - "metadata": {}, "operation": "complete-job" } ``` @@ -495,9 +611,14 @@ To perform a `fail-job` operation, invoke the Zeebe command binding with a `POST "data": { "jobKey": 2251799813685739, "retries": 5, - "errorMessage": "some error occurred" + "errorMessage": "some error occurred", + "retryBackOff": "30s", + "variables": { + "productId": "some-product-id", + "productName": "some-product-name", + "productKey": "some-product-key" + } }, - "metadata": {}, "operation": "fail-job" } ``` @@ -506,8 +627,14 @@ The data parameters are: - `jobKey` - the unique job identifier, as obtained when activating the job - `retries` - the amount of retries the job should have left -- `errorMessage ` - (optional) an message describing why the job failed this is particularly useful if a job runs out of retries and an +- `errorMessage ` - (optional) a message describing why the job failed this is particularly useful if a job runs out of retries and an incident is raised, as it this message can help explain why an incident was raised +- `retryBackOff` - (optional) the back-off timeout for the next retry +- `variables` - (optional) JSON document that will instantiate the variables at the local scope of the + job's associated task; it must be a JSON object, as variables will be mapped in a + key-value fashion. e.g. { "a": 1, "b": 2 } will create two variables, named "a" and + "b" respectively, with their associated values. [{ "a": 1, "b": 2 }] would not be a + valid argument, as the root of the JSON document is an array and not an object. ##### Response @@ -526,7 +653,6 @@ To perform a `update-job-retries` operation, invoke the Zeebe command binding wi "jobKey": 2251799813686172, "retries": 10 }, - "metadata": {}, "operation": "update-job-retries" } ``` @@ -554,7 +680,6 @@ To perform a `throw-error` operation, invoke the Zeebe command binding with a `P "errorCode": "product-fetch-error", "errorMessage": "The product could not be fetched" }, - "metadata": {}, "operation": "throw-error" } ``` diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/zeebe-jobworker.md b/daprdocs/content/en/reference/components-reference/supported-bindings/zeebe-jobworker.md index a4c20cff9ba..b84188090c4 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/zeebe-jobworker.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/zeebe-jobworker.md @@ -48,6 +48,8 @@ spec: value: "productId, productName, productKey" - name: autocomplete value: "true" + - name: retryBackOff + value: "30s" - name: direction value: "input" ``` @@ -70,6 +72,7 @@ spec: | `pollThreshold` | N | Input | Set the threshold of buffered activated jobs before polling for new jobs, i.e. threshold * maxJobsActive. Defaults to 0.3 | `"0.3"` | | `fetchVariables` | N | Input | A list of variables to fetch as the job variables; if empty, all visible variables at the time of activation for the scope of the job will be returned | `"productId"`, `"productName"`, `"productKey"` | | `autocomplete` | N | Input | Indicates if a job should be autocompleted or not. If not set, all jobs will be auto-completed by default. Disable it if the worker should manually complete or fail the job with either a business error or an incident | `"true"`, `"false"` | +| `retryBackOff` | N | Input | The back-off timeout for the next retry if a job fails | `15s` | | `direction` | N | Input | The direction of the binding | `"input"` | ## Binding support @@ -121,6 +124,7 @@ original data type so that it can be converted back to the equivalent data type | X-Zeebe-Worker | string | The name of the worker which activated this job | | X-Zeebe-Retries | int32 | The amount of retries left to this job (should always be positive) | | X-Zeebe-Deadline | int64 | When the job can be activated again, sent as a UNIX epoch timestamp | +| X-Zeebe-Autocomplete | bool | The autocomplete status that is defined in the binding metadata | ## Related links From e9e0e75d9d5e7f6d5cc18fdf1d0691942797100e Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Thu, 14 Sep 2023 09:57:18 -0400 Subject: [PATCH 032/162] update to beta1 Signed-off-by: Hannah Hunter --- .../content/en/concepts/building-blocks-concept.md | 2 +- .../workflow/howto-manage-workflow.md | 14 +++++++------- .../building-blocks/workflow/workflow-patterns.md | 4 ++-- .../content/en/operations/support/alpha-apis.md | 2 +- daprdocs/content/en/reference/api/workflow_api.md | 14 +++++++------- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/daprdocs/content/en/concepts/building-blocks-concept.md b/daprdocs/content/en/concepts/building-blocks-concept.md index 4719626f3c6..1841dd58468 100644 --- a/daprdocs/content/en/concepts/building-blocks-concept.md +++ b/daprdocs/content/en/concepts/building-blocks-concept.md @@ -28,5 +28,5 @@ Dapr provides the following building blocks: | [**Secrets**]({{< ref "secrets-overview.md" >}}) | `/v1.0/secrets` | Dapr provides a secrets building block API and integrates with secret stores such as public cloud stores, local stores and Kubernetes to store the secrets. Services can call the secrets API to retrieve secrets, for example to get a connection string to a database. | [**Configuration**]({{< ref "configuration-api-overview.md" >}}) | `/v1.0/configuration` | The Configuration API enables you to retrieve and subscribe to application configuration items for supported configuration stores. This enables an application to retrieve specific configuration information, for example, at start up or when configuration changes are made in the store. | [**Distributed lock**]({{< ref "distributed-lock-api-overview.md" >}}) | `/v1.0-alpha1/lock` | The distributed lock API enables you to take a lock on a resource so that multiple instances of an application can access the resource without conflicts and provide consistency guarantees. -| [**Workflows**]({{< ref "workflow-overview.md" >}}) | `/v1.0-alpha1/workflow` | The Workflow API enables you to define long running, persistent processes or data flows that span multiple microservices using Dapr workflows or workflow components. The Workflow API can be combined with other Dapr API building blocks. For example, a workflow can call another service with service invocation or retrieve secrets, providing flexibility and portability. +| [**Workflows**]({{< ref "workflow-overview.md" >}}) | `/v1.0-beta1/workflow` | The Workflow API enables you to define long running, persistent processes or data flows that span multiple microservices using Dapr workflows or workflow components. The Workflow API can be combined with other Dapr API building blocks. For example, a workflow can call another service with service invocation or retrieve secrets, providing flexibility and portability. | [**Cryptography**]({{< ref "cryptography-overview.md" >}}) | `/v1.0-alpha1/crypto` | The Cryptography API enables you to perform cryptographic operations, such as encrypting and decrypting messages, without exposing keys to your application. \ No newline at end of file diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md index 1b242f281dd..e4827ae749b 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md @@ -129,7 +129,7 @@ Manage your workflow using HTTP calls. The example below plugs in the properties To start your workflow with an ID `12345678`, run: ```http -POST http://localhost:3500/v1.0-alpha1/workflows/dapr/OrderProcessingWorkflow/start?instanceID=12345678 +POST http://localhost:3500/v1.0-beta1/workflows/dapr/OrderProcessingWorkflow/start?instanceID=12345678 ``` Note that workflow instance IDs can only contain alphanumeric characters, underscores, and dashes. @@ -139,7 +139,7 @@ Note that workflow instance IDs can only contain alphanumeric characters, unders To terminate your workflow with an ID `12345678`, run: ```http -POST http://localhost:3500/v1.0-alpha1/workflows/dapr/12345678/terminate +POST http://localhost:3500/v1.0-beta1/workflows/dapr/12345678/terminate ``` ### Raise an event @@ -147,7 +147,7 @@ POST http://localhost:3500/v1.0-alpha1/workflows/dapr/12345678/terminate For workflow components that support subscribing to external events, such as the Dapr Workflow engine, you can use the following "raise event" API to deliver a named event to a specific workflow instance. ```http -POST http://localhost:3500/v1.0-alpha1/workflows///raiseEvent/ +POST http://localhost:3500/v1.0-beta1/workflows///raiseEvent/ ``` > An `eventName` can be any function. @@ -157,13 +157,13 @@ POST http://localhost:3500/v1.0-alpha1/workflows//}}). diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md index 29048c683ae..5460d14c225 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md @@ -247,7 +247,7 @@ The Dapr workflow HTTP API supports the asynchronous request-reply pattern out-o The following `curl` commands illustrate how the workflow APIs support this pattern. ```bash -curl -X POST http://localhost:3500/v1.0-alpha1/workflows/dapr/OrderProcessingWorkflow/start?instanceID=12345678 -d '{"Name":"Paperclips","Quantity":1,"TotalCost":9.95}' +curl -X POST http://localhost:3500/v1.0-beta1/workflows/dapr/OrderProcessingWorkflow/start?instanceID=12345678 -d '{"Name":"Paperclips","Quantity":1,"TotalCost":9.95}' ``` The previous command will result in the following response JSON: @@ -259,7 +259,7 @@ The previous command will result in the following response JSON: The HTTP client can then construct the status query URL using the workflow instance ID and poll it repeatedly until it sees the "COMPLETE", "FAILURE", or "TERMINATED" status in the payload. ```bash -curl http://localhost:3500/v1.0-alpha1/workflows/dapr/12345678 +curl http://localhost:3500/v1.0-beta1/workflows/dapr/12345678 ``` The following is an example of what an in-progress workflow status might look like. diff --git a/daprdocs/content/en/operations/support/alpha-apis.md b/daprdocs/content/en/operations/support/alpha-apis.md index 3e2e26354ee..9dcc81ab180 100644 --- a/daprdocs/content/en/operations/support/alpha-apis.md +++ b/daprdocs/content/en/operations/support/alpha-apis.md @@ -10,7 +10,7 @@ description: "List of current alpha APIs" | ------------------ | ---- | ---- | ----------- | ------------- | ------------------ | | Query State | [Query State proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/dapr.proto#L44) | `v1.0-alpha1/state/statestore/query` | The state query API enables you to retrieve, filter, and sort the key/value data stored in state store components. | [Query State API]({{< ref "howto-state-query-api.md" >}}) | v1.5 | | Distributed Lock | [Lock proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/dapr.proto#L112) | `/v1.0-alpha1/lock` | The distributed lock API enables you to take a lock on a resource. | [Distributed Lock API]({{< ref "distributed-lock-api-overview.md" >}}) | v1.8 | -| Workflow | [Workflow proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/dapr.proto#L151) | `/v1.0-alpha1/workflow` | The workflow API enables you to define long running, persistent processes or data flows. | [Workflow API]({{< ref "workflow-overview.md" >}}) | v1.10 | +| Workflow | [Workflow proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/dapr.proto#L151) | `/v1.0-beta1/workflow` | The workflow API enables you to define long running, persistent processes or data flows. | [Workflow API]({{< ref "workflow-overview.md" >}}) | v1.10 | | Bulk Publish | [Bulk publish proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/dapr.proto#L59) | `v1.0-alpha1/publish/bulk` | The bulk publish API allows you to publish multiple messages to a topic in a single request. | [Bulk Publish and Subscribe API]({{< ref "pubsub-bulk.md" >}}) | v1.10 | | Bulk Subscribe | [Bulk subscribe proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/appcallback.proto#L57) | N/A | The bulk subscribe application callback receives multiple messages from a topic in a single call. | [Bulk Publish and Subscribe API]({{< ref "pubsub-bulk.md" >}}) | v1.10 | | Cryptography | [Crypto proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/dapr.proto#L118) | `v1.0-alpha1/crypto` | The cryptography API enables you to perform **high level** cryptography operations for encrypting and decrypting messages. | [Cryptography API]({{< ref "cryptography-overview.md" >}}) | v1.11 | diff --git a/daprdocs/content/en/reference/api/workflow_api.md b/daprdocs/content/en/reference/api/workflow_api.md index 7b74de78a31..a2c0d3b1b0b 100644 --- a/daprdocs/content/en/reference/api/workflow_api.md +++ b/daprdocs/content/en/reference/api/workflow_api.md @@ -17,7 +17,7 @@ Dapr provides users with the ability to interact with workflows and comes with a Start a workflow instance with the given name and optionally, an instance ID. ``` -POST http://localhost:3500/v1.0-alpha1/workflows///start[?instanceID=] +POST http://localhost:3500/v1.0-beta1/workflows///start[?instanceID=] ``` Note that workflow instance IDs can only contain alphanumeric characters, underscores, and dashes. @@ -57,7 +57,7 @@ The API call will provide a response similar to this: Terminate a running workflow instance with the given name and instance ID. ``` -POST http://localhost:3500/v1.0-alpha1/workflows///terminate +POST http://localhost:3500/v1.0-beta1/workflow///terminate ``` ### URL parameters @@ -84,7 +84,7 @@ This API does not return any content. For workflow components that support subscribing to external events, such as the Dapr Workflow engine, you can use the following "raise event" API to deliver a named event to a specific workflow instance. ``` -POST http://localhost:3500/v1.0-alpha1/workflows///raiseEvent/ +POST http://localhost:3500/v1.0-beta1/workflows///raiseEvent/ ``` {{% alert title="Note" color="primary" %}} @@ -117,7 +117,7 @@ None. Pause a running workflow instance. ``` -POST http://localhost:3500/v1.0-alpha1/workflows///pause +POST http://localhost:3500/v1.0-beta1/workflows///pause ``` ### URL parameters @@ -144,7 +144,7 @@ None. Resume a paused workflow instance. ``` -POST http://localhost:3500/v1.0-alpha1/workflows///resume +POST http://localhost:3500/v1.0-beta1/workflow///resume ``` ### URL parameters @@ -171,7 +171,7 @@ None. Purge the workflow state from your state store with the workflow's instance ID. ``` -POST http://localhost:3500/v1.0-alpha1/workflows///purge +POST http://localhost:3500/v1.0-beta1/workflows///purge ``` ### URL parameters @@ -198,7 +198,7 @@ None. Get information about a given workflow instance. ``` -GET http://localhost:3500/v1.0-alpha1/workflows// +GET http://localhost:3500/v1.0-beta1/workflows// ``` ### URL parameters From 7ff09b565cd2dd96ca0e2e112135729966c69616 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Thu, 14 Sep 2023 10:25:00 -0400 Subject: [PATCH 033/162] add beta apis and move workflow to that table Signed-off-by: Hannah Hunter --- .../{alpha-apis.md => alpha-beta-apis.md} | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) rename daprdocs/content/en/operations/support/{alpha-apis.md => alpha-beta-apis.md} (87%) diff --git a/daprdocs/content/en/operations/support/alpha-apis.md b/daprdocs/content/en/operations/support/alpha-beta-apis.md similarity index 87% rename from daprdocs/content/en/operations/support/alpha-apis.md rename to daprdocs/content/en/operations/support/alpha-beta-apis.md index 9dcc81ab180..0f028dfd080 100644 --- a/daprdocs/content/en/operations/support/alpha-apis.md +++ b/daprdocs/content/en/operations/support/alpha-beta-apis.md @@ -1,16 +1,24 @@ --- type: docs -title: "Alpha APIs" -linkTitle: "Alpha APIs" +title: "Alpha and Beta APIs" +linkTitle: "Alpha & Beta APIs" weight: 5000 -description: "List of current alpha APIs" +description: "List of current alpha and beta APIs" --- +## Alpha APIs + | Building block/API | gRPC | HTTP | Description | Documentation | Version introduced | | ------------------ | ---- | ---- | ----------- | ------------- | ------------------ | | Query State | [Query State proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/dapr.proto#L44) | `v1.0-alpha1/state/statestore/query` | The state query API enables you to retrieve, filter, and sort the key/value data stored in state store components. | [Query State API]({{< ref "howto-state-query-api.md" >}}) | v1.5 | | Distributed Lock | [Lock proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/dapr.proto#L112) | `/v1.0-alpha1/lock` | The distributed lock API enables you to take a lock on a resource. | [Distributed Lock API]({{< ref "distributed-lock-api-overview.md" >}}) | v1.8 | -| Workflow | [Workflow proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/dapr.proto#L151) | `/v1.0-beta1/workflow` | The workflow API enables you to define long running, persistent processes or data flows. | [Workflow API]({{< ref "workflow-overview.md" >}}) | v1.10 | | Bulk Publish | [Bulk publish proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/dapr.proto#L59) | `v1.0-alpha1/publish/bulk` | The bulk publish API allows you to publish multiple messages to a topic in a single request. | [Bulk Publish and Subscribe API]({{< ref "pubsub-bulk.md" >}}) | v1.10 | | Bulk Subscribe | [Bulk subscribe proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/appcallback.proto#L57) | N/A | The bulk subscribe application callback receives multiple messages from a topic in a single call. | [Bulk Publish and Subscribe API]({{< ref "pubsub-bulk.md" >}}) | v1.10 | | Cryptography | [Crypto proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/dapr.proto#L118) | `v1.0-alpha1/crypto` | The cryptography API enables you to perform **high level** cryptography operations for encrypting and decrypting messages. | [Cryptography API]({{< ref "cryptography-overview.md" >}}) | v1.11 | + +## Beta APIs + +| Building block/API | gRPC | HTTP | Description | Documentation | Version introduced | +| ------------------ | ---- | ---- | ----------- | ------------- | ------------------ | +| Workflow | [Workflow proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/dapr.proto#L151) | `/v1.0-beta1/workflow` | The workflow API enables you to define long running, persistent processes or data flows. | [Workflow API]({{< ref "workflow-overview.md" >}}) | v1.10 | + From 8fc717d25c170f0e2ed22326b9ce7dc752759f80 Mon Sep 17 00:00:00 2001 From: Cassandra Coyle Date: Thu, 14 Sep 2023 10:55:25 -0500 Subject: [PATCH 034/162] add beta exception for binding components Signed-off-by: Cassandra Coyle --- .../en/operations/components/certification-lifecycle.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/daprdocs/content/en/operations/components/certification-lifecycle.md b/daprdocs/content/en/operations/components/certification-lifecycle.md index d91f48d3879..602b08440c3 100644 --- a/daprdocs/content/en/operations/components/certification-lifecycle.md +++ b/daprdocs/content/en/operations/components/certification-lifecycle.md @@ -48,6 +48,9 @@ All components start at the Alpha stage. - The component contains a record of the conformance test result reviewed and approved by Dapr maintainers with specific components-contrib version - Recommended for only non-business-critical uses because of potential for incompatible changes in subsequent releases +A component may skip the Beta stage and conformance test requirement per the discretion of the Maintainer if: +- The component is a binding and the certification tests are comprehensive + ### Stable - The component must have component [certification tests](#certification-tests) validating functionality and resiliency From 8913005d3a40ce5edc47992e29b23bbe5e338922 Mon Sep 17 00:00:00 2001 From: Cassie Coyle Date: Thu, 14 Sep 2023 12:36:14 -0500 Subject: [PATCH 035/162] Update daprdocs/content/en/operations/components/certification-lifecycle.md Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Signed-off-by: Cassie Coyle --- .../en/operations/components/certification-lifecycle.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/daprdocs/content/en/operations/components/certification-lifecycle.md b/daprdocs/content/en/operations/components/certification-lifecycle.md index 602b08440c3..bfe3e9b9b27 100644 --- a/daprdocs/content/en/operations/components/certification-lifecycle.md +++ b/daprdocs/content/en/operations/components/certification-lifecycle.md @@ -48,7 +48,11 @@ All components start at the Alpha stage. - The component contains a record of the conformance test result reviewed and approved by Dapr maintainers with specific components-contrib version - Recommended for only non-business-critical uses because of potential for incompatible changes in subsequent releases +{{% alert title="Note" color="primary" %}} A component may skip the Beta stage and conformance test requirement per the discretion of the Maintainer if: +- The component is a binding +- The certification tests are comprehensive +{{% /alert %}} - The component is a binding and the certification tests are comprehensive ### Stable From a44ae8f492a5a06a172c2341f6a88945164cea82 Mon Sep 17 00:00:00 2001 From: Cassie Coyle Date: Thu, 14 Sep 2023 12:36:34 -0500 Subject: [PATCH 036/162] Update daprdocs/content/en/operations/components/certification-lifecycle.md Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Signed-off-by: Cassie Coyle --- .../content/en/operations/components/certification-lifecycle.md | 1 - 1 file changed, 1 deletion(-) diff --git a/daprdocs/content/en/operations/components/certification-lifecycle.md b/daprdocs/content/en/operations/components/certification-lifecycle.md index bfe3e9b9b27..078e1d2b0fe 100644 --- a/daprdocs/content/en/operations/components/certification-lifecycle.md +++ b/daprdocs/content/en/operations/components/certification-lifecycle.md @@ -53,7 +53,6 @@ A component may skip the Beta stage and conformance test requirement per the dis - The component is a binding - The certification tests are comprehensive {{% /alert %}} -- The component is a binding and the certification tests are comprehensive ### Stable From adbe4f75030886230f8ba00bd2fc278926d6e67e Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Thu, 14 Sep 2023 15:09:16 -0400 Subject: [PATCH 037/162] link to lifecycles Signed-off-by: Hannah Hunter --- daprdocs/content/en/operations/support/alpha-beta-apis.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/daprdocs/content/en/operations/support/alpha-beta-apis.md b/daprdocs/content/en/operations/support/alpha-beta-apis.md index 0f028dfd080..c35d4fc4240 100644 --- a/daprdocs/content/en/operations/support/alpha-beta-apis.md +++ b/daprdocs/content/en/operations/support/alpha-beta-apis.md @@ -22,3 +22,6 @@ description: "List of current alpha and beta APIs" | ------------------ | ---- | ---- | ----------- | ------------- | ------------------ | | Workflow | [Workflow proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/dapr.proto#L151) | `/v1.0-beta1/workflow` | The workflow API enables you to define long running, persistent processes or data flows. | [Workflow API]({{< ref "workflow-overview.md" >}}) | v1.10 | +## Related links + +[Learn more about the Alpha, Beta, and Stable lifecycle stages.]({{< ref "certification-lifecycle.md#certification-levels" >}}) \ No newline at end of file From 2eac805c5e890fbb11a37bcf90e7caa581af4651 Mon Sep 17 00:00:00 2001 From: Shubham Sharma Date: Sat, 16 Sep 2023 03:19:36 +0530 Subject: [PATCH 038/162] Add subscriptions to metadata API reference examples (#3682) * Add subscription example to metadata API Signed-off-by: Shubham Sharma * Update metadata API docs Signed-off-by: Shubham Sharma --------- Signed-off-by: Shubham Sharma Co-authored-by: Mark Fussell --- .../content/en/reference/api/metadata_api.md | 69 ++++++++++++++++--- 1 file changed, 59 insertions(+), 10 deletions(-) diff --git a/daprdocs/content/en/reference/api/metadata_api.md b/daprdocs/content/en/reference/api/metadata_api.md index 737e98671aa..77b5687500c 100644 --- a/daprdocs/content/en/reference/api/metadata_api.md +++ b/daprdocs/content/en/reference/api/metadata_api.md @@ -75,6 +75,7 @@ Name | Type ---- | ---- | ----------- id | string | Application ID runtimeVersion | string | Version of the Dapr runtime +enabledFeatures | string[] | List of features enabled by Dapr Configuration, see https://docs.dapr.io/operations/configuration/preview-features/ actors | [Metadata API Response Registered Actor](#metadataapiresponseactor)[] | A json encoded array of registered actors metadata. extended.attributeName | string | List of custom attributes as key-value pairs, where key is the attribute name. components | [Metadata API Response Component](#metadataapiresponsecomponent)[] | A json encoded array of loaded components metadata. @@ -111,14 +112,14 @@ Name | Type | Description pubsubname | string | Name of the pub/sub. topic | string | Topic name. metadata | object | Metadata associated with the subscription. -rules | [Metadata API Response Subscription Rules](metadataapiresponsesubscriptionrules)[] | List of rules associated with the subscription. +rules | [Metadata API Response Subscription Rules](#metadataapiresponsesubscriptionrules)[] | List of rules associated with the subscription. deadLetterTopic | string | Dead letter topic name. **Metadata API Response Subscription Rules** Name | Type | Description ---- | ---- | ----------- -match | string | CEL expression to match the message. +match | string | CEL expression to match the message, see https://docs.dapr.io/developing-applications/building-blocks/pubsub/howto-route-messages/#common-expression-language-cel path | string | Path to route the message if the match expression is true. **Metadata API Response AppConnectionProperties** @@ -143,15 +144,13 @@ healthThreshold | integer | Max number of failed health probes before the app is ### Examples -Note: This example is based on the Actor sample provided in the [Dapr SDK for Python](https://github.com/dapr/python-sdk/tree/master/examples/demo_actor). - ```shell curl http://localhost:3500/v1.0/metadata ``` ```json { - "id": "demo-actor", + "id": "myApp", "runtimeVersion": "1.12.0", "enabledFeatures": [ "ServiceInvocationStreaming" @@ -178,6 +177,27 @@ curl http://localhost:3500/v1.0/metadata ] } ], + "httpEndpoints": [ + { + "name": "my-backend-api" + } + ], + "subscriptions": [ + { + "pubsubname": "pubsub", + "topic": "orders", + "deadLetterTopic": "", + "metadata": { + "ttlInSeconds": "30" + }, + "rules": [ + { + "match": "%!s()", + "path": "orders" + } + ] + } + ], "extended": { "appCommand": "uvicorn --port 3000 demo_actor_service:app", "appPID": "98121", @@ -187,7 +207,12 @@ curl http://localhost:3500/v1.0/metadata "appConnectionProperties": { "port": 3000, "protocol": "http", - "channelAddress": "127.0.0.1" + "channelAddress": "127.0.0.1", + "health": { + "healthProbeInterval": "5s", + "healthProbeTimeout": "500ms", + "healthThreshold": 3 + } } } ``` @@ -236,8 +261,6 @@ Code | Description ### Examples -Note: This example is based on the Actor sample provided in the [Dapr SDK for Python](https://github.com/dapr/python-sdk/tree/master/examples/demo_actor). - Add a custom attribute to the metadata endpoint: ```shell @@ -248,7 +271,7 @@ Get the metadata information to confirm your custom attribute was added: ```json { - "id": "demo-actor", + "id": "myApp", "runtimeVersion": "1.12.0", "enabledFeatures": [ "ServiceInvocationStreaming" @@ -275,6 +298,27 @@ Get the metadata information to confirm your custom attribute was added: ] } ], + "httpEndpoints": [ + { + "name": "my-backend-api" + } + ], + "subscriptions": [ + { + "pubsubname": "pubsub", + "topic": "orders", + "deadLetterTopic": "", + "metadata": { + "ttlInSeconds": "30" + }, + "rules": [ + { + "match": "%!s()", + "path": "orders" + } + ] + } + ], "extended": { "myDemoAttribute": "myDemoAttributeValue", "appCommand": "uvicorn --port 3000 demo_actor_service:app", @@ -285,7 +329,12 @@ Get the metadata information to confirm your custom attribute was added: "appConnectionProperties": { "port": 3000, "protocol": "http", - "channelAddress": "127.0.0.1" + "channelAddress": "127.0.0.1", + "health": { + "healthProbeInterval": "5s", + "healthProbeTimeout": "500ms", + "healthThreshold": 3 + } } } ``` From 02f56a6a7e6ca1ad3afbb66e4fffe5cc9c2ab38b Mon Sep 17 00:00:00 2001 From: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Date: Fri, 15 Sep 2023 18:49:13 -0400 Subject: [PATCH 039/162] remove as a preview feature (#3729) Signed-off-by: Hannah Hunter Co-authored-by: Mark Fussell --- .../content/en/operations/support/support-preview-features.md | 1 - 1 file changed, 1 deletion(-) diff --git a/daprdocs/content/en/operations/support/support-preview-features.md b/daprdocs/content/en/operations/support/support-preview-features.md index b64043b77a3..64d7fa1844f 100644 --- a/daprdocs/content/en/operations/support/support-preview-features.md +++ b/daprdocs/content/en/operations/support/support-preview-features.md @@ -15,7 +15,6 @@ For CLI there is no explicit opt-in, just the version that this was first made a | Feature | Description | Setting | Documentation | Version introduced | | --- | --- | --- | --- | --- | -| **App Middleware** | Allow middleware components to be executed when making service-to-service calls | N/A | [App Middleware]({{}}) | v1.9 | | **Streaming for HTTP service invocation** | Enables (partial) support for using streams in HTTP service invocation; see below for more details. | `ServiceInvocationStreaming` | [Details]({{< ref "support-preview-features.md#streaming-for-http-service-invocation" >}}) | v1.10 | | **Pluggable components** | Allows creating self-hosted gRPC-based components written in any language that supports gRPC. The following component APIs are supported: State stores, Pub/sub, Bindings | N/A | [Pluggable components concept]({{}})| v1.9 | | **Multi-App Run** | Configure multiple Dapr applications from a single configuration file and run from a single command | `dapr run -f` | [Multi-App Run]({{< ref multi-app-dapr-run.md >}}) | v1.10 | From 5ba6f545db7c7cc45ba8281bfa90fbb5812b7eb1 Mon Sep 17 00:00:00 2001 From: zhangchao Date: Sun, 17 Sep 2023 11:51:11 +0800 Subject: [PATCH 040/162] add clientName and heartBeat Signed-off-by: zhangchao --- .../components-reference/supported-pubsub/setup-rabbitmq.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-rabbitmq.md b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-rabbitmq.md index f2fecc6501c..89a098c4fe6 100644 --- a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-rabbitmq.md +++ b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-rabbitmq.md @@ -62,6 +62,10 @@ spec: value: false - name: ttlInSeconds value: 60 + - name: clientName + value: {podName} + - name: heartBeat + value: 10s ``` {{% alert title="Warning" color="warning" %}} @@ -96,6 +100,8 @@ The above example uses secrets as plain strings. It is recommended to use a secr | caCert | Required for using TLS | Certificate Authority (CA) certificate in PEM format for verifying server TLS certificates. | `"-----BEGIN CERTIFICATE-----\n\n-----END CERTIFICATE-----"` | clientCert | Required for using TLS | TLS client certificate in PEM format. Must be used with `clientKey`. | `"-----BEGIN CERTIFICATE-----\n\n-----END CERTIFICATE-----"` | clientKey | Required for using TLS | TLS client key in PEM format. Must be used with `clientCert`. Can be `secretKeyRef` to use a secret reference. | `"-----BEGIN RSA PRIVATE KEY-----\n\n-----END RSA PRIVATE KEY-----"` +| clientName | N | This RabbitMQ [client-provided connection name](https://www.rabbitmq.com/connections.html#client-provided-names) is a custom identifier. If set, the identifier will be mentioned in RabbitMQ server log entries and management UI. Can be set to {uuid}, {podName}, or {appID}, which is replaced by Dapr runtime to the real value. | `"app1"`, `{uuid}`, `{podName}`, `{appID}` +| heartBeat | N | Defines the heartbeat interval with the server, detecting the aliveness of the peer TCP connection with the RabbitMQ server. Defaults to `10s` . | `"10s"` ## Communication using TLS From efd0ef6a772099a6b50d2a8ea588b68fc0a9c9a0 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Mon, 18 Sep 2023 13:49:15 -0400 Subject: [PATCH 041/162] update per mukundan Signed-off-by: Hannah Hunter --- .../multi-app-dapr-run/multi-app-overview.md | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md index 164ffe982e5..0d34e9bc2e0 100644 --- a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md +++ b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md @@ -98,9 +98,29 @@ Watch [this video for an overview on Multi-App Run](https://youtu.be/s1p9MNl4VGo ## Multi-App Run template file -When you execute `dapr run -f -k .`, Dapr generates the multi-app template file (named `dapr.yaml`) in the `.dapr/deploy` directory within each application. Each time you run `dapr run -f -k .`, the generated Kubernetes Deployment YAML file is overwritten. +Generate the multi-app template file (`dapr.yaml`) by running one of the following commands: -You can name template file with preferred name other than the default. For example `dapr run -f -k ./.yaml`. +```bash +dapr run -k -f . +``` + +Or + +```bash +dapr run -k -f dapr.yaml +``` + +The necessary default service and deployment definitions for Kubernetes are generated within the `.dapr/deploy` folder for each app in the `dapr.yaml` template. + +If the `createService` field is present in the `dapr.yaml` template and set to `true`, then the `service.yaml` file(s): +- Generate in the `.dapr/deploy` folder of each app in the multi-app run template +- Are used to deploy the applications in a dev/test environment in K8s + +You can name the template file with any preferred name other than the default. For example: + +```bash +dapr run -k -f ./.yaml +``` The following example includes some of the template properties you can customize for your applications. In the example, you can simultaneously launch 2 applications with app IDs of `nodeapp` and `pythonapp`. @@ -121,7 +141,7 @@ apps: ``` > **Note:** -> - If the `containerImage` field is not specified, `dapr run -f -k` produces an error. +> - If the `containerImage` field is not specified, `dapr run -k -f` produces an error. > - The `createService` field defines a basic service in Kubernetes (ClusterIP or LoadBalancer) that targets the `--app-port` specified in the template. If `createService` isn't specified, the application is not accessible from outside the cluster. For a more in-depth example and explanation of the template properties, see [Multi-app template]({{< ref multi-app-template.md >}}). From c2fc0a18da2886da9d4a04f7d75291b822b10001 Mon Sep 17 00:00:00 2001 From: Josh van Leeuwen Date: Mon, 18 Sep 2023 19:21:34 +0100 Subject: [PATCH 042/162] Reference: pubsub pulsar OIDC authentication (#3655) * Adds `OIDC` authentication to pulsar pubsub options Signed-off-by: joshvanl * Updates the pulsar OIDC authentication docs to use `oauth2` fields. Signed-off-by: joshvanl * Put pulsar authentication options into different sections Signed-off-by: joshvanl * Update daprdocs/content/en/reference/components-reference/supported-pubsub/setup-pulsar.md Co-authored-by: Mark Fussell Signed-off-by: Josh van Leeuwen * Update daprdocs/content/en/reference/components-reference/supported-pubsub/setup-pulsar.md Co-authored-by: Mark Fussell Signed-off-by: Josh van Leeuwen * Update daprdocs/content/en/reference/components-reference/supported-pubsub/setup-pulsar.md Co-authored-by: Mark Fussell Signed-off-by: Josh van Leeuwen --------- Signed-off-by: joshvanl Signed-off-by: Josh van Leeuwen Co-authored-by: Mark Fussell Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> --- .../supported-pubsub/setup-pulsar.md | 72 ++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-pulsar.md b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-pulsar.md index 45726e25363..a41aaee1f6f 100644 --- a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-pulsar.md +++ b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-pulsar.md @@ -73,7 +73,6 @@ The above example uses secrets as plain strings. It is recommended to use a [sec |--------------------|:--------:|---------|---------| | host | Y | Address of the Pulsar broker. Default is `"localhost:6650"` | `"localhost:6650"` OR `"http://pulsar-pj54qwwdpz4b-pulsar.ap-sg.public.pulsar.com:8080"`| | enableTLS | N | Enable TLS. Default: `"false"` | `"true"`, `"false"` | -| token | N | Enable Authentication. | [How to create pulsar token](https://pulsar.apache.org/docs/en/security-jwt/#generate-tokens)| | tenant | N | The topic tenant within the instance. Tenants are essential to multi-tenancy in Pulsar, and spread across clusters. Default: `"public"` | `"public"` | | consumerID | N | Used to set the subscription name or consumer ID. | `"channel1"` | namespace | N | The administrative unit of the topic, which acts as a grouping mechanism for related topics. Default: `"default"` | `"default"` @@ -91,6 +90,77 @@ The above example uses secrets as plain strings. It is recommended to use a [sec | subscribeType | N | Pulsar supports four kinds of [subscription types](https://pulsar.apache.org/docs/3.0.x/concepts-messaging/#subscription-types). Default: `"shared"` | `"shared"`, `"exclusive"`, `"failover"`, `"key_shared"`| | partitionKey | N | Sets the key of the message for routing policy. Default: `""` | | +### Authenticate using Token + +To authenticate to pulsar using a static [JWT token](https://pulsar.apache.org/docs/en/security-jwt), you can use the following metadata field: + +| Field | Required | Details | Example | +|--------|:--------:|---------|---------| +| token | N | Token used for authentication. | [How to create Pulsar token](https://pulsar.apache.org/docs/en/security-jwt/#generate-tokens)| + +```yaml +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: messagebus +spec: + type: pubsub.pulsar + version: v1 + metadata: + - name: host + value: "pulsar.example.com:6650" + - name: token + secretKeyRef: + name: pulsar + key: token +``` + +### Authenticate using OIDC + +Since `v3.0`, [Pulsar supports OIDC authentication](https://pulsar.apache.org/docs/3.0.x/security-openid-connect/). +To enable OIDC authentication, you need to provide the following OAuth2 parameters to the component spec. +OAuth2 authentication cannot be used in combination with token authentication. +It is recommended that you use a secret reference for the client secret. +The pulsar OAuth2 authenticator is not specifically complaint with OIDC so it is your responsibility to ensure fields are compliant. For example, the issuer URL must use the `https` protocol, the requested scopes include `openid`, etc. +If the `oauth2TokenCAPEM` field is omitted then the system's certificate pool is used for connecting to the OAuth2 issuer if using `https`. + +| Field | Required | Details | Example | +|--------|:--------:|---------|---------| +| oauth2TokenURL | N | URL to request the OIDC client_credentials token from. Must not be empty. | "https://oauth.example.com/o/oauth2/token"` | +| oauth2TokenCAPEM | N | CA PEM certificate bundle to connect to the OAuth2 issuer. If not defined, the system's certificate pool will be used. | `"---BEGIN CERTIFICATE---\n...\n---END CERTIFICATE---"` | +| oauth2ClientID | N | OIDC client ID. Must not be empty. | `"my-client-id"` | +| oauth2ClientSecret | N | OIDC client secret. Must not be empty. | `"my-client-secret"` | +| oauth2Audiences | N | Comma separated list of audiences to request for. Must not be empty. | `"my-audience-1,my-audience-2"` | +| oauth2Scopes | N | Comma separated list of scopes to request. Must not be empty. | `"openid,profile,email"` | + + +```yaml +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: messagebus +spec: + type: pubsub.pulsar + version: v1 + metadata: + - name: host + value: "pulsar.example.com:6650" + - name: oauth2TokenURL + value: https://oauth.example.com/o/oauth2/token + - name: oauth2TokenCAPEM + value: "---BEGIN CERTIFICATE---\n...\n---END CERTIFICATE---" + - name: oauth2ClientID + value: my-client-id + - name: oauth2ClientSecret + secretKeyRef: + name: pulsar-oauth2 + key: my-client-secret + - name: oauth2Audiences + value: "my.pulsar.example.com,another.pulsar.example.com" + - name: oauth2Scopes + value: "openid,profile,email" +``` + ### Enabling message delivery retries The Pulsar pub/sub component has no built-in support for retry strategies. This means that sidecar sends a message to the service only once and is not retried in case of failures. To make Dapr use more spohisticated retry policies, you can apply a [retry resiliency policy]({{< ref "policies.md#retries" >}}) to the Pulsar pub/sub component. Note that it will be the same Dapr sidecar retrying the redelivery the message to the same app instance and not other instances. From f261ac56718dac0755eb26eabf2678018d0303c2 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Tue, 19 Sep 2023 09:47:38 -0400 Subject: [PATCH 043/162] updates from Ryan Signed-off-by: Hannah Hunter --- .../building-blocks/workflow/howto-author-workflow.md | 2 +- .../building-blocks/workflow/howto-manage-workflow.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md index b1a44ed2589..52be5dba1ab 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md @@ -126,7 +126,7 @@ public class ProcessPaymentActivity : WorkflowActivity -Define the workflow activities you'd like your workflow to perform. Activities are wrapped in an wrapper class, which is called `OrchestrationWrapper` in the following exmaple. +Define the workflow activities you'd like your workflow to perform. Activities are wrapped in a wrapper class, which is called `OrchestrationWrapper` in the following exmaple. ```java class OrchestratorWrapper implements TaskOrchestrationFactory { diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md index e4827ae749b..2491d99347d 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md @@ -79,7 +79,7 @@ Dictionary workflowOptions; // This is an optional parameter StartWorkflowResponse startResponse = await daprClient.StartWorkflowAsync(orderId, workflowComponent, workflowName, input, workflowOptions); // Get information on the workflow. This response contains information such as the status of the workflow, when it started, and more! -GetWorkflowResponse getResponse = await daprClient.GetWorkflowAsync(orderId, workflowComponent, workflowName); +GetWorkflowResponse getResponse = await daprClient.GetWorkflowAsync(orderId, workflowComponent, eventName); // Terminate the workflow await daprClient.TerminateWorkflowAsync(orderId, workflowComponent); @@ -93,7 +93,7 @@ await daprClient.PauseWorkflowAsync(orderId, workflowComponent); // Resume await daprClient.ResumeWorkflowAsync(orderId, workflowComponent); -// Purge +// Purge the workflow, removing all inbox and history information from associated instance await daprClient.PurgeWorkflowAsync(orderId, workflowComponent); ``` From 3439d2cfe4b5d0d2ff681c5bbeccd3d6832d183b Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Tue, 19 Sep 2023 10:05:54 -0400 Subject: [PATCH 044/162] copy table to new topic under API Signed-off-by: Hannah Hunter --- .../content/en/reference/api/error_codes.md | 2 +- .../content/en/reference/api/placement_api.md | 64 +++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 daprdocs/content/en/reference/api/placement_api.md diff --git a/daprdocs/content/en/reference/api/error_codes.md b/daprdocs/content/en/reference/api/error_codes.md index d93bc0f1188..7de8c0a2c3c 100644 --- a/daprdocs/content/en/reference/api/error_codes.md +++ b/daprdocs/content/en/reference/api/error_codes.md @@ -3,7 +3,7 @@ type: docs title: "Error codes returned by APIs" linkTitle: "Error codes" description: "Detailed reference of the Dapr API error codes" -weight: 1200 +weight: 1300 --- For http calls made to Dapr runtime, when an error is encountered, an error json is returned in http response body. The json contains an error code and an descriptive error message, e.g. diff --git a/daprdocs/content/en/reference/api/placement_api.md b/daprdocs/content/en/reference/api/placement_api.md new file mode 100644 index 00000000000..9cb86a3ddd9 --- /dev/null +++ b/daprdocs/content/en/reference/api/placement_api.md @@ -0,0 +1,64 @@ +--- +type: docs +title: "Placement API reference" +linkTitle: "Placement API" +description: "Detailed documentation on the Placement API" +weight: 1200 +--- + +Dapr has an HTTP API `/placement/state` for placement service that exposes placement table information. The API is exposed on the sidecar on the same port as the healthz. This is an unauthenticated endpoint, and is disabled by default. + +You need to set `DAPR_PLACEMENT_METADATA_ENABLED` environment or `metadata-enabled` command line args to true to enable it. If you are using helm, set `dapr_placement.metadataEnabled` to true. + +## Usecase + +The placement table API can be used for retrieving the current placement table, which contains all the actors registered. This can be helpful for debugging and allows tools to extract and present information about actors. + +## HTTP Request + +``` +GET http://localhost:/placement/state +``` + +## HTTP Response Codes + +Code | Description +---- | ----------- +200 | Placement tables information returned +500 | Placement could not return the placement tables information + +## HTTP Response Body + +**Placement tables API Response Object** + +Name | Type | Description +---- | ---- | ----------- +tableVersion | int | The placement table version +hostList | [Actor Host Info](#actorhostinfo)[] | A json array of registered actors host info. + +**Actor Host Info** + +Name | Type | Description +---- | ---- | ----------- +name | string | The host:port address of the actor. +appId | string | app id. +actorTypes | json string array | List of actor types it hosts. +updatedAt | timestamp | Timestamp of the actor registered/updated. + +## Examples + +```shell + curl localhost:8080/placement/state +``` + +```json +{ + "hostList": [{ + "name": "198.18.0.1:49347", + "appId": "actor", + "actorTypes": ["testActorType"], + "updatedAt": 1690274322325260000 + }], + "tableVersion": 1 +} +``` \ No newline at end of file From 52ed2b667b3b3d15426de6836b22177d1ebc683e Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 20 Sep 2023 12:22:09 -0400 Subject: [PATCH 045/162] update examples in how tos Signed-off-by: Hannah Hunter --- .../workflow/howto-author-workflow.md | 215 ++++++------------ .../workflow/howto-manage-workflow.md | 62 ++++- 2 files changed, 129 insertions(+), 148 deletions(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md index 52be5dba1ab..ada6335ec44 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md @@ -126,27 +126,42 @@ public class ProcessPaymentActivity : WorkflowActivity -Define the workflow activities you'd like your workflow to perform. Activities are wrapped in a wrapper class, which is called `OrchestrationWrapper` in the following exmaple. +Define the workflow activities you'd like your workflow to perform. Activities are wrapped in the public `DemoWorkflowActivity` class, which implements the workflow activities. ```java -class OrchestratorWrapper implements TaskOrchestrationFactory { - private final Constructor workflowConstructor; - private final String name; +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) +public class DemoWorkflowActivity implements WorkflowActivity { + + @Override + public DemoActivityOutput run(WorkflowActivityContext ctx) { + Logger logger = LoggerFactory.getLogger(DemoWorkflowActivity.class); + logger.info("Starting Activity: " + ctx.getName()); + + var message = ctx.getInput(DemoActivityInput.class).getMessage(); + var newMessage = message + " World!, from Activity"; + logger.info("Message Received from input: " + message); + logger.info("Sending message to output: " + newMessage); + + logger.info("Sleeping for 5 seconds to simulate long running operation..."); - public OrchestratorWrapper(Class clazz) { - this.name = clazz.getCanonicalName(); try { - this.workflowConstructor = clazz.getDeclaredConstructor(); - } catch (NoSuchMethodException e) { - throw new RuntimeException( - String.format("No constructor found for workflow class '%s'.", this.name), e - ); + TimeUnit.SECONDS.sleep(5); + } catch (InterruptedException e) { + throw new RuntimeException(e); } + + + logger.info("Activity finished"); + + var output = new DemoActivityOutput(message, newMessage); + logger.info("Activity returned: " + output); + + return output; } } ``` -[See a full Java SDK workflow activity example.](https://github.com/dapr/java-sdk/blob/master/sdk-workflows/src/main/java/io/dapr/workflows/runtime/OrchestratorWrapper.java) +[See the Java SDK workflow activity example in context.](https://github.com/dapr/java-sdk/blob/master/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowActivity.java) {{% /codetab %}} @@ -227,19 +242,29 @@ The `OrderProcessingWorkflow` class is derived from a base class called `Workflo -The `WorkflowActivity` interface executes the activity logic and returns a value to be serialized and returned to the caller. The following code registers a workflow activity object: +Next, register the workflow with the `WorkflowRuntimeBuilder` and start the workflow runtime. ```java -public WorkflowRuntimeBuilder registerWorkflow(Class clazz) { - this.builder = this.builder.addOrchestration( - new OrchestratorWrapper<>(clazz) - ); +public class DemoWorkflowWorker { + + public static void main(String[] args) throws Exception { + + // Register the Workflow with the builder. + WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder().registerWorkflow(DemoWorkflow.class); + builder.registerActivity(DemoWorkflowActivity.class); - return this; + // Build and then start the workflow runtime pulling and executing tasks + try (WorkflowRuntime runtime = builder.build()) { + System.out.println("Start workflow runtime"); + runtime.start(); + } + + System.exit(0); + } } ``` -[See the Java SDK workflow in context.](https://github.com/dapr/java-sdk/blob/master/sdk-workflows/src/main/java/io/dapr/workflows/runtime/WorkflowRuntimeBuilder.java) +[See the Java SDK workflow in context.](https://github.com/dapr/java-sdk/blob/master/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowWorker.java) {{% /codetab %}} @@ -416,137 +441,47 @@ app.Run(); -[In the following example](https://github.com/dapr/java-sdk/blob/master/sdk-workflows/src/main/java/io/dapr/workflows/client/DaprWorkflowClient.java), for a basic Java application using workflows for Java SDK, your project code would include: +[As in the following example](https://github.com/dapr/java-sdk/blob/master/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java), a hello-world application using the Java SDK and Dapr Workflow would include: - A Java package called `io.dapr.workflows.client` to receive the Java SDK client capabilities. - An import of `io.dapr.workflows.Workflow` -- A wrapper extending `Workflow` and implementing tasks/activities -- A declared `WorkflowRuntimeBuilder` builder -- API calls. In the example below, these calls start and terminate the workflow. +- The `DemoWorkflow` class which extends `Workflow` +- Creating the workflow with input and output. +- API calls. In the example below, these calls start and call the workflow activities. ```java -package io.dapr.workflows.client; +package io.dapr.examples.workflows; -import com.microsoft.durabletask.DurableTaskClient; -import com.microsoft.durabletask.DurableTaskGrpcClientBuilder; -import io.dapr.utils.NetworkUtils; +import com.microsoft.durabletask.CompositeTaskFailedException; +import com.microsoft.durabletask.Task; +import com.microsoft.durabletask.TaskCanceledException; import io.dapr.workflows.Workflow; -import io.grpc.ManagedChannel; - -import javax.annotation.Nullable; -import java.util.concurrent.TimeUnit; - -public class DaprWorkflowClient implements AutoCloseable { - - private DurableTaskClient innerClient; - private ManagedChannel grpcChannel; - - /** - * Public constructor for DaprWorkflowClient. This layer constructs the GRPC Channel. - */ - public DaprWorkflowClient() { - this(NetworkUtils.buildGrpcManagedChannel()); - } - - /** - * Private Constructor that passes a created DurableTaskClient and the new GRPC channel. - * - * @param grpcChannel ManagedChannel for GRPC channel. - */ - private DaprWorkflowClient(ManagedChannel grpcChannel) { - this(createDurableTaskClient(grpcChannel), grpcChannel); - } - - /** - * Private Constructor for DaprWorkflowClient. - * - * @param innerClient DurableTaskGrpcClient with GRPC Channel set up. - * @param grpcChannel ManagedChannel for instance variable setting. - * - */ - private DaprWorkflowClient(DurableTaskClient innerClient, ManagedChannel grpcChannel) { - this.innerClient = innerClient; - this.grpcChannel = grpcChannel; - } - - /** - * Static method to create the DurableTaskClient. - * - * @param grpcChannel ManagedChannel for GRPC. - * @return a new instance of a DurableTaskClient with a GRPC channel. - */ - private static DurableTaskClient createDurableTaskClient(ManagedChannel grpcChannel) { - return new DurableTaskGrpcClientBuilder() - .grpcChannel(grpcChannel) - .build(); - } - - /** - * Schedules a new workflow using DurableTask client. - * - * @param any Workflow type - * @param clazz Class extending Workflow to start an instance of. - * @return A String with the randomly-generated instance ID for new Workflow instance. - */ - public String scheduleNewWorkflow(Class clazz) { - return this.innerClient.scheduleNewOrchestrationInstance(clazz.getCanonicalName()); - } - - /** - * Schedules a new workflow using DurableTask client. - * - * @param any Workflow type - * @param clazz Class extending Workflow to start an instance of. - * @param input the input to pass to the scheduled orchestration instance. Must be serializable. - * @return A String with the randomly-generated instance ID for new Workflow instance. - */ - public String scheduleNewWorkflow(Class clazz, Object input) { - return this.innerClient.scheduleNewOrchestrationInstance(clazz.getCanonicalName(), input); - } - - /** - * Schedules a new workflow using DurableTask client. - * - * @param any Workflow type - * @param clazz Class extending Workflow to start an instance of. - * @param input the input to pass to the scheduled orchestration instance. Must be serializable. - * @param instanceId the unique ID of the orchestration instance to schedule - * @return A String with the instanceId parameter value. - */ - public String scheduleNewWorkflow(Class clazz, Object input, String instanceId) { - return this.innerClient.scheduleNewOrchestrationInstance(clazz.getCanonicalName(), input, instanceId); - } - - /** - * Terminates the workflow associated with the provided instance id. - * - * @param workflowInstanceId Workflow instance id to terminate. - * @param output the optional output to set for the terminated orchestration instance. - */ - public void terminateWorkflow(String workflowInstanceId, @Nullable Object output) { - this.innerClient.terminate(workflowInstanceId, output); - } - - /** - * Closes the inner DurableTask client and shutdown the GRPC channel. - * - */ - public void close() throws InterruptedException { - try { - if (this.innerClient != null) { - this.innerClient.close(); - this.innerClient = null; - } - } finally { - if (this.grpcChannel != null && !this.grpcChannel.isShutdown()) { - this.grpcChannel.shutdown().awaitTermination(5, TimeUnit.SECONDS); - this.grpcChannel = null; - } - } +import io.dapr.workflows.WorkflowStub; + +import java.time.Duration; +import java.util.Arrays; +import java.util.List; + +/** + * Implementation of the DemoWorkflow for the server side. + */ +public class DemoWorkflow extends Workflow { + @Override + public WorkflowStub create() { + return ctx -> { + ctx.getLogger().info("Starting Workflow: " + ctx.getName()); + // ... + ctx.getLogger().info("Calling Activity..."); + var input = new DemoActivityInput("Hello Activity!"); + var output = ctx.callActivity(DemoWorkflowActivity.class.getName(), input, DemoActivityOutput.class).await(); + // ... + }; } } ``` +[See the full Java SDK workflow example in context.](https://github.com/dapr/java-sdk/blob/master/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflow.java) + {{% /codetab %}} @@ -570,4 +505,4 @@ Now that you've authored a workflow, learn how to manage it. - Try out the full SDK examples: - [Python example](https://github.com/dapr/python-sdk/tree/master/examples/demo_workflow) - [.NET example](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow) - - [Java example](todo) + - [Java example](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/workflows) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md index 2491d99347d..fb7ad9d57c5 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-manage-workflow.md @@ -102,18 +102,64 @@ await daprClient.PurgeWorkflowAsync(orderId, workflowComponent); {{% codetab %}} -Manage your workflow within your code. In the workflow example from the [Author a workflow]({{< ref "howto-author-workflow.md#write-the-application" >}}) guide, the workflow is registered in the code using the following APIs: +Manage your workflow within your code. [In the workflow example from the Java SDK](https://github.com/dapr/java-sdk/blob/master/examples/src/main/java/io/dapr/examples/workflows/DemoWorkflowClient.java), the workflow is registered in the code using the following APIs: -- **raiseEvent**: Raise an event on a workflow -- **getInstanceMetadata**: Get information on the status of the workflow +- **scheduleNewWorkflow**: Starts a new workflow instance +- **getInstanceState**: Get information on the status of the workflow - **waitForInstanceStart**: Pauses or suspends a workflow instance that can later be resumed -- **waitForInstanceCompletion**: Resumes a paused workflow instance and waits for completion -- **createTaskHub** -- **deleteTaskHub** +- **raiseEvent**: Raises events/tasks for the running workflow instance +- **waitForInstanceCompletion**: Waits for the workflow to complete its tasks +- **purgeInstance**: Removes all metadata related to a specific workflow instance +- **terminateWorkflow**: Terminates the workflow - **purgeInstance**: Removes all metadata related to a specific workflow ```java -todo +package io.dapr.examples.workflows; + +import io.dapr.workflows.client.DaprWorkflowClient; +import io.dapr.workflows.client.WorkflowInstanceStatus; + +// ... +public class DemoWorkflowClient { + + // ... + public static void main(String[] args) throws InterruptedException { + DaprWorkflowClient client = new DaprWorkflowClient(); + + try (client) { + // Start a workflow + String instanceId = client.scheduleNewWorkflow(DemoWorkflow.class, "input data"); + + // Get status information on the workflow + WorkflowInstanceStatus workflowMetadata = client.getInstanceState(instanceId, true); + + // Wait or pause for the workflow instance start + try { + WorkflowInstanceStatus waitForInstanceStartResult = + client.waitForInstanceStart(instanceId, Duration.ofSeconds(60), true); + } + + // Raise an event for the workflow; you can raise several events in parallel + client.raiseEvent(instanceId, "TestEvent", "TestEventPayload"); + client.raiseEvent(instanceId, "event1", "TestEvent 1 Payload"); + client.raiseEvent(instanceId, "event2", "TestEvent 2 Payload"); + client.raiseEvent(instanceId, "event3", "TestEvent 3 Payload"); + + // Wait for workflow to complete running through tasks + try { + WorkflowInstanceStatus waitForInstanceCompletionResult = + client.waitForInstanceCompletion(instanceId, Duration.ofSeconds(60), true); + } + + // Purge the workflow instance, removing all metadata associated with it + boolean purgeResult = client.purgeInstance(instanceId); + + // Terminate the workflow instance + client.terminateWorkflow(instanceToTerminateId, null); + + System.exit(0); + } +} ``` {{% /codetab %}} @@ -197,6 +243,6 @@ Learn more about these HTTP calls in the [workflow API reference guide]({{< ref - Try out the full SDK examples: - [Python example](https://github.com/dapr/python-sdk/blob/master/examples/demo_workflow/app.py) - [.NET example](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow) - - [Java example](todo) + - [Java example](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/workflows) - [Workflow API reference]({{< ref workflow_api.md >}}) From d3618b2bf2046ca9b7b52358bfb34d7fe4e14418 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 20 Sep 2023 13:25:46 -0400 Subject: [PATCH 046/162] add workflow patterns Signed-off-by: Hannah Hunter --- .../workflow/workflow-patterns.md | 96 ++++++++++++++++++- 1 file changed, 93 insertions(+), 3 deletions(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md index 5460d14c225..8e45921c5ef 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md @@ -109,7 +109,21 @@ catch (TaskFailedException) // Task failures are surfaced as TaskFailedException ```java -todo +public static void main(String[] args) throws InterruptedException { + DaprWorkflowClient client = new DaprWorkflowClient(); + + try (client) { + client.raiseEvent(instanceId, "TestEvent", "TestEventPayload"); + + System.out.println(separatorStr); + System.out.println("** Registering parallel Events to be captured by allOf(t1,t2,t3) **"); + client.raiseEvent(instanceId, "event1", "TestEvent 1 Payload"); + client.raiseEvent(instanceId, "event2", "TestEvent 2 Payload"); + client.raiseEvent(instanceId, "event3", "TestEvent 3 Payload"); + System.out.printf("Events raised for workflow with instanceId: %s\n", instanceId); + + } +} ``` {{% /codetab %}} @@ -211,7 +225,47 @@ await context.CallActivityAsync("PostResults", sum); ```java -todo +public static void main(String[] args) throws InterruptedException { + DaprWorkflowClient client = new DaprWorkflowClient(); + + try (client) { + + System.out.println(separatorStr); + System.out.println("**SendExternalMessage**"); + client.raiseEvent(instanceId, "TestEvent", "TestEventPayload"); + + // Get events to process in parallel + System.out.println(separatorStr); + System.out.println("** Registering parallel Events to be captured by allOf(t1,t2,t3) **"); + client.raiseEvent(instanceId, "event1", "TestEvent 1 Payload"); + client.raiseEvent(instanceId, "event2", "TestEvent 2 Payload"); + client.raiseEvent(instanceId, "event3", "TestEvent 3 Payload"); + System.out.printf("Events raised for workflow with instanceId: %s\n", instanceId); + + // Register the raised events to be captured + System.out.println(separatorStr); + System.out.println("** Registering Event to be captured by anyOf(t1,t2,t3) **"); + client.raiseEvent(instanceId, "e2", "event 2 Payload"); + System.out.printf("Event raised for workflow with instanceId: %s\n", instanceId); + + // Wait for all tasks to complete and aggregate results + System.out.println(separatorStr); + System.out.println("**WaitForInstanceCompletion**"); + try { + WorkflowInstanceStatus waitForInstanceCompletionResult = + client.waitForInstanceCompletion(instanceId, Duration.ofSeconds(60), true); + System.out.printf("Result: %s%n", waitForInstanceCompletionResult); + } catch (TimeoutException ex) { + System.out.printf("waitForInstanceCompletion has an exception:%s%n", ex); + } + + System.out.println(separatorStr); + System.out.println("**purgeInstance**"); + boolean purgeResult = client.purgeInstance(instanceId); + System.out.printf("purgeResult: %s%n", purgeResult); + + } +} ``` {{% /codetab %}} @@ -552,7 +606,43 @@ public override async Task RunAsync(WorkflowContext context, OrderP ```java -todo +public static void main(String[] args) throws InterruptedException { + DaprWorkflowClient client = new DaprWorkflowClient(); + + try (client) { + String eventInstanceId = client.scheduleNewWorkflow(DemoWorkflow.class); + System.out.printf("Started new workflow instance with random ID: %s%n", eventInstanceId); + client.raiseEvent(eventInstanceId, "TestException", null); + System.out.printf("Event raised for workflow with instanceId: %s\n", eventInstanceId); + + System.out.println(separatorStr); + String instanceToTerminateId = "terminateMe"; + client.scheduleNewWorkflow(DemoWorkflow.class, null, instanceToTerminateId); + System.out.printf("Started new workflow instance with specified ID: %s%n", instanceToTerminateId); + + TimeUnit.SECONDS.sleep(5); + System.out.println("Terminate this workflow instance manually before the timeout is reached"); + client.terminateWorkflow(instanceToTerminateId, null); + System.out.println(separatorStr); + + String restartingInstanceId = "restarting"; + client.scheduleNewWorkflow(DemoWorkflow.class, null, restartingInstanceId); + System.out.printf("Started new workflow instance with ID: %s%n", restartingInstanceId); + System.out.println("Sleeping 30 seconds to restart the workflow"); + TimeUnit.SECONDS.sleep(30); + + System.out.println("**SendExternalMessage: RestartEvent**"); + client.raiseEvent(restartingInstanceId, "RestartEvent", "RestartEventPayload"); + + System.out.println("Sleeping 30 seconds to terminate the eternal workflow"); + TimeUnit.SECONDS.sleep(30); + client.terminateWorkflow(restartingInstanceId, null); + } + + System.out.println("Exiting DemoWorkflowClient."); + System.exit(0); + +} ``` {{% /codetab %}} From ae71e14c312ea8c309dbd5aa747a29629c629755 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 20 Sep 2023 13:49:09 -0400 Subject: [PATCH 047/162] nyemade limitation edit Signed-off-by: Hannah Hunter --- .../building-blocks/workflow/workflow-overview.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md index cf7326497f1..60edbbe6b5a 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md @@ -109,7 +109,8 @@ Want to skip the quickstarts? Not a problem. You can try out the workflow buildi With Dapr Workflow in beta stage comes the following limitation(s): -- **State stores:** For the {{% dapr-latest-version cli="true" %}} beta release of Dapr Workflow, you're not able to use NoSQL databases. Only SQL databases are supported. NoSQL database support is planned for future minor releases. +- **State stores:** For the {{% dapr-latest-version cli="true" %}} beta release of Dapr Workflow, you're not able to use NoSQL databases. Only SQL databases are supported in the latest release. +- **Application instances:** For the {{% dapr-latest-version cli="true" %}} beta release of Dapr Workflow, only a maximum of 2 application instances is supported. ## Watch the demo From 37e78d70d79b24e22b42be1316b8c0a54a1f99cf Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 20 Sep 2023 14:41:37 -0400 Subject: [PATCH 048/162] address all but one todo Signed-off-by: Hannah Hunter --- .../building-blocks/workflow/workflow-architecture.md | 2 +- .../building-blocks/workflow/workflow-overview.md | 5 +++-- .../building-blocks/workflow/workflow-patterns.md | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-architecture.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-architecture.md index 1322ea94f5f..18ec9110b30 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-architecture.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-architecture.md @@ -196,4 +196,4 @@ See the [Reminder usage and execution guarantees section]({{< ref "workflow-arch - Try out the following examples: - [Python](https://github.com/dapr/python-sdk/tree/master/examples/demo_workflow) - [.NET](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow) - - [Java](todo) \ No newline at end of file + - [Java](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/workflows) \ No newline at end of file diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md index 60edbbe6b5a..1e32756ff58 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md @@ -95,10 +95,10 @@ Want to put workflows to the test? Walk through the following quickstart and tut | Quickstart/tutorial | Description | | ------------------- | ----------- | -| [Workflow quickstart]({{< ref workflow-quickstart.md >}}) | Run a .NET workflow application with four workflow activities to see Dapr Workflow in action | +| [Workflow quickstart]({{< ref workflow-quickstart.md >}}) | Run a workflow application with four workflow activities to see Dapr Workflow in action | | [Workflow Python SDK example](https://github.com/dapr/python-sdk/tree/master/examples/demo_workflow) | Learn how to create a Dapr Workflow and invoke it using the Python `DaprClient` package. | | [Workflow .NET SDK example](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow) | Learn how to create a Dapr Workflow and invoke it using ASP.NET Core web APIs. | -| [Workflow Java SDK example](todo) | Learn how to create a Dapr Workflow and invoke it using the Java `need` package. | +| [Workflow Java SDK example](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/workflows) | Learn how to create a Dapr Workflow and invoke it using the Java `io.dapr.workflows` package. | ### Start using workflows directly in your app @@ -128,3 +128,4 @@ Watch [this video for an overview on Dapr Workflow](https://youtu.be/s1p9MNl4VGo - Try out the full SDK examples: - [.NET example](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow) - [Python example](https://github.com/dapr/python-sdk/tree/master/examples/demo_workflow) + - [Java example](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/workflows) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md index 8e45921c5ef..49b5fc2db01 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md @@ -688,7 +688,8 @@ await daprClient.RaiseWorkflowEventAsync( ```java -todo +System.out.println("**SendExternalMessage: RestartEvent**"); +client.raiseEvent(restartingInstanceId, "RestartEvent", "RestartEventPayload"); ``` {{% /codetab %}} @@ -709,4 +710,4 @@ External events don't have to be directly triggered by humans. They can also be - Try out the following examples: - [Python](https://github.com/dapr/python-sdk/tree/master/examples/demo_workflow) - [.NET](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow) - - [Java](todo) \ No newline at end of file + - [Java](https://github.com/dapr/java-sdk/tree/master/examples/src/main/java/io/dapr/examples/workflows) \ No newline at end of file From bac59579af05de5e47649d800ae25033607a27d3 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 20 Sep 2023 15:06:03 -0400 Subject: [PATCH 049/162] dummy link for workflow package Signed-off-by: Hannah Hunter --- .../building-blocks/workflow/workflow-overview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md index 1e32756ff58..923bc37947e 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md @@ -85,7 +85,7 @@ You can use the following SDKs to author a workflow. | - | - | | Python | [dapr-ext-workflow](https://github.com/dapr/python-sdk/tree/master/ext/dapr-ext-workflow) | | .NET | [Dapr.Workflow](https://www.nuget.org/profiles/dapr.io) | -| Java | need | +| Java | [io.dapr.workflows](https://dapr.github.io/java-sdk/io/dapr/workflows/package-summary.html) | ## Try out workflows From e3af25cbb54f20c2679ed00799ce370248f0fdd8 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 20 Sep 2023 16:32:17 -0400 Subject: [PATCH 050/162] add java to quickstart Signed-off-by: Hannah Hunter --- .../quickstarts/workflow-quickstart.md | 335 ++++++++++++++++-- 1 file changed, 300 insertions(+), 35 deletions(-) diff --git a/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md index fba93d75d3f..7895d4a9224 100644 --- a/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md @@ -23,7 +23,7 @@ In this guide, you'll: {{< tabs "Python" ".NET" "Java" >}} - + {{% codetab %}} The `order-processor` console app starts and manages the `order_processing_workflow`, which simulates purchasing items from a store. The workflow consists of five unique workflow activities, or tasks: @@ -103,7 +103,19 @@ Expected output: If you have Zipkin configured for Dapr locally on your machine, you can view the workflow trace spans in the Zipkin web UI (typically at `http://localhost:9411/zipkin/`). - +### (Optional) Step 4: View in Zipkin + +If you have Zipkin configured for Dapr locally on your machine, you can + +Running `dapr init` launches the [openzipkin/zipkin](https://hub.docker.com/r/openzipkin/zipkin/) Docker container. If the container has stopped running, launch the Zipkin Docker container with the following command: + +``` +docker run -d -p 9411:9411 openzipkin/zipkin +``` + +View the workflow trace spans in the Zipkin web UI (typically at `http://localhost:9411/zipkin/`). + + ### What happened? @@ -337,7 +349,15 @@ Expected output: ### (Optional) Step 4: View in Zipkin -If you have Zipkin configured for Dapr locally on your machine, you can view the workflow trace spans in the Zipkin web UI (typically at `http://localhost:9411/zipkin/`). +If you have Zipkin configured for Dapr locally on your machine, you can + +Running `dapr init` launches the [openzipkin/zipkin](https://hub.docker.com/r/openzipkin/zipkin/) Docker container. If the container has stopped running, launch the Zipkin Docker container with the following command: + +``` +docker run -d -p 9411:9411 openzipkin/zipkin +``` + +View the workflow trace spans in the Zipkin web UI (typically at `http://localhost:9411/zipkin/`). @@ -492,13 +512,23 @@ The `Activities` directory holds the four workflow activities used by the workfl - `ProcessPaymentActivity.cs` - `UpdateInventoryActivity.cs` +## Watch the demo + +Watch [this video to walk through the Dapr Workflow .NET demo](https://youtu.be/BxiKpEmchgQ?t=2564): + + + {{% /codetab %}} {{% codetab %}} The `order-processor` console app starts and manages the lifecycle of an order processing workflow that stores and retrieves data in a state store. The workflow consists of four workflow activities, or tasks: -- +- `NotifyActivity`: Utilizes a logger to print out messages throughout the workflow +- `RequestApprovalActivity`: Requests approval for processing payment +- `ReserveInventoryActivity`: Checks the state store to ensure that there is enough inventory for the purchase +- `ProcessPaymentActivity`: Processes and authorizes the payment +- `UpdateInventoryActivity`: Removes the requested items from the state store and updates the store with the new remaining inventory value ### Step 1: Pre-requisites @@ -507,9 +537,10 @@ For this example, you will need: - [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). - Java JDK 11 (or greater): - - [Oracle JDK](https://www.oracle.com/java/technologies/downloads), or - - OpenJDK -- [Apache Maven](https://maven.apache.org/install.html), version 3.x. + - [Microsoft JDK 11](https://docs.microsoft.com/en-us/java/openjdk/download#openjdk-11) + - [Oracle JDK 11](https://www.oracle.com/technetwork/java/javase/downloads/index.html#JDK11) + - [OpenJDK 11](https://jdk.java.net/11/) +- [Apache Maven](https://maven.apache.org/install.html) version 3.x. - [Docker Desktop](https://www.docker.com/products/docker-desktop) @@ -522,10 +553,16 @@ Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quic git clone https://github.com/dapr/quickstarts.git ``` -In a new terminal window, navigate to the `order-processor` directory: +Navigate to the `order-processor` directory: + +```bash +cd workflows/java/sdk/order-processor +``` + +Install the dependencies: ```bash -need +mvn clean install ``` ### Step 3: Run the order processor app @@ -533,7 +570,7 @@ need In the terminal, start the order processor app alongside a Dapr sidecar: ```bash -need +dapr run --app-id WorkflowConsoleApp --resources-path ../../../components/ --dapr-grpc-port 50001 -- java -jar target/OrderProcessingService-0.0.1-SNAPSHOT.jar io.dapr.quickstarts.workflows.WorkflowConsoleApp ``` This starts the `order-processor` app with unique workflow ID and runs the workflow activities. @@ -541,29 +578,64 @@ This starts the `order-processor` app with unique workflow ID and runs the workf Expected output: ``` -need +== APP == *** Welcome to the Dapr Workflow console app sample! +== APP == *** Using this app, you can place orders that start workflows. +== APP == Start workflow runtime +== APP == Sep 20, 2023 3:23:05 PM com.microsoft.durabletask.DurableTaskGrpcWorker startAndBlock +== APP == INFO: Durable Task worker is connecting to sidecar at 127.0.0.1:50001. + +== APP == ==========Begin the purchase of item:========== +== APP == Starting order workflow, purchasing 10 of cars + +== APP == scheduled new workflow instance of OrderProcessingWorkflow with instance ID: edceba90-9c45-4be8-ad40-60d16e060797 +== APP == [Thread-0] INFO io.dapr.workflows.WorkflowContext - Starting Workflow: io.dapr.quickstarts.workflows.OrderProcessingWorkflow +== APP == [Thread-0] INFO io.dapr.workflows.WorkflowContext - Instance ID(order ID): edceba90-9c45-4be8-ad40-60d16e060797 +== APP == [Thread-0] INFO io.dapr.workflows.WorkflowContext - Current Orchestration Time: 2023-09-20T19:23:09.755Z +== APP == [Thread-0] INFO io.dapr.workflows.WorkflowContext - Received Order: OrderPayload [itemName=cars, totalCost=150000, quantity=10] +== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.NotifyActivity - Received Order: OrderPayload [itemName=cars, totalCost=150000, quantity=10] +== APP == workflow instance edceba90-9c45-4be8-ad40-60d16e060797 started +== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.ReserveInventoryActivity - Reserving inventory for order 'edceba90-9c45-4be8-ad40-60d16e060797' of 10 cars +== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.ReserveInventoryActivity - There are 100 cars available for purchase +== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.ReserveInventoryActivity - Reserved inventory for order 'edceba90-9c45-4be8-ad40-60d16e060797' of 10 cars +== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.RequestApprovalActivity - Requesting approval for order: OrderPayload [itemName=cars, totalCost=150000, quantity=10] +== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.RequestApprovalActivity - Approved requesting approval for order: OrderPayload [itemName=cars, totalCost=150000, quantity=10] +== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.ProcessPaymentActivity - Processing payment: edceba90-9c45-4be8-ad40-60d16e060797 for 10 cars at $150000 +== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.ProcessPaymentActivity - Payment for request ID 'edceba90-9c45-4be8-ad40-60d16e060797' processed successfully +== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.UpdateInventoryActivity - Updating inventory for order 'edceba90-9c45-4be8-ad40-60d16e060797' of 10 cars +== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.UpdateInventoryActivity - Updated inventory for order 'edceba90-9c45-4be8-ad40-60d16e060797': there are now 90 cars left in stock +== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.NotifyActivity - Order completed! : edceba90-9c45-4be8-ad40-60d16e060797 + +== APP == workflow instance edceba90-9c45-4be8-ad40-60d16e060797 completed, out is: {"processed":true} ``` ### (Optional) Step 4: View in Zipkin -If you have Zipkin configured for Dapr locally on your machine, you can view the workflow trace spans in the Zipkin web UI (typically at `http://localhost:9411/zipkin/`). +If you have Zipkin configured for Dapr locally on your machine, you can + +Running `dapr init` launches the [openzipkin/zipkin](https://hub.docker.com/r/openzipkin/zipkin/) Docker container. If the container has stopped running, launch the Zipkin Docker container with the following command: + +``` +docker run -d -p 9411:9411 openzipkin/zipkin +``` + +View the workflow trace spans in the Zipkin web UI (typically at `http://localhost:9411/zipkin/`). ### What happened? -When you ran `need`: +When you ran `dapr run`: -1. A unique order ID for the workflow is generated (in the above example, `need`) and the workflow is scheduled. -1. The `need` workflow activity sends a notification saying an order for 10 cars has been received. -1. The `need` workflow activity checks the inventory data, determines if you can supply the ordered item, and responds with the number of cars in stock. -1. Your workflow starts and notifies you of its status. -1. The `need` workflow activity begins processing payment for order `need` and confirms if successful. -1. The `need` workflow activity updates the inventory with the current available cars after the order has been processed. -1. The `need` workflow activity sends a notification saying that order `need` has completed. +1. A unique order ID for the workflow is generated (in the above example, `edceba90-9c45-4be8-ad40-60d16e060797`) and the workflow is scheduled. +1. The `NotifyActivity` workflow activity sends a notification saying an order for 10 cars has been received. +1. The `ReserveInventoryActivity` workflow activity checks the inventory data, determines if you can supply the ordered item, and responds with the number of cars in stock. +1. Once approved, your workflow starts and notifies you of its status. +1. The `ProcessPaymentActivity` workflow activity begins processing payment for order `edceba90-9c45-4be8-ad40-60d16e060797` and confirms if successful. +1. The `UpdateInventoryActivity` workflow activity updates the inventory with the current available cars after the order has been processed. +1. The `NotifyActivity` workflow activity sends a notification saying that order `edceba90-9c45-4be8-ad40-60d16e060797` has completed. 1. The workflow terminates as completed. -#### `order-processor/... need` +#### `order-processor/WorkflowConsoleApp.java` In the application's program file: - The unique workflow order ID is generated @@ -572,33 +644,226 @@ In the application's program file: - The workflow and the workflow activities it invokes are registered ```java -need +package io.dapr.quickstarts.workflows; +import io.dapr.client.DaprClient; +import io.dapr.client.DaprClientBuilder; +import io.dapr.workflows.client.DaprWorkflowClient; + +public class WorkflowConsoleApp { + + private static final String STATE_STORE_NAME = "statestore-actors"; + + // ... + public static void main(String[] args) throws Exception { + System.out.println("*** Welcome to the Dapr Workflow console app sample!"); + System.out.println("*** Using this app, you can place orders that start workflows."); + // Wait for the sidecar to become available + Thread.sleep(5 * 1000); + + // Register the OrderProcessingWorkflow and its activities with the builder. + WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder().registerWorkflow(OrderProcessingWorkflow.class); + builder.registerActivity(NotifyActivity.class); + builder.registerActivity(ProcessPaymentActivity.class); + builder.registerActivity(RequestApprovalActivity.class); + builder.registerActivity(ReserveInventoryActivity.class); + builder.registerActivity(UpdateInventoryActivity.class); + + // Build the workflow runtime + try (WorkflowRuntime runtime = builder.build()) { + System.out.println("Start workflow runtime"); + runtime.start(false); + } + + InventoryItem inventory = prepareInventoryAndOrder(); + + DaprWorkflowClient workflowClient = new DaprWorkflowClient(); + try (workflowClient) { + executeWorkflow(workflowClient, inventory); + } + + } + + // Start the workflow runtime, pulling and executing tasks + private static void executeWorkflow(DaprWorkflowClient workflowClient, InventoryItem inventory) { + System.out.println("==========Begin the purchase of item:=========="); + String itemName = inventory.getName(); + int orderQuantity = inventory.getQuantity(); + int totalcost = orderQuantity * inventory.getPerItemCost(); + OrderPayload order = new OrderPayload(); + order.setItemName(itemName); + order.setQuantity(orderQuantity); + order.setTotalCost(totalcost); + System.out.println("Starting order workflow, purchasing " + orderQuantity + " of " + itemName); + + String instanceId = workflowClient.scheduleNewWorkflow(OrderProcessingWorkflow.class, order); + System.out.printf("scheduled new workflow instance of OrderProcessingWorkflow with instance ID: %s%n", + instanceId); + + // Check workflow instance start status + try { + workflowClient.waitForInstanceStart(instanceId, Duration.ofSeconds(10), false); + System.out.printf("workflow instance %s started%n", instanceId); + } catch (TimeoutException e) { + System.out.printf("workflow instance %s did not start within 10 seconds%n", instanceId); + return; + } + + // Check workflow instance complete status + try { + WorkflowInstanceStatus workflowStatus = workflowClient.waitForInstanceCompletion(instanceId, + Duration.ofSeconds(30), + true); + if (workflowStatus != null) { + System.out.printf("workflow instance %s completed, out is: %s %n", instanceId, + workflowStatus.getSerializedOutput()); + } else { + System.out.printf("workflow instance %s not found%n", instanceId); + } + } catch (TimeoutException e) { + System.out.printf("workflow instance %s did not complete within 30 seconds%n", instanceId); + } + + } + + private static InventoryItem prepareInventoryAndOrder() { + // prepare 100 cars in inventory + InventoryItem inventory = new InventoryItem(); + inventory.setName("cars"); + inventory.setPerItemCost(15000); + inventory.setQuantity(100); + DaprClient daprClient = new DaprClientBuilder().build(); + restockInventory(daprClient, inventory); + + // prepare order for 10 cars + InventoryItem order = new InventoryItem(); + order.setName("cars"); + order.setPerItemCost(15000); + order.setQuantity(10); + return order; + } + + private static void restockInventory(DaprClient daprClient, InventoryItem inventory) { + String key = inventory.getName(); + daprClient.saveState(STATE_STORE_NAME, key, inventory).block(); + } +} ``` -#### `order-processor/... need` +#### `OrderProcessingWorkflow.java` -In `need`, the workflow is defined as a class with all of its associated tasks (determined by workflow activities). +In `OrderProcessingWorkflow.java`, the workflow is defined as a class with all of its associated tasks (determined by workflow activities). ```java -need +package io.dapr.quickstarts.workflows; +import io.dapr.workflows.Workflow; + +public class OrderProcessingWorkflow extends Workflow { + + @Override + public WorkflowStub create() { + return ctx -> { + Logger logger = ctx.getLogger(); + String orderId = ctx.getInstanceId(); + logger.info("Starting Workflow: " + ctx.getName()); + logger.info("Instance ID(order ID): " + orderId); + logger.info("Current Orchestration Time: " + ctx.getCurrentInstant()); + + OrderPayload order = ctx.getInput(OrderPayload.class); + logger.info("Received Order: " + order.toString()); + OrderResult orderResult = new OrderResult(); + orderResult.setProcessed(false); + + // Notify the user that an order has come through + Notification notification = new Notification(); + notification.setMessage("Received Order: " + order.toString()); + ctx.callActivity(NotifyActivity.class.getName(), notification).await(); + + // Determine if there is enough of the item available for purchase by checking + // the inventory + InventoryRequest inventoryRequest = new InventoryRequest(); + inventoryRequest.setRequestId(orderId); + inventoryRequest.setItemName(order.getItemName()); + inventoryRequest.setQuantity(order.getQuantity()); + InventoryResult inventoryResult = ctx.callActivity(ReserveInventoryActivity.class.getName(), + inventoryRequest, InventoryResult.class).await(); + + // If there is insufficient inventory, fail and let the user know + if (!inventoryResult.isSuccess()) { + notification.setMessage("Insufficient inventory for order : " + order.getItemName()); + ctx.callActivity(NotifyActivity.class.getName(), notification).await(); + ctx.complete(orderResult); + return; + } + + // Require orders over a certain threshold to be approved + if (order.getTotalCost() > 5000) { + ApprovalResult approvalResult = ctx.callActivity(RequestApprovalActivity.class.getName(), + order, ApprovalResult.class).await(); + if (approvalResult != ApprovalResult.Approved) { + notification.setMessage("Order " + order.getItemName() + " was not approved."); + ctx.callActivity(NotifyActivity.class.getName(), notification).await(); + ctx.complete(orderResult); + return; + } + } + + // There is enough inventory available so the user can purchase the item(s). + // Process their payment + PaymentRequest paymentRequest = new PaymentRequest(); + paymentRequest.setRequestId(orderId); + paymentRequest.setItemBeingPurchased(order.getItemName()); + paymentRequest.setQuantity(order.getQuantity()); + paymentRequest.setAmount(order.getTotalCost()); + boolean isOK = ctx.callActivity(ProcessPaymentActivity.class.getName(), + paymentRequest, boolean.class).await(); + if (!isOK) { + notification.setMessage("Payment failed for order : " + orderId); + ctx.callActivity(NotifyActivity.class.getName(), notification).await(); + ctx.complete(orderResult); + return; + } + + inventoryResult = ctx.callActivity(UpdateInventoryActivity.class.getName(), + inventoryRequest, InventoryResult.class).await(); + if (!inventoryResult.isSuccess()) { + // If there is an error updating the inventory, refund the user + // paymentRequest.setAmount(-1 * paymentRequest.getAmount()); + // ctx.callActivity(ProcessPaymentActivity.class.getName(), + // paymentRequest).await(); + + // Let users know their payment processing failed + notification.setMessage("Order failed to update inventory! : " + orderId); + ctx.callActivity(NotifyActivity.class.getName(), notification).await(); + ctx.complete(orderResult); + return; + } + + // Let user know their order was processed + notification.setMessage("Order completed! : " + orderId); + ctx.callActivity(NotifyActivity.class.getName(), notification).await(); + + // Complete the workflow with order result is processed + orderResult.setProcessed(true); + ctx.complete(orderResult); + }; + } + +} ``` -#### `order-processor/... need` directory +#### `activities` directory The `Activities` directory holds the four workflow activities used by the workflow, defined in the following files: -- `need` +- [`NotifyActivity.java`](https://github.com/dapr/quickstarts/tree/master/workflows/java/sdk/order-processor/src/main/java/io/dapr/quickstarts/workflows/activities/NotifyActivity.java) +- [`RequestApprovalActivity`](https://github.com/dapr/quickstarts/tree/master/workflows/java/sdk/order-processor/src/main/java/io/dapr/quickstarts/workflows/activities/RequestApprovalActivity.java) +- [`ReserveInventoryActivity`](https://github.com/dapr/quickstarts/tree/master/workflows/java/sdk/order-processor/src/main/java/io/dapr/quickstarts/workflows/activities/ReserveInventoryActivity.java) +- [`ProcessPaymentActivity`](https://github.com/dapr/quickstarts/tree/master/workflows/java/sdk/order-processor/src/main/java/io/dapr/quickstarts/workflows/activities/ProcessPaymentActivity.java) +- [`UpdateInventoryActivity`](https://github.com/dapr/quickstarts/tree/master/workflows/java/sdk/order-processor/src/main/java/io/dapr/quickstarts/workflows/activities/UpdateInventoryActivity.java) {{% /codetab %}} {{< /tabs >}} -## Watch the demo - -Watch [this video to walk through the Dapr Workflow .NET demo](https://youtu.be/BxiKpEmchgQ?t=2564): - - - - ## Tell us what you think! We're continuously working to improve our Quickstart examples and value your feedback. Did you find this Quickstart helpful? Do you have suggestions for improvement? @@ -610,4 +875,4 @@ Join the discussion in our [discord channel](https://discord.com/channels/778680 - Walk through a more in-depth [.NET SDK example workflow](https://github.com/dapr/dotnet-sdk/tree/master/examples/Workflow) - Learn more about [Workflow as a Dapr building block]({{< ref workflow-overview >}}) -{{< button text="Explore Dapr tutorials >>" page="getting-started/tutorials/_index.md" >}} +{{< button text="Explore Dapr tutorials >>" page="getting-started/tutorials/_index.md" >}} \ No newline at end of file From cd472f17a5b149b0fc195d989d2505905f234fdf Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 20 Sep 2023 17:01:23 -0400 Subject: [PATCH 051/162] fix localized link Signed-off-by: Hannah Hunter --- .../en/getting-started/quickstarts/workflow-quickstart.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md index 7895d4a9224..a8d3f7c883c 100644 --- a/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md @@ -7,7 +7,7 @@ description: Get started with the Dapr Workflow building block --- {{% alert title="Note" color="primary" %}} -Dapr Workflow is currently in beta. [See known limitations for {{% dapr-latest-version cli="true" %}}]({{< ref "workflow-overview.md#limitations" >}}). +Dapr Workflow is currently in beta. [See known limitations for {{% dapr-latest-version cli="true" %}}]({{< ref "workflow-overview.md#limitations" >}}). {{% /alert %}} Let's take a look at the Dapr [Workflow building block]({{< ref workflow >}}). In this Quickstart, you'll create a simple console application to demonstrate Dapr's workflow programming model and the workflow management APIs. @@ -537,7 +537,7 @@ For this example, you will need: - [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). - Java JDK 11 (or greater): - - [Microsoft JDK 11](https://docs.microsoft.com/en-us/java/openjdk/download#openjdk-11) + - [Microsoft JDK 11](https://docs.microsoft.com/java/openjdk/download#openjdk-11) - [Oracle JDK 11](https://www.oracle.com/technetwork/java/javase/downloads/index.html#JDK11) - [OpenJDK 11](https://jdk.java.net/11/) - [Apache Maven](https://maven.apache.org/install.html) version 3.x. From c533e13b56779d1934119c7242e50c52e85646c5 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 20 Sep 2023 17:12:09 -0400 Subject: [PATCH 052/162] mukundan review Signed-off-by: Hannah Hunter --- .../multi-app-dapr-run/multi-app-overview.md | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md index 0d34e9bc2e0..e0d6c5511d9 100644 --- a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md +++ b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md @@ -26,7 +26,7 @@ With Multi-App Run, you can start multiple applications in either self-hosted or ## Multi-App Run template file -When you execute `dapr run -f .`, it generates the multi-app template file (named `dapr.yaml`) present in the current directory to run all the applications. +When you execute `dapr run -f .`, it starts the multi-app template file (named `dapr.yaml`) present in the current directory to run all the applications. You can name template file with preferred name other than the default. For example `dapr run -f ./.yaml`. @@ -98,23 +98,15 @@ Watch [this video for an overview on Multi-App Run](https://youtu.be/s1p9MNl4VGo ## Multi-App Run template file -Generate the multi-app template file (`dapr.yaml`) by running one of the following commands: +When you execute `dapr run -k -f .` or `dapr run -k -f dapr.yaml`, the applications defined in the `dapr.yaml` multi-app run template file will start in Kubernetes default namespace. -```bash -dapr run -k -f . -``` +The necessary default service and deployment definitions for Kubernetes are generated within the `.dapr/deploy` folder for each app in the `dapr.yaml` template. -Or +If the `createService` field is set to `true` in the `dapr.yaml` template for an app, then the `service.yaml` file is generated in the `.dapr/deploy` folder of the app. -```bash -dapr run -k -f dapr.yaml -``` - -The necessary default service and deployment definitions for Kubernetes are generated within the `.dapr/deploy` folder for each app in the `dapr.yaml` template. +Otherwise, only the `deployment.yaml` file is generated for each app that has the `containerImage` field set. -If the `createService` field is present in the `dapr.yaml` template and set to `true`, then the `service.yaml` file(s): -- Generate in the `.dapr/deploy` folder of each app in the multi-app run template -- Are used to deploy the applications in a dev/test environment in K8s +The files `service.yaml` and `deployment.yaml` are used to deploy the applications in `default` namespace in Kubernetes. This feature is specifically targeted only for running multiple apps in a dev/test environment in Kubernetes. You can name the template file with any preferred name other than the default. For example: From 1f48c689de2b400ac78d8d3c083334a8792f25a6 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 20 Sep 2023 18:08:21 -0400 Subject: [PATCH 053/162] endgame updates for 1.12 Signed-off-by: Hannah Hunter --- .github/workflows/website-root.yml | 4 ++-- README.md | 4 ++-- daprdocs/config.toml | 17 ++++++++++------- .../support/support-release-policy.md | 10 ++++++---- .../layouts/shortcodes/dapr-latest-version.html | 2 +- 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/.github/workflows/website-root.yml b/.github/workflows/website-root.yml index 1f8e503e4c2..17989accb7d 100644 --- a/.github/workflows/website-root.yml +++ b/.github/workflows/website-root.yml @@ -4,11 +4,11 @@ on: workflow_dispatch: push: branches: - - v1.11 + - v1.12 pull_request: types: [opened, synchronize, reopened, closed] branches: - - v1.11 + - v1.12 concurrency: # Cancel the previously triggered build for only PR build. diff --git a/README.md b/README.md index 11ec2756e4d..a189c74f09e 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,8 @@ The following branches are currently maintained: | Branch | Website | Description | | ------------------------------------------------------------ | -------------------------- | ------------------------------------------------------------------------------------------------ | -| [v1.11](https://github.com/dapr/docs) (primary) | https://docs.dapr.io | Latest Dapr release documentation. Typo fixes, clarifications, and most documentation goes here. | -| [v1.12](https://github.com/dapr/docs/tree/v1.12) (pre-release) | https://v1-12.docs.dapr.io/ | Pre-release documentation. Doc updates that are only applicable to v1.12+ go here. | +| [v1.12](https://github.com/dapr/docs) (primary) | https://docs.dapr.io | Latest Dapr release documentation. Typo fixes, clarifications, and most documentation goes here. | +| [v1.13](https://github.com/dapr/docs/tree/v1.13) (pre-release) | https://v1-13.docs.dapr.io/ | Pre-release documentation. Doc updates that are only applicable to v1.13+ go here. | For more information visit the [Dapr branch structure](https://docs.dapr.io/contributing/docs-contrib/contributing-docs/#branch-guidance) document. diff --git a/daprdocs/config.toml b/daprdocs/config.toml index d28410be666..5b500e5e12f 100644 --- a/daprdocs/config.toml +++ b/daprdocs/config.toml @@ -1,5 +1,5 @@ # Site Configuration -baseURL = "https://v1-12.docs.dapr.io" +baseURL = "https://docs.dapr.io" title = "Dapr Docs" theme = "docsy" disableFastRender = true @@ -168,20 +168,23 @@ offlineSearch = false github_repo = "https://github.com/dapr/docs" github_project_repo = "https://github.com/dapr/dapr" github_subdir = "daprdocs" -github_branch = "v1.11" +github_branch = "v1.12" # Versioning -version_menu = "v1.11 (latest)" -version = "v1.11" +version_menu = "v1.12 (latest)" +version = "v1.12" archived_version = false url_latest_version = "https://docs.dapr.io" [[params.versions]] - version = "v1.12 (preview)" - url = "#" + version = "v1.13 (preview)" + url = "https://v1-13.docs.dapr.io" [[params.versions]] - version = "v1.11 (latest)" + version = "v1.12 (latest)" url = "https://docs.dapr.io" +[[params.versions]] + version = "v1.11" + url = "https://v1-11.docs.dapr.io" [[params.versions]] version = "v1.10" url = "https://v1-10.docs.dapr.io" diff --git a/daprdocs/content/en/operations/support/support-release-policy.md b/daprdocs/content/en/operations/support/support-release-policy.md index 915042374eb..dc3f8d080eb 100644 --- a/daprdocs/content/en/operations/support/support-release-policy.md +++ b/daprdocs/content/en/operations/support/support-release-policy.md @@ -45,10 +45,11 @@ The table below shows the versions of Dapr releases that have been tested togeth | Release date | Runtime | CLI | SDKs | Dashboard | Status | Release notes | |--------------------|:--------:|:--------|---------|---------|---------|------------| -| August 31st 2023 | 1.11.3
| 1.11.0 | Java 1.9.0
Go 1.8.0
PHP 1.1.0
Python 1.10.0
.NET 1.11.0
JS 3.1.0 | 0.13.0 | Supported (current) | [v1.11.3 release notes](https://github.com/dapr/dapr/releases/tag/v1.11.3) | -| July 20th 2023 | 1.11.2
| 1.11.0 | Java 1.9.0
Go 1.8.0
PHP 1.1.0
Python 1.10.0
.NET 1.11.0
JS 3.1.0 | 0.13.0 | Supported (current) | [v1.11.2 release notes](https://github.com/dapr/dapr/releases/tag/v1.11.2) | -| June 22nd 2023 | 1.11.1
| 1.11.0 | Java 1.9.0
Go 1.8.0
PHP 1.1.0
Python 1.10.0
.NET 1.11.0
JS 3.1.0 | 0.13.0 | Supported (current) | [v1.11.1 release notes](https://github.com/dapr/dapr/releases/tag/v1.11.1) | -| June 12th 2023 | 1.11.0
| 1.11.0 | Java 1.9.0
Go 1.8.0
PHP 1.1.0
Python 1.10.0
.NET 1.11.0
JS 3.1.0 | 0.13.0 | Supported (current) | [v1.11.0 release notes](https://github.com/dapr/dapr/releases/tag/v1.11.0) | +| September 26th 2023 | 1.12.0
| 1.12.0 | Java 1.9.0
Go 1.8.0
PHP 1.1.0
Python 1.10.0
.NET 1.11.0
JS 3.1.0 | 0.13.0 | Supported (current) | [v1.12.0 release notes](https://github.com/dapr/dapr/releases/tag/v1.12.0) | +| August 31st 2023 | 1.11.3
| 1.11.0 | Java 1.9.0
Go 1.8.0
PHP 1.1.0
Python 1.10.0
.NET 1.11.0
JS 3.1.0 | 0.13.0 | Supported | [v1.11.3 release notes](https://github.com/dapr/dapr/releases/tag/v1.11.3) | +| July 20th 2023 | 1.11.2
| 1.11.0 | Java 1.9.0
Go 1.8.0
PHP 1.1.0
Python 1.10.0
.NET 1.11.0
JS 3.1.0 | 0.13.0 | Supported | [v1.11.2 release notes](https://github.com/dapr/dapr/releases/tag/v1.11.2) | +| June 22nd 2023 | 1.11.1
| 1.11.0 | Java 1.9.0
Go 1.8.0
PHP 1.1.0
Python 1.10.0
.NET 1.11.0
JS 3.1.0 | 0.13.0 | Supported | [v1.11.1 release notes](https://github.com/dapr/dapr/releases/tag/v1.11.1) | +| June 12th 2023 | 1.11.0
| 1.11.0 | Java 1.9.0
Go 1.8.0
PHP 1.1.0
Python 1.10.0
.NET 1.11.0
JS 3.1.0 | 0.13.0 | Supported | [v1.11.0 release notes](https://github.com/dapr/dapr/releases/tag/v1.11.0) | | July 20th 2023 | 1.10.9
| 1.10.0 | Java 1.8.0
Go 1.7.0
PHP 1.1.0
Python 1.9.0
.NET 1.10.0
JS 3.0.0 | 0.11.0 | Supported | [v1.10.9 release notes](https://github.com/dapr/dapr/releases/tag/v1.10.9) | | June 22nd 2023 | 1.10.8
| 1.10.0 | Java 1.8.0
Go 1.7.0
PHP 1.1.0
Python 1.9.0
.NET 1.10.0
JS 3.0.0 | 0.11.0 | Supported | [v1.10.8 release notes](https://github.com/dapr/dapr/releases/tag/v1.10.8) | | May 15th 2023 | 1.10.7
| 1.10.0 | Java 1.8.0
Go 1.7.0
PHP 1.1.0
Python 1.9.0
.NET 1.10.0
JS 3.0.0 | 0.11.0 | Supported | | @@ -123,6 +124,7 @@ General guidance on upgrading can be found for [self hosted mode]({{< ref self-h | 1.9.0 | N/A | 1.9.6 | | 1.10.0 | N/A | 1.10.8 | | 1.11.0 | N/A | 1.11.3 | +| 1.12.0 | N/A | 1.12.0 | ## Upgrade on Hosting platforms diff --git a/daprdocs/layouts/shortcodes/dapr-latest-version.html b/daprdocs/layouts/shortcodes/dapr-latest-version.html index 9b4bf780551..109d34c73d2 100644 --- a/daprdocs/layouts/shortcodes/dapr-latest-version.html +++ b/daprdocs/layouts/shortcodes/dapr-latest-version.html @@ -1 +1 @@ -{{- if .Get "short" }}1.11{{ else if .Get "long" }}1.11.3{{ else if .Get "cli" }}1.11.0{{ else }}1.11.3{{ end -}} +{{- if .Get "short" }}1.12{{ else if .Get "long" }}1.12.0{{ else if .Get "cli" }}1.12.0{{ else }}1.12.0{{ end -}} From b06d0f28451adf785fdeeef2ee76c0ac6b184cdc Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Thu, 21 Sep 2023 07:52:57 -0400 Subject: [PATCH 054/162] removing some typos Signed-off-by: Hannah Hunter --- .../getting-started/quickstarts/workflow-quickstart.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md index a8d3f7c883c..2ac77f77146 100644 --- a/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md @@ -101,12 +101,6 @@ Expected output: ### (Optional) Step 4: View in Zipkin -If you have Zipkin configured for Dapr locally on your machine, you can view the workflow trace spans in the Zipkin web UI (typically at `http://localhost:9411/zipkin/`). - -### (Optional) Step 4: View in Zipkin - -If you have Zipkin configured for Dapr locally on your machine, you can - Running `dapr init` launches the [openzipkin/zipkin](https://hub.docker.com/r/openzipkin/zipkin/) Docker container. If the container has stopped running, launch the Zipkin Docker container with the following command: ``` @@ -349,8 +343,6 @@ Expected output: ### (Optional) Step 4: View in Zipkin -If you have Zipkin configured for Dapr locally on your machine, you can - Running `dapr init` launches the [openzipkin/zipkin](https://hub.docker.com/r/openzipkin/zipkin/) Docker container. If the container has stopped running, launch the Zipkin Docker container with the following command: ``` @@ -610,8 +602,6 @@ Expected output: ### (Optional) Step 4: View in Zipkin -If you have Zipkin configured for Dapr locally on your machine, you can - Running `dapr init` launches the [openzipkin/zipkin](https://hub.docker.com/r/openzipkin/zipkin/) Docker container. If the container has stopped running, launch the Zipkin Docker container with the following command: ``` From 3706c74591200cfc5a8b6c5b1dcabe60127d0353 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Thu, 21 Sep 2023 11:10:13 -0400 Subject: [PATCH 055/162] add multi-app run Signed-off-by: Hannah Hunter --- .../quickstarts/pubsub-quickstart.md | 755 ++++++++++++++++++ .../serviceinvocation-quickstart.md | 674 +++++++++++++++- .../quickstarts/statemanagement-quickstart.md | 684 +++++++++++++++- 3 files changed, 2111 insertions(+), 2 deletions(-) diff --git a/daprdocs/content/en/getting-started/quickstarts/pubsub-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/pubsub-quickstart.md index cb6096c519b..96dbd8bdc44 100644 --- a/daprdocs/content/en/getting-started/quickstarts/pubsub-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/pubsub-quickstart.md @@ -14,6 +14,761 @@ Let's take a look at Dapr's [Publish and Subscribe (Pub/sub) building block]({{< +You can try out this pub/sub quickstart by either: + +- [Running all applications in this sample simultaneously with the Multi-App Run template file]({{< ref "#run-using-multi-app-run" >}}), or +- [Running one application at a time]({{< ref "#run-one-application-at-a-time" >}}) + +## Run using Multi-App Run + +{{% alert title="Note" color="primary" %}} + [Multi-App Run]({{< ref multi-app-dapr-run >}}) is currently a preview feature only supported in Linux/MacOS. +{{% /alert %}} + +Select your preferred language-specific Dapr SDK before proceeding with the Quickstart. + +{{< tabs "Python" "JavaScript" ".NET" "Java" "Go" >}} + +{{% codetab %}} + +### Step 1: Pre-requisites + +For this example, you will need: + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). +- [Python 3.7+ installed](https://www.python.org/downloads/). + +- [Docker Desktop](https://www.docker.com/products/docker-desktop) + + +### Step 2: Set up the environment + +Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/pub_sub). + +```bash +git clone https://github.com/dapr/quickstarts.git +``` + +From the root of the Quickstarts directory, navigate into the pub/sub directory: + +```bash +cd pub_sub/python/sdk +``` + +### Step 3: Run the publisher and subscriber + +With the following command, simultaneously run the following services alongside their own Dapr sidecars: +- The `order-processor` subscriber +- The `checkout` publisher + +```bash +dapr run -f . +``` + +**Expected output** + +``` +== APP - checkout-sdk == Published data: Order { OrderId = 1 } +== APP - order-processor == Subscriber received : Order { OrderId = 1 } +== APP - checkout-sdk == Published data: Order { OrderId = 2 } +== APP - order-processor == Subscriber received : Order { OrderId = 2 } +== APP - checkout-sdk == Published data: Order { OrderId = 3 } +== APP - order-processor == Subscriber received : Order { OrderId = 3 } +== APP - checkout-sdk == Published data: Order { OrderId = 4 } +== APP - order-processor == Subscriber received : Order { OrderId = 4 } +== APP - checkout-sdk == Published data: Order { OrderId = 5 } +== APP - order-processor == Subscriber received : Order { OrderId = 5 } +== APP - checkout-sdk == Published data: Order { OrderId = 6 } +== APP - order-processor == Subscriber received : Order { OrderId = 6 } +== APP - checkout-sdk == Published data: Order { OrderId = 7 } +== APP - order-processor == Subscriber received : Order { OrderId = 7 } +== APP - checkout-sdk == Published data: Order { OrderId = 8 } +== APP - order-processor == Subscriber received : Order { OrderId = 8 } +== APP - checkout-sdk == Published data: Order { OrderId = 9 } +== APP - order-processor == Subscriber received : Order { OrderId = 9 } +== APP - checkout-sdk == Published data: Order { OrderId = 10 } +== APP - order-processor == Subscriber received : Order { OrderId = 10 } +Exited App successfully +``` + +### What happened? + +When you ran `dapr init` during Dapr install, the following YAML files were generated in the `.dapr/components` directory: +- [`dapr.yaml` Multi-App Run template file]({{< ref "#dapryaml-multi-app-run-template-file" >}}) +- [`pubsub.yaml` component file]({{< ref "#pubsubyaml-component-file" >}}) + +Running `dapr run -f .` in this Quickstart started both the [subscriber]({{< ref "#order-processor-subscriber" >}}) and [publisher]({{< ref "#checkout-publisher" >}}) applications. + +#### `dapr.yaml` Multi-App Run template file + +Running the [Multi-App Run template file]({{< ref multi-app-dapr-run >}}) with `dapr run -f .` starts all applications in your project. In this Quickstart, the `dapr.yaml` file contains the following: + +```yml +version: 1 +common: + resourcesPath: ../../components/ +apps: + - appID: order-processor-sdk + appDirPath: ./order-processor/ + appPort: 6001 + command: ["uvicorn", "app:app"] + - appID: checkout-sdk + appDirPath: ./checkout/ + command: ["python3", "app.py"] +``` + +#### `pubsub.yaml` component file + +With the `pubsub.yaml` component, you can easily swap out underlying components without application code changes. + +The Redis `pubsub.yaml` file included for this Quickstart contains the following: + +```yaml +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: orderpubsub +spec: + type: pubsub.redis + version: v1 + metadata: + - name: redisHost + value: localhost:6379 + - name: redisPassword + value: "" +``` + +In the component YAML file: + +- `metadata/name` is how your application talks to the component. +- `spec/metadata` defines the connection to the instance of the component. +- `scopes` specify which application can use the component. + +#### `order-processor` subscriber + +In the `order-processor` subscriber, you subscribe to the Redis instance called `orderpubsub` [(as defined in the `pubsub.yaml` component)]({{< ref "#pubsubyaml-component-file" >}}) and topic `orders`. This enables your app code to talk to the Redis component instance through the Dapr sidecar. + +```py +# Register Dapr pub/sub subscriptions +@app.route('/dapr/subscribe', methods=['GET']) +def subscribe(): + subscriptions = [{ + 'pubsubname': 'orderpubsub', + 'topic': 'orders', + 'route': 'orders' + }] + print('Dapr pub/sub is subscribed to: ' + json.dumps(subscriptions)) + return jsonify(subscriptions) + + +# Dapr subscription in /dapr/subscribe sets up this route +@app.route('/orders', methods=['POST']) +def orders_subscriber(): + event = from_http(request.headers, request.get_data()) + print('Subscriber received : ' + event.data['orderid'], flush=True) + return json.dumps({'success': True}), 200, { + 'ContentType': 'application/json'} + + +app.run(port=5001) +``` + +#### `checkout` publisher + +In the `checkout` publisher, you publish the orderId message to the Redis instance called `orderpubsub` [(as defined in the `pubsub.yaml` component)]({{< ref "#pubsubyaml-component-file" >}}) and topic `orders`. As soon as the service starts, it publishes in a loop: + +```python +with DaprClient() as client: + # Publish an event/message using Dapr PubSub + result = client.publish_event( + pubsub_name='orderpubsub', + topic_name='orders', + data=json.dumps(order), + data_content_type='application/json', + ) +``` + +{{% /codetab %}} + + +{{% codetab %}} + +### Step 1: Pre-requisites + +For this example, you will need: + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). +- [Latest Node.js installed](https://nodejs.org/download/). + +- [Docker Desktop](https://www.docker.com/products/docker-desktop) + + +### Step 2: Set up the environment + +Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/pub_sub). + +```bash +git clone https://github.com/dapr/quickstarts.git +``` + +From the root of the Quickstarts directory, navigate into the pub/sub directory: + +```bash +cd pub_sub/javascript/sdk +``` + +### Step 3: Run the publisher and subscriber + +With the following command, simultaneously run the following services alongside their own Dapr sidecars: +- The `order-processor` subscriber +- The `checkout` publisher + +```bash +dapr run -f . +``` + +**Expected output** + +``` +== APP - checkout-sdk == Published data: Order { OrderId = 1 } +== APP - order-processor == Subscriber received : Order { OrderId = 1 } +== APP - checkout-sdk == Published data: Order { OrderId = 2 } +== APP - order-processor == Subscriber received : Order { OrderId = 2 } +== APP - checkout-sdk == Published data: Order { OrderId = 3 } +== APP - order-processor == Subscriber received : Order { OrderId = 3 } +== APP - checkout-sdk == Published data: Order { OrderId = 4 } +== APP - order-processor == Subscriber received : Order { OrderId = 4 } +== APP - checkout-sdk == Published data: Order { OrderId = 5 } +== APP - order-processor == Subscriber received : Order { OrderId = 5 } +== APP - checkout-sdk == Published data: Order { OrderId = 6 } +== APP - order-processor == Subscriber received : Order { OrderId = 6 } +== APP - checkout-sdk == Published data: Order { OrderId = 7 } +== APP - order-processor == Subscriber received : Order { OrderId = 7 } +== APP - checkout-sdk == Published data: Order { OrderId = 8 } +== APP - order-processor == Subscriber received : Order { OrderId = 8 } +== APP - checkout-sdk == Published data: Order { OrderId = 9 } +== APP - order-processor == Subscriber received : Order { OrderId = 9 } +== APP - checkout-sdk == Published data: Order { OrderId = 10 } +== APP - order-processor == Subscriber received : Order { OrderId = 10 } +Exited App successfully +``` + +### What happened? + +When you ran `dapr init` during Dapr install, the following YAML files were generated in the `.dapr/components` directory: +- [`dapr.yaml` Multi-App Run template file]({{< ref "#dapryaml-multi-app-run-template-file" >}}) +- [`pubsub.yaml` component file]({{< ref "#pubsubyaml-component-file" >}}) + +Running `dapr run -f .` in this Quickstart started both the [subscriber]({{< ref "#order-processor-subscriber" >}}) and [publisher]({{< ref "#checkout-publisher" >}}) applications. + +#### `dapr.yaml` Multi-App Run template file + +Running the [Multi-App Run template file]({{< ref multi-app-dapr-run >}}) with `dapr run -f .` starts all applications in your project. In this Quickstart, the `dapr.yaml` file contains the following: + +```yml +version: 1 +common: + resourcesPath: ../../components/ +apps: + - appID: order-processor + appDirPath: ./order-processor/ + appPort: 5002 + command: ["npm", "run", "start"] + - appID: checkout-sdk + appDirPath: ./checkout/ + command: ["npm", "run", "start"] +``` + +#### `pubsub.yaml` component file + +With the `pubsub.yaml` component, you can easily swap out underlying components without application code changes. + +The Redis `pubsub.yaml` file included for this Quickstart contains the following: + +```yaml +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: orderpubsub +spec: + type: pubsub.redis + version: v1 + metadata: + - name: redisHost + value: localhost:6379 + - name: redisPassword + value: "" +``` + +In the component YAML file: + +- `metadata/name` is how your application talks to the component. +- `spec/metadata` defines the connection to the instance of the component. +- `scopes` specify which application can use the component. + +#### `order-processor` subscriber + +In the `order-processor` subscriber, you subscribe to the Redis instance called `orderpubsub` [(as defined in the `pubsub.yaml` component)]({{< ref "#pubsubyaml-component-file" >}}) and topic `orders`. This enables your app code to talk to the Redis component instance through the Dapr sidecar. + +```js +server.pubsub.subscribe("orderpubsub", "orders", (data) => console.log("Subscriber received: " + JSON.stringify(data))); +``` + +#### `checkout` publisher + +In the `checkout` publisher service, you publish the orderId message to the Redis instance called `orderpubsub` [(as defined in the `pubsub.yaml` component)]({{< ref "#pubsubyaml-component-file" >}}) and topic `orders`. As soon as the service starts, it publishes in a loop: + +```js +const client = new DaprClient(); + +await client.pubsub.publish(PUBSUB_NAME, PUBSUB_TOPIC, order); +console.log("Published data: " + JSON.stringify(order)); +``` + +{{% /codetab %}} + + +{{% codetab %}} + +### Step 1: Pre-requisites + +For this example, you will need: + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). +- [.NET SDK or .NET 6 SDK installed](https://dotnet.microsoft.com/download). + +- [Docker Desktop](https://www.docker.com/products/docker-desktop) + + +### Step 2: Set up the environment + +Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/pub_sub). + +```bash +git clone https://github.com/dapr/quickstarts.git +``` + +From the root of the Quickstarts directory, navigate into the pub/sub directory: + +```bash +cd pub_sub/csharp/sdk +``` + +### Step 3: Run the publisher and subscriber + +With the following command, simultaneously run the following services alongside their own Dapr sidecars: +- The `order-processor` subscriber +- The `checkout` publisher + +```bash +dapr run -f . +``` + +**Expected output** + +``` +== APP - checkout-sdk == Published data: Order { OrderId = 1 } +== APP - order-processor == Subscriber received : Order { OrderId = 1 } +== APP - checkout-sdk == Published data: Order { OrderId = 2 } +== APP - order-processor == Subscriber received : Order { OrderId = 2 } +== APP - checkout-sdk == Published data: Order { OrderId = 3 } +== APP - order-processor == Subscriber received : Order { OrderId = 3 } +== APP - checkout-sdk == Published data: Order { OrderId = 4 } +== APP - order-processor == Subscriber received : Order { OrderId = 4 } +== APP - checkout-sdk == Published data: Order { OrderId = 5 } +== APP - order-processor == Subscriber received : Order { OrderId = 5 } +== APP - checkout-sdk == Published data: Order { OrderId = 6 } +== APP - order-processor == Subscriber received : Order { OrderId = 6 } +== APP - checkout-sdk == Published data: Order { OrderId = 7 } +== APP - order-processor == Subscriber received : Order { OrderId = 7 } +== APP - checkout-sdk == Published data: Order { OrderId = 8 } +== APP - order-processor == Subscriber received : Order { OrderId = 8 } +== APP - checkout-sdk == Published data: Order { OrderId = 9 } +== APP - order-processor == Subscriber received : Order { OrderId = 9 } +== APP - checkout-sdk == Published data: Order { OrderId = 10 } +== APP - order-processor == Subscriber received : Order { OrderId = 10 } +Exited App successfully +``` + +### What happened? + +When you ran `dapr init` during Dapr install, the following YAML files were generated in the `.dapr/components` directory: +- [`dapr.yaml` Multi-App Run template file]({{< ref "#dapryaml-multi-app-run-template-file" >}}) +- [`pubsub.yaml` component file]({{< ref "#pubsubyaml-component-file" >}}) + +Running `dapr run -f .` in this Quickstart started both the [subscriber]({{< ref "#order-processor-subscriber" >}}) and [publisher]({{< ref "#checkout-publisher" >}}) applications. + +#### `dapr.yaml` Multi-App Run template file + +Running the [Multi-App Run template file]({{< ref multi-app-dapr-run >}}) with `dapr run -f .` starts all applications in your project. In this Quickstart, the `dapr.yaml` file contains the following: + +```yml +version: 1 +common: + resourcesPath: ../../components/ +apps: + - appID: order-processor + appDirPath: ./order-processor/ + appPort: 7006 + command: ["dotnet", "run"] + - appID: checkout-sdk + appDirPath: ./checkout/ + command: ["dotnet", "run"] +``` + +#### `pubsub.yaml` component file + +With the `pubsub.yaml` component, you can easily swap out underlying components without application code changes. + +The Redis `pubsub.yaml` file included for this Quickstart contains the following: + +```yaml +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: orderpubsub +spec: + type: pubsub.redis + version: v1 + metadata: + - name: redisHost + value: localhost:6379 + - name: redisPassword + value: "" +``` + +In the component YAML file: + +- `metadata/name` is how your application talks to the component. +- `spec/metadata` defines the connection to the instance of the component. +- `scopes` specify which application can use the component. + +#### `order-processor` subscriber + +In the `order-processor` subscriber, you subscribe to the Redis instance called `orderpubsub` [(as defined in the `pubsub.yaml` component)]({{< ref "#pubsubyaml-component-file" >}}) and topic `orders`. This enables your app code to talk to the Redis component instance through the Dapr sidecar. + +```cs +// Dapr subscription in [Topic] routes orders topic to this route +app.MapPost("/orders", [Topic("orderpubsub", "orders")] (Order order) => { + Console.WriteLine("Subscriber received : " + order); + return Results.Ok(order); +}); + +public record Order([property: JsonPropertyName("orderId")] int OrderId); +``` + +#### `checkout` publisher + +In the `checkout` publisher, you publish the orderId message to the Redis instance called `orderpubsub` [(as defined in the `pubsub.yaml` component)]({{< ref "#pubsubyaml-component-file" >}}) and topic `orders`. As soon as the service starts, it publishes in a loop: + +```cs +using var client = new DaprClientBuilder().Build(); +await client.PublishEventAsync("orderpubsub", "orders", order); +Console.WriteLine("Published data: " + order); +``` + +{{% /codetab %}} + + +{{% codetab %}} + +### Step 1: Pre-requisites + +For this example, you will need: + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). +- Java JDK 11 (or greater): + - [Oracle JDK](https://www.oracle.com/java/technologies/downloads), or + - OpenJDK +- [Apache Maven](https://maven.apache.org/install.html), version 3.x. + +- [Docker Desktop](https://www.docker.com/products/docker-desktop) + + +### Step 2: Set up the environment + +Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/pub_sub). + +```bash +git clone https://github.com/dapr/quickstarts.git +``` + +From the root of the Quickstarts directory, navigate into the pub/sub directory: + +```bash +cd pub_sub/java/sdk +``` + +### Step 3: Run the publisher and subscriber + +With the following command, simultaneously run the following services alongside their own Dapr sidecars: +- The `order-processor` subscriber +- The `checkout` publisher + +```bash +dapr run -f . +``` + +**Expected output** + +``` +== APP - checkout-sdk == Published data: Order { OrderId = 1 } +== APP - order-processor == Subscriber received : Order { OrderId = 1 } +== APP - checkout-sdk == Published data: Order { OrderId = 2 } +== APP - order-processor == Subscriber received : Order { OrderId = 2 } +== APP - checkout-sdk == Published data: Order { OrderId = 3 } +== APP - order-processor == Subscriber received : Order { OrderId = 3 } +== APP - checkout-sdk == Published data: Order { OrderId = 4 } +== APP - order-processor == Subscriber received : Order { OrderId = 4 } +== APP - checkout-sdk == Published data: Order { OrderId = 5 } +== APP - order-processor == Subscriber received : Order { OrderId = 5 } +== APP - checkout-sdk == Published data: Order { OrderId = 6 } +== APP - order-processor == Subscriber received : Order { OrderId = 6 } +== APP - checkout-sdk == Published data: Order { OrderId = 7 } +== APP - order-processor == Subscriber received : Order { OrderId = 7 } +== APP - checkout-sdk == Published data: Order { OrderId = 8 } +== APP - order-processor == Subscriber received : Order { OrderId = 8 } +== APP - checkout-sdk == Published data: Order { OrderId = 9 } +== APP - order-processor == Subscriber received : Order { OrderId = 9 } +== APP - checkout-sdk == Published data: Order { OrderId = 10 } +== APP - order-processor == Subscriber received : Order { OrderId = 10 } +Exited App successfully +``` + +### What happened? + +When you ran `dapr init` during Dapr install, the following YAML files were generated in the `.dapr/components` directory: +- [`dapr.yaml` Multi-App Run template file]({{< ref "#dapryaml-multi-app-run-template-file" >}}) +- [`pubsub.yaml` component file]({{< ref "#pubsubyaml-component-file" >}}) + +Running `dapr run -f .` in this Quickstart started both the [subscriber]({{< ref "#order-processor-subscriber" >}}) and [publisher]({{< ref "#checkout-publisher" >}}) applications. + +#### `dapr.yaml` Multi-App Run template file + +Running the [Multi-App Run template file]({{< ref multi-app-dapr-run >}}) with `dapr run -f .` starts all applications in your project. In this Quickstart, the `dapr.yaml` file contains the following: + +```yml +version: 1 +common: + resourcesPath: ../../components/ +apps: + - appID: order-processor-sdk + appDirPath: ./order-processor/target/ + appPort: 8080 + command: ["java", "-jar", "OrderProcessingService-0.0.1-SNAPSHOT.jar"] + - appID: checkout-sdk + appDirPath: ./checkout/target/ + command: ["java", "-jar", "CheckoutService-0.0.1-SNAPSHOT.jar"] +``` + +#### `pubsub.yaml` component file + +With the `pubsub.yaml` component, you can easily swap out underlying components without application code changes. + +The Redis `pubsub.yaml` file included for this Quickstart contains the following: + +```yaml +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: orderpubsub +spec: + type: pubsub.redis + version: v1 + metadata: + - name: redisHost + value: localhost:6379 + - name: redisPassword + value: "" +``` + +In the component YAML file: + +- `metadata/name` is how your application talks to the component. +- `spec/metadata` defines the connection to the instance of the component. +- `scopes` specify which application can use the component. + +#### `order-processor` subscriber + +In the `order-processor` subscriber, you subscribe to the Redis instance called `orderpubsub` [(as defined in the `pubsub.yaml` component)]({{< ref "#pubsubyaml-component-file" >}}) and topic `orders`. This enables your app code to talk to the Redis component instance through the Dapr sidecar. + +```java +@Topic(name = "orders", pubsubName = "orderpubsub") +@PostMapping(path = "/orders", consumes = MediaType.ALL_VALUE) +public Mono getCheckout(@RequestBody(required = false) CloudEvent cloudEvent) { + return Mono.fromSupplier(() -> { + try { + logger.info("Subscriber received: " + cloudEvent.getData().getOrderId()); + return ResponseEntity.ok("SUCCESS"); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); +} +``` + +#### `checkout` publisher + +In the `checkout` publisher, you publish the orderId message to the Redis instance called `orderpubsub` [(as defined in the `pubsub.yaml` component)]({{< ref "#pubsubyaml-component-file" >}}) and topic `orders`. As soon as the service starts, it publishes in a loop: + +```java +DaprClient client = new DaprClientBuilder().build(); +client.publishEvent( + PUBSUB_NAME, + TOPIC_NAME, + order).block(); +logger.info("Published data: " + order.getOrderId()); +``` + +{{% /codetab %}} + + +{{% codetab %}} + +### Step 1: Pre-requisites + +For this example, you will need: + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). +- [Latest version of Go](https://go.dev/dl/). + +- [Docker Desktop](https://www.docker.com/products/docker-desktop) + + +### Step 2: Set up the environment + +Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/pub_sub). + +```bash +git clone https://github.com/dapr/quickstarts.git +``` + +From the root of the Quickstarts directory, navigate into the pub/sub directory: + +```bash +cd pub_sub/go/sdk +``` + +### Step 3: Run the publisher and subscriber + +With the following command, simultaneously run the following services alongside their own Dapr sidecars: +- The `order-processor` subscriber +- The `checkout` publisher + +```bash +dapr run -f . +``` + +**Expected output** + +``` +== APP - checkout-sdk == Published data: Order { OrderId = 1 } +== APP - order-processor == Subscriber received : Order { OrderId = 1 } +== APP - checkout-sdk == Published data: Order { OrderId = 2 } +== APP - order-processor == Subscriber received : Order { OrderId = 2 } +== APP - checkout-sdk == Published data: Order { OrderId = 3 } +== APP - order-processor == Subscriber received : Order { OrderId = 3 } +== APP - checkout-sdk == Published data: Order { OrderId = 4 } +== APP - order-processor == Subscriber received : Order { OrderId = 4 } +== APP - checkout-sdk == Published data: Order { OrderId = 5 } +== APP - order-processor == Subscriber received : Order { OrderId = 5 } +== APP - checkout-sdk == Published data: Order { OrderId = 6 } +== APP - order-processor == Subscriber received : Order { OrderId = 6 } +== APP - checkout-sdk == Published data: Order { OrderId = 7 } +== APP - order-processor == Subscriber received : Order { OrderId = 7 } +== APP - checkout-sdk == Published data: Order { OrderId = 8 } +== APP - order-processor == Subscriber received : Order { OrderId = 8 } +== APP - checkout-sdk == Published data: Order { OrderId = 9 } +== APP - order-processor == Subscriber received : Order { OrderId = 9 } +== APP - checkout-sdk == Published data: Order { OrderId = 10 } +== APP - order-processor == Subscriber received : Order { OrderId = 10 } +Exited App successfully +``` + +### What happened? + +When you ran `dapr init` during Dapr install, the following YAML files were generated in the `.dapr/components` directory: +- [`dapr.yaml` Multi-App Run template file]({{< ref "#dapryaml-multi-app-run-template-file" >}}) +- [`pubsub.yaml` component file]({{< ref "#pubsubyaml-component-file" >}}) + +Running `dapr run -f .` in this Quickstart started both the [subscriber]({{< ref "#order-processor-subscriber" >}}) and [publisher]({{< ref "#checkout-publisher" >}}) applications. + +#### `dapr.yaml` Multi-App Run template file + +Running the [Multi-App Run template file]({{< ref multi-app-dapr-run >}}) with `dapr run -f .` starts all applications in your project. In this Quickstart, the `dapr.yaml` file contains the following: + +```yml +version: 1 +common: + resourcesPath: ../../components/ +apps: + - appID: order-processor + appDirPath: ./order-processor/ + appPort: 6005 + command: ["go", "run", "."] + - appID: checkout-sdk + appDirPath: ./checkout/ + command: ["go", "run", "."] +``` + +#### `pubsub.yaml` component file + +With the `pubsub.yaml` component, you can easily swap out underlying components without application code changes. + +The Redis `pubsub.yaml` file included for this Quickstart contains the following: + +```yaml +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: orderpubsub +spec: + type: pubsub.redis + version: v1 + metadata: + - name: redisHost + value: localhost:6379 + - name: redisPassword + value: "" +``` + +In the component YAML file: + +- `metadata/name` is how your application talks to the component. +- `spec/metadata` defines the connection to the instance of the component. +- `scopes` specify which application can use the component. + +#### `order-processor` subscriber + +In the `order-processor` subscriber, you subscribe to the Redis instance called `orderpubsub` [(as defined in the `pubsub.yaml` component)]({{< ref "#pubsubyaml-component-file" >}}) and topic `orders`. This enables your app code to talk to the Redis component instance through the Dapr sidecar. + +```go +func eventHandler(ctx context.Context, e *common.TopicEvent) (retry bool, err error) { + fmt.Println("Subscriber received: ", e.Data) + return false, nil +} +``` + +#### `checkout` publisher + +In the `checkout` publisher, you publish the orderId message to the Redis instance called `orderpubsub` [(as defined in the `pubsub.yaml` component)]({{< ref "#pubsubyaml-component-file" >}}) and topic `orders`. As soon as the service starts, it publishes in a loop: + +```go +client, err := dapr.NewClient() + +if err := client.PublishEvent(ctx, PUBSUB_NAME, PUBSUB_TOPIC, []byte(order)); err != nil { + panic(err) +} + +fmt.Println("Published data: ", order) +``` + +{{% /codetab %}} + +{{< /tabs >}} + +## Run one application at a time + Select your preferred language-specific Dapr SDK before proceeding with the Quickstart. {{< tabs "Python" "JavaScript" ".NET" "Java" "Go" >}} diff --git a/daprdocs/content/en/getting-started/quickstarts/serviceinvocation-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/serviceinvocation-quickstart.md index fc61df703cf..bd866aa2478 100644 --- a/daprdocs/content/en/getting-started/quickstarts/serviceinvocation-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/serviceinvocation-quickstart.md @@ -10,10 +10,682 @@ With [Dapr's Service Invocation building block](https://docs.dapr.io/developing- Diagram showing the steps of service invocation -Dapr offers several methods for service invocation, which you can choose depending on your scenario. For this Quickstart, you'll enable the checkout service to invoke a method using HTTP proxy in the order-processor service. +Dapr offers several methods for service invocation, which you can choose depending on your scenario. For this Quickstart, you'll enable the checkout service to invoke a method using HTTP proxy in the order-processor service and by either: +- [Running all applications in this sample simultaneously with the Multi-App Run template file]({{< ref "#run-using-multi-app-run" >}}), or +- [Running one application at a time]({{< ref "#run-one-application-at-a-time" >}}) Learn more about Dapr's methods for service invocation in the [overview article]({{< ref service-invocation-overview.md >}}). +## Run using Multi-App Run + +{{% alert title="Note" color="primary" %}} + [Multi-App Run]({{< ref multi-app-dapr-run >}}) is currently a preview feature only supported in Linux/MacOS. +{{% /alert %}} + +Select your preferred language before proceeding with the Quickstart. + +{{< tabs "Python" "JavaScript" ".NET" "Java" "Go" >}} + +{{% codetab %}} + +### Step 1: Pre-requisites + +For this example, you will need: + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). +- [Python 3.7+ installed](https://www.python.org/downloads/). + +- [Docker Desktop](https://www.docker.com/products/docker-desktop) + + +### Step 2: Set up the environment + +Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/service_invocation). + +```bash +git clone https://github.com/dapr/quickstarts.git +``` + +From the root of the Quickstart clone directory, navigate to the quickstart directory. + +```bash +cd service_invocation/python/http +``` + +### Step 3: Run the `order-processor` and `checkout` services + +With the following command, simultaneously run the following services alongside their own Dapr sidecars: +- The `order-processor` service +- The `checkout` service + +```bash +dapr run -f . +``` + +**Expected output** + +``` +== APP - order-processor == Order received : Order { orderId = 1 } +== APP - checkout == Order passed: Order { OrderId = 1 } +== APP - order-processor == Order received : Order { orderId = 2 } +== APP - checkout == Order passed: Order { OrderId = 2 } +== APP - order-processor == Order received : Order { orderId = 3 } +== APP - checkout == Order passed: Order { OrderId = 3 } +== APP - order-processor == Order received : Order { orderId = 4 } +== APP - checkout == Order passed: Order { OrderId = 4 } +== APP - order-processor == Order received : Order { orderId = 5 } +== APP - checkout == Order passed: Order { OrderId = 5 } +== APP - order-processor == Order received : Order { orderId = 6 } +== APP - checkout == Order passed: Order { OrderId = 6 } +== APP - order-processor == Order received : Order { orderId = 7 } +== APP - checkout == Order passed: Order { OrderId = 7 } +== APP - order-processor == Order received : Order { orderId = 8 } +== APP - checkout == Order passed: Order { OrderId = 8 } +== APP - order-processor == Order received : Order { orderId = 9 } +== APP - checkout == Order passed: Order { OrderId = 9 } +== APP - order-processor == Order received : Order { orderId = 10 } +== APP - checkout == Order passed: Order { OrderId = 10 } +== APP - order-processor == Order received : Order { orderId = 11 } +== APP - checkout == Order passed: Order { OrderId = 11 } +== APP - order-processor == Order received : Order { orderId = 12 } +== APP - checkout == Order passed: Order { OrderId = 12 } +== APP - order-processor == Order received : Order { orderId = 13 } +== APP - checkout == Order passed: Order { OrderId = 13 } +== APP - order-processor == Order received : Order { orderId = 14 } +== APP - checkout == Order passed: Order { OrderId = 14 } +== APP - order-processor == Order received : Order { orderId = 15 } +== APP - checkout == Order passed: Order { OrderId = 15 } +== APP - order-processor == Order received : Order { orderId = 16 } +== APP - checkout == Order passed: Order { OrderId = 16 } +== APP - order-processor == Order received : Order { orderId = 17 } +== APP - checkout == Order passed: Order { OrderId = 17 } +== APP - order-processor == Order received : Order { orderId = 18 } +== APP - checkout == Order passed: Order { OrderId = 18 } +== APP - order-processor == Order received : Order { orderId = 19 } +== APP - checkout == Order passed: Order { OrderId = 19 } +== APP - order-processor == Order received : Order { orderId = 20 } +== APP - checkout == Order passed: Order { OrderId = 20 } +Exited App successfully +``` + +### What happened? + +Running `dapr run -f .` in this Quickstart started both the [subscriber]({{< ref "#order-processor-service" >}}) and [publisher]({{< ref "#checkout-service" >}}) applications using the `dapr.yaml` Multi-App Run template file. + +#### `dapr.yaml` Multi-App Run template file + +Running the [Multi-App Run template file]({{< ref multi-app-dapr-run >}}) with `dapr run -f .` starts all applications in your project. In this Quickstart, the `dapr.yaml` file contains the following: + +```yml +version: 1 +apps: + - appDirPath: ./order-processor/ + appID: order-processor + appPort: 8001 + command: ["python3", "app.py"] + - appID: checkout + appDirPath: ./checkout/ + command: ["python3", "app.py"] +``` + +#### `order-processor` service + +The `order-processor` service receives the call from the `checkout` service: + +```py +@app.route('/orders', methods=['POST']) +def getOrder(): + data = request.json + print('Order received : ' + json.dumps(data), flush=True) + return json.dumps({'success': True}), 200, { + 'ContentType': 'application/json'} + + +app.run(port=8001) +``` + +#### `checkout` service + +In the `checkout` service, you'll notice there's no need to rewrite your app code to use Dapr's service invocation. You can enable service invocation by simply adding the `dapr-app-id` header, which specifies the ID of the target service. + +```python +headers = {'dapr-app-id': 'order-processor'} + +result = requests.post( + url='%s/orders' % (base_url), + data=json.dumps(order), + headers=headers +) +``` + +{{% /codetab %}} + + +{{% codetab %}} + +### Step 1: Pre-requisites + +For this example, you will need: + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). +- [Latest Node.js installed](https://nodejs.org/). + +- [Docker Desktop](https://www.docker.com/products/docker-desktop) + + +### Step 2: Set up the environment + +Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/service_invocation). + +```bash +git clone https://github.com/dapr/quickstarts.git +``` + +From the root of the Quickstart clone directory, navigate to the quickstart directory. + +```bash +cd service_invocation/javascript/http +``` + +### Step 3: Run the `order-processor` and `checkout` services + +With the following command, simultaneously run the following services alongside their own Dapr sidecars: +- The `order-processor` service +- The `checkout` service + +```bash +dapr run -f . +``` + +**Expected output** + +``` +== APP - order-processor == Order received : Order { orderId = 1 } +== APP - checkout == Order passed: Order { OrderId = 1 } +== APP - order-processor == Order received : Order { orderId = 2 } +== APP - checkout == Order passed: Order { OrderId = 2 } +== APP - order-processor == Order received : Order { orderId = 3 } +== APP - checkout == Order passed: Order { OrderId = 3 } +== APP - order-processor == Order received : Order { orderId = 4 } +== APP - checkout == Order passed: Order { OrderId = 4 } +== APP - order-processor == Order received : Order { orderId = 5 } +== APP - checkout == Order passed: Order { OrderId = 5 } +== APP - order-processor == Order received : Order { orderId = 6 } +== APP - checkout == Order passed: Order { OrderId = 6 } +== APP - order-processor == Order received : Order { orderId = 7 } +== APP - checkout == Order passed: Order { OrderId = 7 } +== APP - order-processor == Order received : Order { orderId = 8 } +== APP - checkout == Order passed: Order { OrderId = 8 } +== APP - order-processor == Order received : Order { orderId = 9 } +== APP - checkout == Order passed: Order { OrderId = 9 } +== APP - order-processor == Order received : Order { orderId = 10 } +== APP - checkout == Order passed: Order { OrderId = 10 } +== APP - order-processor == Order received : Order { orderId = 11 } +== APP - checkout == Order passed: Order { OrderId = 11 } +== APP - order-processor == Order received : Order { orderId = 12 } +== APP - checkout == Order passed: Order { OrderId = 12 } +== APP - order-processor == Order received : Order { orderId = 13 } +== APP - checkout == Order passed: Order { OrderId = 13 } +== APP - order-processor == Order received : Order { orderId = 14 } +== APP - checkout == Order passed: Order { OrderId = 14 } +== APP - order-processor == Order received : Order { orderId = 15 } +== APP - checkout == Order passed: Order { OrderId = 15 } +== APP - order-processor == Order received : Order { orderId = 16 } +== APP - checkout == Order passed: Order { OrderId = 16 } +== APP - order-processor == Order received : Order { orderId = 17 } +== APP - checkout == Order passed: Order { OrderId = 17 } +== APP - order-processor == Order received : Order { orderId = 18 } +== APP - checkout == Order passed: Order { OrderId = 18 } +== APP - order-processor == Order received : Order { orderId = 19 } +== APP - checkout == Order passed: Order { OrderId = 19 } +== APP - order-processor == Order received : Order { orderId = 20 } +== APP - checkout == Order passed: Order { OrderId = 20 } +Exited App successfully +``` + +### What happened? + +Running `dapr run -f .` in this Quickstart started both the [subscriber]({{< ref "#order-processor-service" >}}) and [publisher]({{< ref "#checkout-service" >}}) applications using the `dapr.yaml` Multi-App Run template file. + +#### `dapr.yaml` Multi-App Run template file + +Running the [Multi-App Run template file]({{< ref multi-app-dapr-run >}}) with `dapr run -f .` starts all applications in your project. In this Quickstart, the `dapr.yaml` file contains the following: + +```yml +version: 1 +apps: + - appDirPath: ./order-processor/ + appID: order-processor + appPort: 5001 + command: ["npm", "start"] + - appID: checkout + appDirPath: ./checkout/ + command: ["npm", "start"] +``` + +#### `order-processor` service + +The `order-processor` service receives the call from the `checkout` service: + +```javascript +app.post('/orders', (req, res) => { + console.log("Order received:", req.body); + res.sendStatus(200); +}); +``` + +#### `checkout` service + +In the `checkout` service, you'll notice there's no need to rewrite your app code to use Dapr's service invocation. You can enable service invocation by simply adding the `dapr-app-id` header, which specifies the ID of the target service. + +```javascript +let axiosConfig = { + headers: { + "dapr-app-id": "order-processor" + } +}; +const res = await axios.post(`${DAPR_HOST}:${DAPR_HTTP_PORT}/orders`, order , axiosConfig); +console.log("Order passed: " + res.config.data); +``` + +{{% /codetab %}} + + +{{% codetab %}} + +### Step 1: Pre-requisites + +For this example, you will need: + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). +- [.NET SDK or .NET 7 SDK installed](https://dotnet.microsoft.com/download). + +- [Docker Desktop](https://www.docker.com/products/docker-desktop) + + +### Step 2: Set up the environment + +Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/service_invocation). + +```bash +git clone https://github.com/dapr/quickstarts.git +``` + +From the root of the Quickstart clone directory, navigate to the quickstart directory. + +```bash +cd service_invocation/csharp/http +``` + +### Step 3: Run the `order-processor` and `checkout` services + +With the following command, simultaneously run the following services alongside their own Dapr sidecars: +- The `order-processor` service +- The `checkout` service + +```bash +dapr run -f . +``` + +**Expected output** + +``` +== APP - order-processor == Order received : Order { orderId = 1 } +== APP - checkout == Order passed: Order { OrderId = 1 } +== APP - order-processor == Order received : Order { orderId = 2 } +== APP - checkout == Order passed: Order { OrderId = 2 } +== APP - order-processor == Order received : Order { orderId = 3 } +== APP - checkout == Order passed: Order { OrderId = 3 } +== APP - order-processor == Order received : Order { orderId = 4 } +== APP - checkout == Order passed: Order { OrderId = 4 } +== APP - order-processor == Order received : Order { orderId = 5 } +== APP - checkout == Order passed: Order { OrderId = 5 } +== APP - order-processor == Order received : Order { orderId = 6 } +== APP - checkout == Order passed: Order { OrderId = 6 } +== APP - order-processor == Order received : Order { orderId = 7 } +== APP - checkout == Order passed: Order { OrderId = 7 } +== APP - order-processor == Order received : Order { orderId = 8 } +== APP - checkout == Order passed: Order { OrderId = 8 } +== APP - order-processor == Order received : Order { orderId = 9 } +== APP - checkout == Order passed: Order { OrderId = 9 } +== APP - order-processor == Order received : Order { orderId = 10 } +== APP - checkout == Order passed: Order { OrderId = 10 } +== APP - order-processor == Order received : Order { orderId = 11 } +== APP - checkout == Order passed: Order { OrderId = 11 } +== APP - order-processor == Order received : Order { orderId = 12 } +== APP - checkout == Order passed: Order { OrderId = 12 } +== APP - order-processor == Order received : Order { orderId = 13 } +== APP - checkout == Order passed: Order { OrderId = 13 } +== APP - order-processor == Order received : Order { orderId = 14 } +== APP - checkout == Order passed: Order { OrderId = 14 } +== APP - order-processor == Order received : Order { orderId = 15 } +== APP - checkout == Order passed: Order { OrderId = 15 } +== APP - order-processor == Order received : Order { orderId = 16 } +== APP - checkout == Order passed: Order { OrderId = 16 } +== APP - order-processor == Order received : Order { orderId = 17 } +== APP - checkout == Order passed: Order { OrderId = 17 } +== APP - order-processor == Order received : Order { orderId = 18 } +== APP - checkout == Order passed: Order { OrderId = 18 } +== APP - order-processor == Order received : Order { orderId = 19 } +== APP - checkout == Order passed: Order { OrderId = 19 } +== APP - order-processor == Order received : Order { orderId = 20 } +== APP - checkout == Order passed: Order { OrderId = 20 } +Exited App successfully +``` + +### What happened? + +Running `dapr run -f .` in this Quickstart started both the [subscriber]({{< ref "#order-processor-service" >}}) and [publisher]({{< ref "#checkout-service" >}}) applications using the `dapr.yaml` Multi-App Run template file. + +#### `dapr.yaml` Multi-App Run template file + +Running the [Multi-App Run template file]({{< ref multi-app-dapr-run >}}) with `dapr run -f .` starts all applications in your project. In this Quickstart, the `dapr.yaml` file contains the following: + +```yml +version: 1 +apps: + - appDirPath: ./order-processor/ + appID: order-processor + appPort: 7001 + command: ["dotnet", "run"] + - appID: checkout + appDirPath: ./checkout/ + command: ["dotnet", "run"] +``` + +#### `order-processor` service + +The `order-processor` service receives the call from the `checkout` service: + +```csharp +app.MapPost("/orders", (Order order) => +{ + Console.WriteLine("Order received : " + order); + return order.ToString(); +}); +``` + +#### `checkout` service + +In the Program.cs file for the `checkout` service, you'll notice there's no need to rewrite your app code to use Dapr's service invocation. You can enable service invocation by simply adding the `dapr-app-id` header, which specifies the ID of the target service. + +```csharp +var client = new HttpClient(); +client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + +client.DefaultRequestHeaders.Add("dapr-app-id", "order-processor"); + +var response = await client.PostAsync($"{baseURL}/orders", content); + Console.WriteLine("Order passed: " + order); +``` + +{{% /codetab %}} + + +{{% codetab %}} + +### Step 1: Pre-requisites + +For this example, you will need: + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). +- Java JDK 11 (or greater): + - [Oracle JDK](https://www.oracle.com/java/technologies/downloads), or + - OpenJDK +- [Apache Maven](https://maven.apache.org/install.html), version 3.x. + +- [Docker Desktop](https://www.docker.com/products/docker-desktop) + + +### Step 2: Set up the environment + +Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/service_invocation). + +```bash +git clone https://github.com/dapr/quickstarts.git +``` + +From the root of the Quickstart clone directory, navigate to the quickstart directory. + +```bash +cd service_invocation/java/http +``` + +### Step 3: Run the `order-processor` and `checkout` services + +With the following command, simultaneously run the following services alongside their own Dapr sidecars: +- The `order-processor` service +- The `checkout` service + +```bash +dapr run -f . +``` + +**Expected output** + +``` +== APP - order-processor == Order received : Order { orderId = 1 } +== APP - checkout == Order passed: Order { OrderId = 1 } +== APP - order-processor == Order received : Order { orderId = 2 } +== APP - checkout == Order passed: Order { OrderId = 2 } +== APP - order-processor == Order received : Order { orderId = 3 } +== APP - checkout == Order passed: Order { OrderId = 3 } +== APP - order-processor == Order received : Order { orderId = 4 } +== APP - checkout == Order passed: Order { OrderId = 4 } +== APP - order-processor == Order received : Order { orderId = 5 } +== APP - checkout == Order passed: Order { OrderId = 5 } +== APP - order-processor == Order received : Order { orderId = 6 } +== APP - checkout == Order passed: Order { OrderId = 6 } +== APP - order-processor == Order received : Order { orderId = 7 } +== APP - checkout == Order passed: Order { OrderId = 7 } +== APP - order-processor == Order received : Order { orderId = 8 } +== APP - checkout == Order passed: Order { OrderId = 8 } +== APP - order-processor == Order received : Order { orderId = 9 } +== APP - checkout == Order passed: Order { OrderId = 9 } +== APP - order-processor == Order received : Order { orderId = 10 } +== APP - checkout == Order passed: Order { OrderId = 10 } +== APP - order-processor == Order received : Order { orderId = 11 } +== APP - checkout == Order passed: Order { OrderId = 11 } +== APP - order-processor == Order received : Order { orderId = 12 } +== APP - checkout == Order passed: Order { OrderId = 12 } +== APP - order-processor == Order received : Order { orderId = 13 } +== APP - checkout == Order passed: Order { OrderId = 13 } +== APP - order-processor == Order received : Order { orderId = 14 } +== APP - checkout == Order passed: Order { OrderId = 14 } +== APP - order-processor == Order received : Order { orderId = 15 } +== APP - checkout == Order passed: Order { OrderId = 15 } +== APP - order-processor == Order received : Order { orderId = 16 } +== APP - checkout == Order passed: Order { OrderId = 16 } +== APP - order-processor == Order received : Order { orderId = 17 } +== APP - checkout == Order passed: Order { OrderId = 17 } +== APP - order-processor == Order received : Order { orderId = 18 } +== APP - checkout == Order passed: Order { OrderId = 18 } +== APP - order-processor == Order received : Order { orderId = 19 } +== APP - checkout == Order passed: Order { OrderId = 19 } +== APP - order-processor == Order received : Order { orderId = 20 } +== APP - checkout == Order passed: Order { OrderId = 20 } +Exited App successfully +``` + +### What happened? + +Running `dapr run -f .` in this Quickstart started both the [subscriber]({{< ref "#order-processor-service" >}}) and [publisher]({{< ref "#checkout-service" >}}) applications using the `dapr.yaml` Multi-App Run template file. + +#### `dapr.yaml` Multi-App Run template file + +Running the [Multi-App Run template file]({{< ref multi-app-dapr-run >}}) with `dapr run -f .` starts all applications in your project. In this Quickstart, the `dapr.yaml` file contains the following: + +```yml +version: 1 +apps: + - appDirPath: ./order-processor/ + appID: order-processor + appPort: 9001 + command: ["java", "-jar", "target/OrderProcessingService-0.0.1-SNAPSHOT.jar"] + - appID: checkout + appDirPath: ./checkout/ + command: ["java", "-jar", "target/CheckoutService-0.0.1-SNAPSHOT.jar"] +``` + +#### `order-processor` service + +The `order-processor` service receives the call from the `checkout` service: + +```java +public String processOrders(@RequestBody Order body) { + System.out.println("Order received: "+ body.getOrderId()); + return "CID" + body.getOrderId(); + } +``` + +#### `checkout` service + +In the `checkout` service, you'll notice there's no need to rewrite your app code to use Dapr's service invocation. You can enable service invocation by simply adding the `dapr-app-id` header, which specifies the ID of the target service. + +```java +.header("Content-Type", "application/json") +.header("dapr-app-id", "order-processor") + +HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); +System.out.println("Order passed: "+ orderId) +``` + +{{% /codetab %}} + + +{{% codetab %}} + +### Step 1: Pre-requisites + +For this example, you will need: + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). +- [Latest version of Go](https://go.dev/dl/). + +- [Docker Desktop](https://www.docker.com/products/docker-desktop) + + +### Step 2: Set up the environment + +Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/service_invocation). + + +```bash +git clone https://github.com/dapr/quickstarts.git +``` + +From the root of the Quickstart clone directory, navigate to the quickstart directory. + +```bash +cd service_invocation/go/http +``` + +### Step 3: Run the `order-processor` and `checkout` services + +With the following command, simultaneously run the following services alongside their own Dapr sidecars: +- The `order-processor` service +- The `checkout` service + +```bash +dapr run -f . +``` + +**Expected output** + +``` +== APP - order-processor == Order received : Order { orderId = 1 } +== APP - checkout == Order passed: Order { OrderId = 1 } +== APP - order-processor == Order received : Order { orderId = 2 } +== APP - checkout == Order passed: Order { OrderId = 2 } +== APP - order-processor == Order received : Order { orderId = 3 } +== APP - checkout == Order passed: Order { OrderId = 3 } +== APP - order-processor == Order received : Order { orderId = 4 } +== APP - checkout == Order passed: Order { OrderId = 4 } +== APP - order-processor == Order received : Order { orderId = 5 } +== APP - checkout == Order passed: Order { OrderId = 5 } +== APP - order-processor == Order received : Order { orderId = 6 } +== APP - checkout == Order passed: Order { OrderId = 6 } +== APP - order-processor == Order received : Order { orderId = 7 } +== APP - checkout == Order passed: Order { OrderId = 7 } +== APP - order-processor == Order received : Order { orderId = 8 } +== APP - checkout == Order passed: Order { OrderId = 8 } +== APP - order-processor == Order received : Order { orderId = 9 } +== APP - checkout == Order passed: Order { OrderId = 9 } +== APP - order-processor == Order received : Order { orderId = 10 } +== APP - checkout == Order passed: Order { OrderId = 10 } +== APP - order-processor == Order received : Order { orderId = 11 } +== APP - checkout == Order passed: Order { OrderId = 11 } +== APP - order-processor == Order received : Order { orderId = 12 } +== APP - checkout == Order passed: Order { OrderId = 12 } +== APP - order-processor == Order received : Order { orderId = 13 } +== APP - checkout == Order passed: Order { OrderId = 13 } +== APP - order-processor == Order received : Order { orderId = 14 } +== APP - checkout == Order passed: Order { OrderId = 14 } +== APP - order-processor == Order received : Order { orderId = 15 } +== APP - checkout == Order passed: Order { OrderId = 15 } +== APP - order-processor == Order received : Order { orderId = 16 } +== APP - checkout == Order passed: Order { OrderId = 16 } +== APP - order-processor == Order received : Order { orderId = 17 } +== APP - checkout == Order passed: Order { OrderId = 17 } +== APP - order-processor == Order received : Order { orderId = 18 } +== APP - checkout == Order passed: Order { OrderId = 18 } +== APP - order-processor == Order received : Order { orderId = 19 } +== APP - checkout == Order passed: Order { OrderId = 19 } +== APP - order-processor == Order received : Order { orderId = 20 } +== APP - checkout == Order passed: Order { OrderId = 20 } +Exited App successfully +``` + +### What happened? + +Running `dapr run -f .` in this Quickstart started both the [subscriber]({{< ref "#order-processor-service" >}}) and [publisher]({{< ref "#checkout-service" >}}) applications using the `dapr.yaml` Multi-App Run template file. + +#### `dapr.yaml` Multi-App Run template file + +Running the [Multi-App Run template file]({{< ref multi-app-dapr-run >}}) with `dapr run -f .` starts all applications in your project. In this Quickstart, the `dapr.yaml` file contains the following: + +```yml +version: 1 +apps: + - appDirPath: ./order-processor/ + appID: order-processor + appPort: 6006 + command: ["go", "run", "."] + - appID: checkout + appDirPath: ./checkout/ + command: ["go", "run", "."] +``` + +#### `order-processor` service + +In the `order-processor` service, each order is received via an HTTP POST request and processed by the `getOrder` function. + +```go +func getOrder(w http.ResponseWriter, r *http.Request) { + data, err := ioutil.ReadAll(r.Body) + if err != nil { + log.Fatal(err) + } + log.Printf("Order received : %s", string(data)) +} +``` + +#### `checkout` service + +In the `checkout` service, you'll notice there's no need to rewrite your app code to use Dapr's service invocation. You can enable service invocation by simply adding the `dapr-app-id` header, which specifies the ID of the target service. + +```go +req.Header.Add("dapr-app-id", "order-processor") + +response, err := client.Do(req) +``` + +{{% /codetab %}} + +{{% /tabs %}} + +## Run one application at a time + Select your preferred language before proceeding with the Quickstart. {{< tabs "Python" "JavaScript" ".NET" "Java" "Go" >}} diff --git a/daprdocs/content/en/getting-started/quickstarts/statemanagement-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/statemanagement-quickstart.md index f73aa37f494..7c80c4c2304 100644 --- a/daprdocs/content/en/getting-started/quickstarts/statemanagement-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/statemanagement-quickstart.md @@ -6,10 +6,692 @@ weight: 72 description: "Get started with Dapr's State Management building block" --- -Let's take a look at Dapr's [State Management building block]({{< ref state-management >}}). In this Quickstart, you will save, get, and delete state using a Redis state store, but you can swap this out for any one of the [supported state stores]({{< ref supported-state-stores.md >}}). +Let's take a look at Dapr's [State Management building block]({{< ref state-management >}}). In this Quickstart, you will save, get, and delete state using a Redis state store by either: +- [Running all applications simultaneously with the Multi-App Run template file]({{< ref "#run-using-multi-app-run" >}}), or +- [Running a single application at a time]({{< ref "#run-one-application-at-a-time" >}}) +While this sample uses Redis, you can swap it out for any one of the [supported state stores]({{< ref supported-state-stores.md >}}). + +## Run using Multi-App Run + +{{% alert title="Note" color="primary" %}} + [Multi-App Run]({{< ref multi-app-dapr-run >}}) is currently a preview feature only supported in Linux/MacOS. +{{% /alert %}} + +Select your preferred language-specific Dapr SDK before proceeding with the Quickstart. + +{{< tabs "Python" "JavaScript" ".NET" "Java" "Go" >}} + +{{% codetab %}} + +### Pre-requisites + +For this example, you will need: + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). +- [Python 3.7+ installed](https://www.python.org/downloads/). + +- [Docker Desktop](https://www.docker.com/products/docker-desktop) + + +### Step 1: Set up the environment + +Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/state_management). + +```bash +git clone https://github.com/dapr/quickstarts.git +``` + +### Step 2: Manipulate service state + +In a terminal window, navigate to the `order-processor` directory. + +```bash +cd state_management/python/sdk/order-processor +``` + +Install the dependencies: + +```bash +pip3 install -r requirements.txt +``` + +Run the `order-processor` service alongside a Dapr sidecar using [Multi-App Run]({{< ref multi-app-dapr-run >}}). + +```bash +dapr run -f +``` + +The `order-processor` service writes, reads, and deletes an `orderId` key/value pair to the `statestore` instance [defined in the `statestore.yaml` component]({{< ref "#statestoreyaml-component-file" >}}). As soon as the service starts, it performs a loop. + +```python +with DaprClient() as client: + + # Save state into the state store + client.save_state(DAPR_STORE_NAME, orderId, str(order)) + logging.info('Saving Order: %s', order) + + # Get state from the state store + result = client.get_state(DAPR_STORE_NAME, orderId) + logging.info('Result after get: ' + str(result.data)) + + # Delete state from the state store + client.delete_state(store_name=DAPR_STORE_NAME, key=orderId) + logging.info('Deleting Order: %s', order) +``` + +### Step 3: View the order-processor outputs + +Notice, as specified in the code above, the code saves application state in the Dapr state store, reads it, then deletes it. + +Order-processor output: +``` +== APP == INFO:root:Saving Order: {'orderId': '1'} +== APP == INFO:root:Result after get: b"{'orderId': '1'}" +== APP == INFO:root:Deleting Order: {'orderId': '1'} +== APP == INFO:root:Saving Order: {'orderId': '2'} +== APP == INFO:root:Result after get: b"{'orderId': '2'}" +== APP == INFO:root:Deleting Order: {'orderId': '2'} +== APP == INFO:root:Saving Order: {'orderId': '3'} +== APP == INFO:root:Result after get: b"{'orderId': '3'}" +== APP == INFO:root:Deleting Order: {'orderId': '3'} +== APP == INFO:root:Saving Order: {'orderId': '4'} +== APP == INFO:root:Result after get: b"{'orderId': '4'}" +== APP == INFO:root:Deleting Order: {'orderId': '4'} +``` + +#### `dapr.yaml` Multi-App Run template file + +When you run `dapr init`, Dapr creates a default [Multi-App Run template file]({{< ref multi-app-dapr-run >}}) named `dapr.yaml`. Running `dapr run -f` starts all applications in your project. In this sample, the `dapr.yaml` file contains the following: + +```yml +version: 1 +common: + resourcesPath: ../../../resources/ +apps: + - appID: order-processor + appDirPath: ./order-processor/ + command: ["dotnet", "run"] +``` + +#### `statestore.yaml` component file + +When you run `dapr init`, Dapr also creates a default Redis `statestore.yaml` and runs a Redis container on your local machine, located: + +- On Windows, under `%UserProfile%\.dapr\components\statestore.yaml` +- On Linux/MacOS, under `~/.dapr/components/statestore.yaml` + +With the `statestore.yaml` component, you can easily swap out the [state store](/reference/components-reference/supported-state-stores/) without making code changes. + +The Redis `statestore.yaml` file included for this quickstart contains the following: + +```yaml +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: statestore +spec: + type: state.redis + version: v1 + metadata: + - name: redisHost + value: localhost:6379 + - name: redisPassword + value: "" + - name: actorStateStore + value: "true" +``` + +In the YAML file: + +- `metadata/name` is how your application talks to the component (called `DAPR_STORE_NAME` in the code sample). +- `spec/metadata` defines the connection to the Redis instance used by the component. + +{{% /codetab %}} + + +{{% codetab %}} + +### Pre-requisites + +For this example, you will need: + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). +- [Latest Node.js installed](https://nodejs.org/download/). + +- [Docker Desktop](https://www.docker.com/products/docker-desktop) + + +### Step 1: Set up the environment + +Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/state_management). + +```bash +git clone https://github.com/dapr/quickstarts.git +``` + +### Step 2: Manipulate service state + +In a terminal window, navigate to the `order-processor` directory. + +```bash +cd state_management/javascript/sdk/order-processor +``` + +Install dependencies, which will include the `@dapr/dapr` package from the JavaScript SDK: + +```bash +npm install +``` + +Verify you have the following files included in the service directory: + +- `package.json` +- `package-lock.json` + +Run the `order-processor` service alongside a Dapr sidecar. + +```bash +dapr run -f +``` + +The `order-processor` service writes, reads, and deletes an `orderId` key/value pair to the `statestore` instance [defined in the `statestore.yaml` component]({{< ref "#statestoreyaml-component-file" >}}). As soon as the service starts, it performs a loop. + +```js +const client = new DaprClient() + +// Save state into a state store +await client.state.save(DAPR_STATE_STORE_NAME, order) +console.log("Saving Order: ", order) + +// Get state from a state store +const savedOrder = await client.state.get(DAPR_STATE_STORE_NAME, order.orderId) +console.log("Getting Order: ", savedOrder) + +// Delete state from the state store +await client.state.delete(DAPR_STATE_STORE_NAME, order.orderId) +console.log("Deleting Order: ", order) +``` +### Step 3: View the order-processor outputs + +Notice, as specified in the code above, the code saves application state in the Dapr state store, reads it, then deletes it. + +Order-processor output: +``` +== APP == > order-processor@1.0.0 start +== APP == > node index.js +== APP == Saving Order: { orderId: 1 } +== APP == Saving Order: { orderId: 2 } +== APP == Saving Order: { orderId: 3 } +== APP == Saving Order: { orderId: 4 } +== APP == Saving Order: { orderId: 5 } +== APP == Getting Order: { orderId: 1 } +== APP == Deleting Order: { orderId: 1 } +== APP == Getting Order: { orderId: 2 } +== APP == Deleting Order: { orderId: 2 } +== APP == Getting Order: { orderId: 3 } +== APP == Deleting Order: { orderId: 3 } +== APP == Getting Order: { orderId: 4 } +== APP == Deleting Order: { orderId: 4 } +== APP == Getting Order: { orderId: 5 } +== APP == Deleting Order: { orderId: 5 } +``` + +#### `dapr.yaml` Multi-App Run template file + +When you run `dapr init`, Dapr creates a default Multi-App Run template file named `dapr.yaml`. Running `dapr run -f` starts all applications in your project. In this sample, the `dapr.yaml` file contains the following: + +```yml +version: 1 +common: + resourcesPath: ../../../resources/ +apps: + - appID: order-processor + appDirPath: ./order-processor/ + command: ["dotnet", "run"] +``` + +#### `statestore.yaml` component file + +When you run `dapr init`, Dapr creates a default Redis `statestore.yaml` and runs a Redis container on your local machine, located: + +- On Windows, under `%UserProfile%\.dapr\components\statestore.yaml` +- On Linux/MacOS, under `~/.dapr/components/statestore.yaml` + +With the `statestore.yaml` component, you can easily swap out the [state store](/reference/components-reference/supported-state-stores/) without making code changes. + +The Redis `statestore.yaml` file included for this quickstart contains the following: + +```yaml +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: statestore +spec: + type: state.redis + version: v1 + metadata: + - name: redisHost + value: localhost:6379 + - name: redisPassword + value: "" + - name: actorStateStore + value: "true" +``` + +In the YAML file: + +- `metadata/name` is how your application talks to the component (called `DAPR_STORE_NAME` in the code sample). +- `spec/metadata` defines the connection to the Redis instance used by the component. + +{{% /codetab %}} + + +{{% codetab %}} + +### Pre-requisites + +For this example, you will need: + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). +- [.NET SDK or .NET 6 SDK installed](https://dotnet.microsoft.com/download). + +- [Docker Desktop](https://www.docker.com/products/docker-desktop) + + +### Step 1: Set up the environment + +Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/state_management). + +```bash +git clone https://github.com/dapr/quickstarts.git +``` + +### Step 2: Manipulate service state + +In a terminal window, navigate to the `order-processor` directory. + +```bash +cd state_management/csharp/sdk/order-processor +``` + +Recall NuGet packages: + +```bash +dotnet restore +dotnet build +``` + +Run the `order-processor` service alongside a Dapr sidecar. + +```bash +dapr run -f +``` + +The `order-processor` service writes, reads, and deletes an `orderId` key/value pair to the `statestore` instance [defined in the `statestore.yaml` component]({{< ref "#statestoreyaml-component-file" >}}). As soon as the service starts, it performs a loop. + +```cs +var client = new DaprClientBuilder().Build(); + +// Save state into the state store +await client.SaveStateAsync(DAPR_STORE_NAME, orderId.ToString(), order.ToString()); +Console.WriteLine("Saving Order: " + order); + +// Get state from the state store +var result = await client.GetStateAsync(DAPR_STORE_NAME, orderId.ToString()); +Console.WriteLine("Getting Order: " + result); + +// Delete state from the state store +await client.DeleteStateAsync(DAPR_STORE_NAME, orderId.ToString()); +Console.WriteLine("Deleting Order: " + order); +``` +### Step 3: View the order-processor outputs + +Notice, as specified in the code above, the code saves application state in the Dapr state store, reads it, then deletes it. + +Order-processor output: +``` +== APP == Saving Order: Order { orderId = 1 } +== APP == Getting Order: Order { orderId = 1 } +== APP == Deleting Order: Order { orderId = 1 } +== APP == Saving Order: Order { orderId = 2 } +== APP == Getting Order: Order { orderId = 2 } +== APP == Deleting Order: Order { orderId = 2 } +== APP == Saving Order: Order { orderId = 3 } +== APP == Getting Order: Order { orderId = 3 } +== APP == Deleting Order: Order { orderId = 3 } +== APP == Saving Order: Order { orderId = 4 } +== APP == Getting Order: Order { orderId = 4 } +== APP == Deleting Order: Order { orderId = 4 } +== APP == Saving Order: Order { orderId = 5 } +== APP == Getting Order: Order { orderId = 5 } +== APP == Deleting Order: Order { orderId = 5 } +``` + +#### `dapr.yaml` Multi-App Run template file + +When you run `dapr init`, Dapr creates a default Multi-App Run template file named `dapr.yaml`. Running `dapr run -f` starts all applications in your project. In this sample, the `dapr.yaml` file contains the following: + +```yml +version: 1 +common: + resourcesPath: ../../../resources/ +apps: + - appID: order-processor + appDirPath: ./order-processor/ + command: ["dotnet", "run"] +``` + +#### `statestore.yaml` component file + +When you run `dapr init`, Dapr creates a default Redis `statestore.yaml` and runs a Redis container on your local machine, located: + +- On Windows, under `%UserProfile%\.dapr\components\statestore.yaml` +- On Linux/MacOS, under `~/.dapr/components/statestore.yaml` + +With the `statestore.yaml` component, you can easily swap out the [state store](/reference/components-reference/supported-state-stores/) without making code changes. + +The Redis `statestore.yaml` file included for this quickstart contains the following: + +```yaml +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: statestore +spec: + type: state.redis + version: v1 + metadata: + - name: redisHost + value: localhost:6379 + - name: redisPassword + value: "" + - name: actorStateStore + value: "true" +``` + +In the YAML file: + +- `metadata/name` is how your application talks to the component (called `DAPR_STORE_NAME` in the code sample). +- `spec/metadata` defines the connection to the Redis instance used by the component. + +{{% /codetab %}} + + +{{% codetab %}} + +### Pre-requisites + +For this example, you will need: + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). +- Java JDK 11 (or greater): + - [Oracle JDK](https://www.oracle.com/java/technologies/downloads), or + - OpenJDK +- [Apache Maven](https://maven.apache.org/install.html), version 3.x. + +- [Docker Desktop](https://www.docker.com/products/docker-desktop) + + +### Step 1: Set up the environment + +Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/state_management). + +```bash +git clone https://github.com/dapr/quickstarts.git +``` + +### Step 2: Manipulate service state + +In a terminal window, navigate to the `order-processor` directory. + +```bash +cd state_management/java/sdk/order-processor +``` + +Install the dependencies: + +```bash +mvn clean install +``` + +Run the `order-processor` service alongside a Dapr sidecar. + +```bash +dapr run -f +``` + +The `order-processor` service writes, reads, and deletes an `orderId` key/value pair to the `statestore` instance [defined in the `statestore.yaml` component]({{< ref "#statestoreyaml-component-file" >}}). As soon as the service starts, it performs a loop. + +```java +try (DaprClient client = new DaprClientBuilder().build()) { + for (int i = 1; i <= 10; i++) { + int orderId = i; + Order order = new Order(); + order.setOrderId(orderId); + + // Save state into the state store + client.saveState(DAPR_STATE_STORE, String.valueOf(orderId), order).block(); + LOGGER.info("Saving Order: " + order.getOrderId()); + + // Get state from the state store + State response = client.getState(DAPR_STATE_STORE, String.valueOf(orderId), Order.class).block(); + LOGGER.info("Getting Order: " + response.getValue().getOrderId()); + + // Delete state from the state store + client.deleteState(DAPR_STATE_STORE, String.valueOf(orderId)).block(); + LOGGER.info("Deleting Order: " + orderId); + TimeUnit.MILLISECONDS.sleep(1000); + } +``` +### Step 3: View the order-processor outputs + +Notice, as specified in the code above, the code saves application state in the Dapr state store, reads it, then deletes it. + +Order-processor output: +``` +== APP == INFO:root:Saving Order: {'orderId': '1'} +== APP == INFO:root:Result after get: b"{'orderId': '1'}" +== APP == INFO:root:Deleting Order: {'orderId': '1'} +== APP == INFO:root:Saving Order: {'orderId': '2'} +== APP == INFO:root:Result after get: b"{'orderId': '2'}" +== APP == INFO:root:Deleting Order: {'orderId': '2'} +== APP == INFO:root:Saving Order: {'orderId': '3'} +== APP == INFO:root:Result after get: b"{'orderId': '3'}" +== APP == INFO:root:Deleting Order: {'orderId': '3'} +== APP == INFO:root:Saving Order: {'orderId': '4'} +== APP == INFO:root:Result after get: b"{'orderId': '4'}" +== APP == INFO:root:Deleting Order: {'orderId': '4'} +``` + +#### `dapr.yaml` Multi-App Run template file + +When you run `dapr init`, Dapr creates a default Multi-App Run template file named `dapr.yaml`. Running `dapr run -f` starts all applications in your project. In this sample, the `dapr.yaml` file contains the following: + +```yml +version: 1 +common: + resourcesPath: ../../../resources/ +apps: + - appID: order-processor + appDirPath: ./order-processor/ + command: ["dotnet", "run"] +``` + +#### `statestore.yaml` component file + +When you run `dapr init`, Dapr creates a default Redis `statestore.yaml` and runs a Redis container on your local machine, located: + +- On Windows, under `%UserProfile%\.dapr\components\statestore.yaml` +- On Linux/MacOS, under `~/.dapr/components/statestore.yaml` + +With the `statestore.yaml` component, you can easily swap out the [state store](/reference/components-reference/supported-state-stores/) without making code changes. + +The Redis `statestore.yaml` file included for this Quickstart contains the following: + +```yaml +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: statestore +spec: + type: state.redis + version: v1 + metadata: + - name: redisHost + value: localhost:6379 + - name: redisPassword + value: "" + - name: actorStateStore + value: "true" +``` + +In the YAML file: + +- `metadata/name` is how your application talks to the component (called `DAPR_STORE_NAME` in the code sample). +- `spec/metadata` defines the connection to the Redis instance used by the component. + +{{% /codetab %}} + + +{{% codetab %}} + +### Pre-requisites + +For this example, you will need: + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). +- [Latest version of Go](https://go.dev/dl/). + +- [Docker Desktop](https://www.docker.com/products/docker-desktop) + + +### Step 1: Set up the environment + +Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/state_management). + +```bash +git clone https://github.com/dapr/quickstarts.git +``` + +### Step 2: Manipulate service state + +In a terminal window, navigate to the `order-processor` directory. + +```bash +cd state_management/go/sdk/order-processor +``` + +Install the dependencies and build the application: + +```bash +go build . +``` + +Run the `order-processor` service alongside a Dapr sidecar. + +```bash +dapr run -f +``` + +The `order-processor` service writes, reads, and deletes an `orderId` key/value pair to the `statestore` instance [defined in the `statestore.yaml` component]({{< ref "#statestoreyaml-component-file" >}}). As soon as the service starts, it performs a loop. + +```go + client, err := dapr.NewClient() + + // Save state into the state store + _ = client.SaveState(ctx, STATE_STORE_NAME, strconv.Itoa(orderId), []byte(order)) + log.Print("Saving Order: " + string(order)) + + // Get state from the state store + result, _ := client.GetState(ctx, STATE_STORE_NAME, strconv.Itoa(orderId)) + fmt.Println("Getting Order: " + string(result.Value)) + + // Delete state from the state store + _ = client.DeleteState(ctx, STATE_STORE_NAME, strconv.Itoa(orderId)) + log.Print("Deleting Order: " + string(order)) +``` + +### Step 3: View the order-processor outputs + +Notice, as specified in the code above, the code saves application state in the Dapr state store, reads it, then deletes it. + +Order-processor output: +``` +== APP == dapr client initializing for: 127.0.0.1:53689 +== APP == 2022/04/01 09:16:03 Saving Order: {"orderId":1} +== APP == Getting Order: {"orderId":1} +== APP == 2022/04/01 09:16:03 Deleting Order: {"orderId":1} +== APP == 2022/04/01 09:16:03 Saving Order: {"orderId":2} +== APP == Getting Order: {"orderId":2} +== APP == 2022/04/01 09:16:03 Deleting Order: {"orderId":2} +== APP == 2022/04/01 09:16:03 Saving Order: {"orderId":3} +== APP == Getting Order: {"orderId":3} +== APP == 2022/04/01 09:16:03 Deleting Order: {"orderId":3} +== APP == 2022/04/01 09:16:03 Saving Order: {"orderId":4} +== APP == Getting Order: {"orderId":4} +== APP == 2022/04/01 09:16:03 Deleting Order: {"orderId":4} +== APP == 2022/04/01 09:16:03 Saving Order: {"orderId":5} +== APP == Getting Order: {"orderId":5} +== APP == 2022/04/01 09:16:03 Deleting Order: {"orderId":5} +``` + +#### `dapr.yaml` Multi-App Run template file + +When you run `dapr init`, Dapr creates a default Multi-App Run template file named `dapr.yaml`. Running `dapr run -f` starts all applications in your project. In this sample, the `dapr.yaml` file contains the following: + +```yml +version: 1 +common: + resourcesPath: ../../../resources/ +apps: + - appID: order-processor + appDirPath: ./order-processor/ + command: ["dotnet", "run"] +``` + +#### `statestore.yaml` component file + +When you run `dapr init`, Dapr creates a default Redis `statestore.yaml` and runs a Redis container on your local machine, located: + +- On Windows, under `%UserProfile%\.dapr\components\statestore.yaml` +- On Linux/MacOS, under `~/.dapr/components/statestore.yaml` + +With the `statestore.yaml` component, you can easily swap out the [state store](/reference/components-reference/supported-state-stores/) without making code changes. + +The Redis `statestore.yaml` file included for this Quickstart contains the following: + +```yaml +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: statestore +spec: + type: state.redis + version: v1 + metadata: + - name: redisHost + value: localhost:6379 + - name: redisPassword + value: "" + - name: actorStateStore + value: "true" +``` + +In the YAML file: + +- `metadata/name` is how your application talks to the component (called `DAPR_STORE_NAME` in the code sample). +- `spec/metadata` defines the connection to the Redis instance used by the component. + +{{% /codetab %}} + +{{< /tabs >}} + + +## Run one application at a time + Select your preferred language-specific Dapr SDK before proceeding with the Quickstart. {{< tabs "Python" "JavaScript" ".NET" "Java" "Go" >}} From 821ca0fb08306b8a9ec3905256646e59b9ddff8d Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Thu, 21 Sep 2023 11:16:40 -0400 Subject: [PATCH 056/162] update headings Signed-off-by: Hannah Hunter --- .../quickstarts/pubsub-quickstart.md | 50 +++++++++---------- .../serviceinvocation-quickstart.md | 28 +++++------ .../quickstarts/statemanagement-quickstart.md | 30 +++++------ 3 files changed, 54 insertions(+), 54 deletions(-) diff --git a/daprdocs/content/en/getting-started/quickstarts/pubsub-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/pubsub-quickstart.md index 96dbd8bdc44..3581606d0a4 100644 --- a/daprdocs/content/en/getting-started/quickstarts/pubsub-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/pubsub-quickstart.md @@ -99,7 +99,7 @@ When you ran `dapr init` during Dapr install, the following YAML files were gene Running `dapr run -f .` in this Quickstart started both the [subscriber]({{< ref "#order-processor-subscriber" >}}) and [publisher]({{< ref "#checkout-publisher" >}}) applications. -#### `dapr.yaml` Multi-App Run template file +##### `dapr.yaml` Multi-App Run template file Running the [Multi-App Run template file]({{< ref multi-app-dapr-run >}}) with `dapr run -f .` starts all applications in your project. In this Quickstart, the `dapr.yaml` file contains the following: @@ -117,7 +117,7 @@ apps: command: ["python3", "app.py"] ``` -#### `pubsub.yaml` component file +##### `pubsub.yaml` component file With the `pubsub.yaml` component, you can easily swap out underlying components without application code changes. @@ -144,7 +144,7 @@ In the component YAML file: - `spec/metadata` defines the connection to the instance of the component. - `scopes` specify which application can use the component. -#### `order-processor` subscriber +##### `order-processor` subscriber In the `order-processor` subscriber, you subscribe to the Redis instance called `orderpubsub` [(as defined in the `pubsub.yaml` component)]({{< ref "#pubsubyaml-component-file" >}}) and topic `orders`. This enables your app code to talk to the Redis component instance through the Dapr sidecar. @@ -173,7 +173,7 @@ def orders_subscriber(): app.run(port=5001) ``` -#### `checkout` publisher +##### `checkout` publisher In the `checkout` publisher, you publish the orderId message to the Redis instance called `orderpubsub` [(as defined in the `pubsub.yaml` component)]({{< ref "#pubsubyaml-component-file" >}}) and topic `orders`. As soon as the service starts, it publishes in a loop: @@ -261,7 +261,7 @@ When you ran `dapr init` during Dapr install, the following YAML files were gene Running `dapr run -f .` in this Quickstart started both the [subscriber]({{< ref "#order-processor-subscriber" >}}) and [publisher]({{< ref "#checkout-publisher" >}}) applications. -#### `dapr.yaml` Multi-App Run template file +##### `dapr.yaml` Multi-App Run template file Running the [Multi-App Run template file]({{< ref multi-app-dapr-run >}}) with `dapr run -f .` starts all applications in your project. In this Quickstart, the `dapr.yaml` file contains the following: @@ -279,7 +279,7 @@ apps: command: ["npm", "run", "start"] ``` -#### `pubsub.yaml` component file +##### `pubsub.yaml` component file With the `pubsub.yaml` component, you can easily swap out underlying components without application code changes. @@ -306,7 +306,7 @@ In the component YAML file: - `spec/metadata` defines the connection to the instance of the component. - `scopes` specify which application can use the component. -#### `order-processor` subscriber +##### `order-processor` subscriber In the `order-processor` subscriber, you subscribe to the Redis instance called `orderpubsub` [(as defined in the `pubsub.yaml` component)]({{< ref "#pubsubyaml-component-file" >}}) and topic `orders`. This enables your app code to talk to the Redis component instance through the Dapr sidecar. @@ -314,7 +314,7 @@ In the `order-processor` subscriber, you subscribe to the Redis instance called server.pubsub.subscribe("orderpubsub", "orders", (data) => console.log("Subscriber received: " + JSON.stringify(data))); ``` -#### `checkout` publisher +##### `checkout` publisher In the `checkout` publisher service, you publish the orderId message to the Redis instance called `orderpubsub` [(as defined in the `pubsub.yaml` component)]({{< ref "#pubsubyaml-component-file" >}}) and topic `orders`. As soon as the service starts, it publishes in a loop: @@ -398,7 +398,7 @@ When you ran `dapr init` during Dapr install, the following YAML files were gene Running `dapr run -f .` in this Quickstart started both the [subscriber]({{< ref "#order-processor-subscriber" >}}) and [publisher]({{< ref "#checkout-publisher" >}}) applications. -#### `dapr.yaml` Multi-App Run template file +##### `dapr.yaml` Multi-App Run template file Running the [Multi-App Run template file]({{< ref multi-app-dapr-run >}}) with `dapr run -f .` starts all applications in your project. In this Quickstart, the `dapr.yaml` file contains the following: @@ -416,7 +416,7 @@ apps: command: ["dotnet", "run"] ``` -#### `pubsub.yaml` component file +##### `pubsub.yaml` component file With the `pubsub.yaml` component, you can easily swap out underlying components without application code changes. @@ -443,7 +443,7 @@ In the component YAML file: - `spec/metadata` defines the connection to the instance of the component. - `scopes` specify which application can use the component. -#### `order-processor` subscriber +##### `order-processor` subscriber In the `order-processor` subscriber, you subscribe to the Redis instance called `orderpubsub` [(as defined in the `pubsub.yaml` component)]({{< ref "#pubsubyaml-component-file" >}}) and topic `orders`. This enables your app code to talk to the Redis component instance through the Dapr sidecar. @@ -457,7 +457,7 @@ app.MapPost("/orders", [Topic("orderpubsub", "orders")] (Order order) => { public record Order([property: JsonPropertyName("orderId")] int OrderId); ``` -#### `checkout` publisher +##### `checkout` publisher In the `checkout` publisher, you publish the orderId message to the Redis instance called `orderpubsub` [(as defined in the `pubsub.yaml` component)]({{< ref "#pubsubyaml-component-file" >}}) and topic `orders`. As soon as the service starts, it publishes in a loop: @@ -543,7 +543,7 @@ When you ran `dapr init` during Dapr install, the following YAML files were gene Running `dapr run -f .` in this Quickstart started both the [subscriber]({{< ref "#order-processor-subscriber" >}}) and [publisher]({{< ref "#checkout-publisher" >}}) applications. -#### `dapr.yaml` Multi-App Run template file +##### `dapr.yaml` Multi-App Run template file Running the [Multi-App Run template file]({{< ref multi-app-dapr-run >}}) with `dapr run -f .` starts all applications in your project. In this Quickstart, the `dapr.yaml` file contains the following: @@ -561,7 +561,7 @@ apps: command: ["java", "-jar", "CheckoutService-0.0.1-SNAPSHOT.jar"] ``` -#### `pubsub.yaml` component file +##### `pubsub.yaml` component file With the `pubsub.yaml` component, you can easily swap out underlying components without application code changes. @@ -588,7 +588,7 @@ In the component YAML file: - `spec/metadata` defines the connection to the instance of the component. - `scopes` specify which application can use the component. -#### `order-processor` subscriber +##### `order-processor` subscriber In the `order-processor` subscriber, you subscribe to the Redis instance called `orderpubsub` [(as defined in the `pubsub.yaml` component)]({{< ref "#pubsubyaml-component-file" >}}) and topic `orders`. This enables your app code to talk to the Redis component instance through the Dapr sidecar. @@ -607,7 +607,7 @@ public Mono getCheckout(@RequestBody(required = false) CloudEven } ``` -#### `checkout` publisher +##### `checkout` publisher In the `checkout` publisher, you publish the orderId message to the Redis instance called `orderpubsub` [(as defined in the `pubsub.yaml` component)]({{< ref "#pubsubyaml-component-file" >}}) and topic `orders`. As soon as the service starts, it publishes in a loop: @@ -693,7 +693,7 @@ When you ran `dapr init` during Dapr install, the following YAML files were gene Running `dapr run -f .` in this Quickstart started both the [subscriber]({{< ref "#order-processor-subscriber" >}}) and [publisher]({{< ref "#checkout-publisher" >}}) applications. -#### `dapr.yaml` Multi-App Run template file +##### `dapr.yaml` Multi-App Run template file Running the [Multi-App Run template file]({{< ref multi-app-dapr-run >}}) with `dapr run -f .` starts all applications in your project. In this Quickstart, the `dapr.yaml` file contains the following: @@ -711,7 +711,7 @@ apps: command: ["go", "run", "."] ``` -#### `pubsub.yaml` component file +##### `pubsub.yaml` component file With the `pubsub.yaml` component, you can easily swap out underlying components without application code changes. @@ -738,7 +738,7 @@ In the component YAML file: - `spec/metadata` defines the connection to the instance of the component. - `scopes` specify which application can use the component. -#### `order-processor` subscriber +##### `order-processor` subscriber In the `order-processor` subscriber, you subscribe to the Redis instance called `orderpubsub` [(as defined in the `pubsub.yaml` component)]({{< ref "#pubsubyaml-component-file" >}}) and topic `orders`. This enables your app code to talk to the Redis component instance through the Dapr sidecar. @@ -749,7 +749,7 @@ func eventHandler(ctx context.Context, e *common.TopicEvent) (retry bool, err er } ``` -#### `checkout` publisher +##### `checkout` publisher In the `checkout` publisher, you publish the orderId message to the Redis instance called `orderpubsub` [(as defined in the `pubsub.yaml` component)]({{< ref "#pubsubyaml-component-file" >}}) and topic `orders`. As soon as the service starts, it publishes in a loop: @@ -912,7 +912,7 @@ Subscriber output: == APP == INFO:root:Subscriber received: {"orderId": 10} ``` -#### `pubsub.yaml` component file +##### `pubsub.yaml` component file When you run `dapr init`, Dapr creates a default Redis `pubsub.yaml` and runs a Redis container on your local machine, located: @@ -1070,7 +1070,7 @@ Subscriber output: ``` -#### `pubsub.yaml` component file +##### `pubsub.yaml` component file When you run `dapr init`, Dapr creates a default Redis `pubsub.yaml` and runs a Redis container on your local machine, located: @@ -1223,7 +1223,7 @@ Subscriber output: == APP == Subscriber received: Order { OrderId = 10 } ``` -#### `pubsub.yaml` component file +##### `pubsub.yaml` component file When you run `dapr init`, Dapr creates a default Redis `pubsub.yaml` and runs a Redis container on your local machine, located: @@ -1385,7 +1385,7 @@ Subscriber output: == APP == 2022-03-07 13:31:37.919 INFO 43512 --- [nio-8080-exec-2] c.s.c.OrderProcessingServiceController : Subscriber received: 10 ``` -#### `pubsub.yaml` component file +##### `pubsub.yaml` component file When you run `dapr init`, Dapr creates a default Redis `pubsub.yaml` and runs a Redis container on your local machine, located: @@ -1543,7 +1543,7 @@ Subscriber output: Note: the order in which they are received may vary. -#### `pubsub.yaml` component file +##### `pubsub.yaml` component file When you run `dapr init`, Dapr creates a default Redis `pubsub.yaml` and runs a Redis container on your local machine, located: diff --git a/daprdocs/content/en/getting-started/quickstarts/serviceinvocation-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/serviceinvocation-quickstart.md index bd866aa2478..08014b5e10b 100644 --- a/daprdocs/content/en/getting-started/quickstarts/serviceinvocation-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/serviceinvocation-quickstart.md @@ -112,7 +112,7 @@ Exited App successfully Running `dapr run -f .` in this Quickstart started both the [subscriber]({{< ref "#order-processor-service" >}}) and [publisher]({{< ref "#checkout-service" >}}) applications using the `dapr.yaml` Multi-App Run template file. -#### `dapr.yaml` Multi-App Run template file +##### `dapr.yaml` Multi-App Run template file Running the [Multi-App Run template file]({{< ref multi-app-dapr-run >}}) with `dapr run -f .` starts all applications in your project. In this Quickstart, the `dapr.yaml` file contains the following: @@ -128,7 +128,7 @@ apps: command: ["python3", "app.py"] ``` -#### `order-processor` service +##### `order-processor` service The `order-processor` service receives the call from the `checkout` service: @@ -247,7 +247,7 @@ Exited App successfully Running `dapr run -f .` in this Quickstart started both the [subscriber]({{< ref "#order-processor-service" >}}) and [publisher]({{< ref "#checkout-service" >}}) applications using the `dapr.yaml` Multi-App Run template file. -#### `dapr.yaml` Multi-App Run template file +##### `dapr.yaml` Multi-App Run template file Running the [Multi-App Run template file]({{< ref multi-app-dapr-run >}}) with `dapr run -f .` starts all applications in your project. In this Quickstart, the `dapr.yaml` file contains the following: @@ -263,7 +263,7 @@ apps: command: ["npm", "start"] ``` -#### `order-processor` service +##### `order-processor` service The `order-processor` service receives the call from the `checkout` service: @@ -274,7 +274,7 @@ app.post('/orders', (req, res) => { }); ``` -#### `checkout` service +##### `checkout` service In the `checkout` service, you'll notice there's no need to rewrite your app code to use Dapr's service invocation. You can enable service invocation by simply adding the `dapr-app-id` header, which specifies the ID of the target service. @@ -377,7 +377,7 @@ Exited App successfully Running `dapr run -f .` in this Quickstart started both the [subscriber]({{< ref "#order-processor-service" >}}) and [publisher]({{< ref "#checkout-service" >}}) applications using the `dapr.yaml` Multi-App Run template file. -#### `dapr.yaml` Multi-App Run template file +##### `dapr.yaml` Multi-App Run template file Running the [Multi-App Run template file]({{< ref multi-app-dapr-run >}}) with `dapr run -f .` starts all applications in your project. In this Quickstart, the `dapr.yaml` file contains the following: @@ -393,7 +393,7 @@ apps: command: ["dotnet", "run"] ``` -#### `order-processor` service +##### `order-processor` service The `order-processor` service receives the call from the `checkout` service: @@ -405,7 +405,7 @@ app.MapPost("/orders", (Order order) => }); ``` -#### `checkout` service +##### `checkout` service In the Program.cs file for the `checkout` service, you'll notice there's no need to rewrite your app code to use Dapr's service invocation. You can enable service invocation by simply adding the `dapr-app-id` header, which specifies the ID of the target service. @@ -511,7 +511,7 @@ Exited App successfully Running `dapr run -f .` in this Quickstart started both the [subscriber]({{< ref "#order-processor-service" >}}) and [publisher]({{< ref "#checkout-service" >}}) applications using the `dapr.yaml` Multi-App Run template file. -#### `dapr.yaml` Multi-App Run template file +##### `dapr.yaml` Multi-App Run template file Running the [Multi-App Run template file]({{< ref multi-app-dapr-run >}}) with `dapr run -f .` starts all applications in your project. In this Quickstart, the `dapr.yaml` file contains the following: @@ -527,7 +527,7 @@ apps: command: ["java", "-jar", "target/CheckoutService-0.0.1-SNAPSHOT.jar"] ``` -#### `order-processor` service +##### `order-processor` service The `order-processor` service receives the call from the `checkout` service: @@ -538,7 +538,7 @@ public String processOrders(@RequestBody Order body) { } ``` -#### `checkout` service +##### `checkout` service In the `checkout` service, you'll notice there's no need to rewrite your app code to use Dapr's service invocation. You can enable service invocation by simply adding the `dapr-app-id` header, which specifies the ID of the target service. @@ -640,7 +640,7 @@ Exited App successfully Running `dapr run -f .` in this Quickstart started both the [subscriber]({{< ref "#order-processor-service" >}}) and [publisher]({{< ref "#checkout-service" >}}) applications using the `dapr.yaml` Multi-App Run template file. -#### `dapr.yaml` Multi-App Run template file +##### `dapr.yaml` Multi-App Run template file Running the [Multi-App Run template file]({{< ref multi-app-dapr-run >}}) with `dapr run -f .` starts all applications in your project. In this Quickstart, the `dapr.yaml` file contains the following: @@ -656,7 +656,7 @@ apps: command: ["go", "run", "."] ``` -#### `order-processor` service +##### `order-processor` service In the `order-processor` service, each order is received via an HTTP POST request and processed by the `getOrder` function. @@ -670,7 +670,7 @@ func getOrder(w http.ResponseWriter, r *http.Request) { } ``` -#### `checkout` service +##### `checkout` service In the `checkout` service, you'll notice there's no need to rewrite your app code to use Dapr's service invocation. You can enable service invocation by simply adding the `dapr-app-id` header, which specifies the ID of the target service. diff --git a/daprdocs/content/en/getting-started/quickstarts/statemanagement-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/statemanagement-quickstart.md index 7c80c4c2304..e360c660076 100644 --- a/daprdocs/content/en/getting-started/quickstarts/statemanagement-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/statemanagement-quickstart.md @@ -102,7 +102,7 @@ Order-processor output: == APP == INFO:root:Deleting Order: {'orderId': '4'} ``` -#### `dapr.yaml` Multi-App Run template file +##### `dapr.yaml` Multi-App Run template file When you run `dapr init`, Dapr creates a default [Multi-App Run template file]({{< ref multi-app-dapr-run >}}) named `dapr.yaml`. Running `dapr run -f` starts all applications in your project. In this sample, the `dapr.yaml` file contains the following: @@ -116,7 +116,7 @@ apps: command: ["dotnet", "run"] ``` -#### `statestore.yaml` component file +##### `statestore.yaml` component file When you run `dapr init`, Dapr also creates a default Redis `statestore.yaml` and runs a Redis container on your local machine, located: @@ -239,7 +239,7 @@ Order-processor output: == APP == Deleting Order: { orderId: 5 } ``` -#### `dapr.yaml` Multi-App Run template file +##### `dapr.yaml` Multi-App Run template file When you run `dapr init`, Dapr creates a default Multi-App Run template file named `dapr.yaml`. Running `dapr run -f` starts all applications in your project. In this sample, the `dapr.yaml` file contains the following: @@ -253,7 +253,7 @@ apps: command: ["dotnet", "run"] ``` -#### `statestore.yaml` component file +##### `statestore.yaml` component file When you run `dapr init`, Dapr creates a default Redis `statestore.yaml` and runs a Redis container on your local machine, located: @@ -370,7 +370,7 @@ Order-processor output: == APP == Deleting Order: Order { orderId = 5 } ``` -#### `dapr.yaml` Multi-App Run template file +##### `dapr.yaml` Multi-App Run template file When you run `dapr init`, Dapr creates a default Multi-App Run template file named `dapr.yaml`. Running `dapr run -f` starts all applications in your project. In this sample, the `dapr.yaml` file contains the following: @@ -384,7 +384,7 @@ apps: command: ["dotnet", "run"] ``` -#### `statestore.yaml` component file +##### `statestore.yaml` component file When you run `dapr init`, Dapr creates a default Redis `statestore.yaml` and runs a Redis container on your local machine, located: @@ -506,7 +506,7 @@ Order-processor output: == APP == INFO:root:Deleting Order: {'orderId': '4'} ``` -#### `dapr.yaml` Multi-App Run template file +##### `dapr.yaml` Multi-App Run template file When you run `dapr init`, Dapr creates a default Multi-App Run template file named `dapr.yaml`. Running `dapr run -f` starts all applications in your project. In this sample, the `dapr.yaml` file contains the following: @@ -520,7 +520,7 @@ apps: command: ["dotnet", "run"] ``` -#### `statestore.yaml` component file +##### `statestore.yaml` component file When you run `dapr init`, Dapr creates a default Redis `statestore.yaml` and runs a Redis container on your local machine, located: @@ -638,7 +638,7 @@ Order-processor output: == APP == 2022/04/01 09:16:03 Deleting Order: {"orderId":5} ``` -#### `dapr.yaml` Multi-App Run template file +##### `dapr.yaml` Multi-App Run template file When you run `dapr init`, Dapr creates a default Multi-App Run template file named `dapr.yaml`. Running `dapr run -f` starts all applications in your project. In this sample, the `dapr.yaml` file contains the following: @@ -652,7 +652,7 @@ apps: command: ["dotnet", "run"] ``` -#### `statestore.yaml` component file +##### `statestore.yaml` component file When you run `dapr init`, Dapr creates a default Redis `statestore.yaml` and runs a Redis container on your local machine, located: @@ -776,7 +776,7 @@ Order-processor output: == APP == INFO:root:Deleting Order: {'orderId': '4'} ``` -#### `statestore.yaml` component file +##### `statestore.yaml` component file When you run `dapr init`, Dapr creates a default Redis `statestore.yaml` and runs a Redis container on your local machine, located: @@ -898,7 +898,7 @@ Order-processor output: == APP == Deleting Order: { orderId: 5 } ``` -#### `statestore.yaml` component file +##### `statestore.yaml` component file When you run `dapr init`, Dapr creates a default Redis `statestore.yaml` and runs a Redis container on your local machine, located: @@ -1015,7 +1015,7 @@ Order-processor output: == APP == Deleting Order: Order { orderId = 5 } ``` -#### `statestore.yaml` component file +##### `statestore.yaml` component file When you run `dapr init`, Dapr creates a default Redis `statestore.yaml` and runs a Redis container on your local machine, located: @@ -1137,7 +1137,7 @@ Order-processor output: == APP == INFO:root:Deleting Order: {'orderId': '4'} ``` -#### `statestore.yaml` component file +##### `statestore.yaml` component file When you run `dapr init`, Dapr creates a default Redis `statestore.yaml` and runs a Redis container on your local machine, located: @@ -1255,7 +1255,7 @@ Order-processor output: == APP == 2022/04/01 09:16:03 Deleting Order: {"orderId":5} ``` -#### `statestore.yaml` component file +##### `statestore.yaml` component file When you run `dapr init`, Dapr creates a default Redis `statestore.yaml` and runs a Redis container on your local machine, located: From eecadf3de60b50ef2861eb5045a889ec3ac3ae1a Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Thu, 21 Sep 2023 11:24:42 -0400 Subject: [PATCH 057/162] start bindings quickstart ahead of changes Signed-off-by: Hannah Hunter --- .../quickstarts/bindings-quickstart.md | 1072 ++++++++++++++++- 1 file changed, 1071 insertions(+), 1 deletion(-) diff --git a/daprdocs/content/en/getting-started/quickstarts/bindings-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/bindings-quickstart.md index b818c434836..64731de6852 100644 --- a/daprdocs/content/en/getting-started/quickstarts/bindings-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/bindings-quickstart.md @@ -11,10 +11,1080 @@ Let's take a look at Dapr's [Bindings building block]({{< ref bindings >}}). Usi - Trigger your app with events coming in from external systems. - Interface with external systems. -In this Quickstart, you will schedule a batch script to run every 10 seconds using an input [Cron]({{< ref cron.md >}}) binding. The script processes a JSON file and outputs data to a SQL database using the [PostgreSQL]({{< ref postgresql.md >}}) Dapr binding. +In this Quickstart, you will schedule a batch script to run every 10 seconds using an input [Cron]({{< ref cron.md >}}) binding. The script processes a JSON file and outputs data to a SQL database using the [PostgreSQL]({{< ref postgresql.md >}}) Dapr binding. You can run this quickstart by either: +- [Running all applications in this sample simultaneously with the Multi-App Run template file]({{< ref "#run-using-multi-app-run" >}}), or +- [Running one application at a time]({{< ref "#run-one-application-at-a-time" >}}) +## Run using Multi-App Run + +{{% alert title="Note" color="primary" %}} + [Multi-App Run]({{< ref multi-app-dapr-run >}}) is currently a preview feature only supported in Linux/MacOS. +{{% /alert %}} + +Select your preferred language-specific Dapr SDK before proceeding with the Quickstart. + +{{< tabs "Python" "JavaScript" ".NET" "Java" "Go" >}} + +{{% codetab %}} + +### Pre-requisites + +For this example, you will need: + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). +- [Python 3.7+ installed](https://www.python.org/downloads/). + +- [Docker Desktop](https://www.docker.com/products/docker-desktop) + + +### Step 1: Set up the environment + +Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/bindings). + +```bash +git clone https://github.com/dapr/quickstarts.git +``` + +### Step 2: Run PostgreSQL Docker container locally + +Run the [PostgreSQL instance](https://www.postgresql.org/) locally in a Docker container on your machine. The Quickstart sample includes a Docker Compose file to locally customize, build, run, and initialize the `postgres` container with a default `orders` table. + +In a terminal window, from the root of the Quickstarts clone directory, navigate to the `bindings/db` directory. + +```bash +cd bindings/db +``` + +Run the following command to set up the container: + +```bash +docker compose up +``` + +Verify that the container is running locally. + +```bash +docker ps +``` + +The output should include: + +```bash +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +55305d1d378b postgres "docker-entrypoint.s…" 3 seconds ago Up 2 seconds 0.0.0.0:5432->5432/tcp sql_db +``` + +### Step 3: Schedule a Cron job and write to the database + +In a new terminal window, navigate to the SDK directory. + +```bash +cd bindings/python/sdk/batch +``` + +Install the dependencies: + +```bash +pip3 install -r requirements.txt +``` + +Run the `batch-sdk` service alongside a Dapr sidecar. + +```bash +dapr run --app-id batch-sdk --app-port 50051 --resources-path ../../../components -- python3 app.py +``` + +> **Note**: Since Python3.exe is not defined in Windows, you may need to use `python app.py` instead of `python3 app.py`. + +The code inside the `process_batch` function is executed every 10 seconds (defined in [`binding-cron.yaml`]({{< ref "#componentsbinding-cronyaml-component-file" >}}) in the `components` directory). The binding trigger looks for a route called via HTTP POST in your application by the Dapr sidecar. + +```python +# Triggered by Dapr input binding +@app.route('/' + cron_binding_name, methods=['POST']) +def process_batch(): +``` + +The `batch-sdk` service uses the PostgreSQL output binding defined in the [`binding-postgresql.yaml`]({{< ref "#componentbinding-postgresyaml-component-file" >}}) component to insert the `OrderId`, `Customer`, and `Price` records into the `orders` table. + +```python +with DaprClient() as d: + sqlCmd = ('insert into orders (orderid, customer, price) values ' + + '(%s, \'%s\', %s)' % (order_line['orderid'], + order_line['customer'], + order_line['price'])) + payload = {'sql': sqlCmd} + + print(sqlCmd, flush=True) + + try: + # Insert order using Dapr output binding via HTTP Post + resp = d.invoke_binding(binding_name=sql_binding, operation='exec', + binding_metadata=payload, data='') + return resp + except Exception as e: + print(e, flush=True) + raise SystemExit(e) +``` + +### Step 4: View the output of the job + +Notice, as specified above, the code invokes the output binding with the `OrderId`, `Customer`, and `Price` as a payload. + +Your output binding's `print` statement output: + +``` +== APP == Processing batch.. +== APP == insert into orders (orderid, customer, price) values (1, 'John Smith', 100.32) +== APP == insert into orders (orderid, customer, price) values (2, 'Jane Bond', 15.4) +== APP == insert into orders (orderid, customer, price) values (3, 'Tony James', 35.56) +== APP == Finished processing batch +``` + +In a new terminal, verify the same data has been inserted into the database. Navigate to the `bindings/db` directory. + +```bash +cd bindings/db +``` + +Run the following to start the interactive *psql* CLI: + +```bash +docker exec -i -t postgres psql --username postgres -p 5432 -h localhost --no-password +``` + +At the `admin=#` prompt, change to the `orders` table: + +```bash +\c orders; +``` + +At the `orders=#` prompt, select all rows: + +```bash +select * from orders; +``` + +The output should look like this: + +``` + orderid | customer | price +---------+------------+-------- + 1 | John Smith | 100.32 + 2 | Jane Bond | 15.4 + 3 | Tony James | 35.56 +``` + +#### `components\binding-cron.yaml` component file + +When you execute the `dapr run` command and specify the component path, the Dapr sidecar: + +- Initiates the Cron [binding building block]({{< ref bindings >}}) +- Calls the binding endpoint (`batch`) every 10 seconds + +The Cron `binding-cron.yaml` file included for this Quickstart contains the following: + +```yaml +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: cron + namespace: quickstarts +spec: + type: bindings.cron + version: v1 + metadata: + - name: schedule + value: "@every 10s" # valid cron schedule + - name: direction + value: "input" # direction of the cron binding +``` + +**Note:** The `metadata` section of `binding-cron.yaml` contains a [Cron expression]({{< ref cron.md >}}) that specifies how often the binding is invoked. + +#### `component\binding-postgresql.yaml` component file + +When you execute the `dapr run` command and specify the component path, the Dapr sidecar: + +- Initiates the PostgreSQL [binding building block]({{< ref postgresql.md >}}) +- Connects to PostgreSQL using the settings specified in the `binding-postgresql.yaml` file + +With the `binding-postgresql.yaml` component, you can easily swap out the backend database [binding]({{< ref supported-bindings.md >}}) without making code changes. + +The PostgreSQL `binding-postgresql.yaml` file included for this Quickstart contains the following: + +```yaml +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: sqldb + namespace: quickstarts +spec: + type: bindings.postgresql + version: v1 + metadata: + - name: url # Required + value: "user=postgres password=docker host=localhost port=5432 dbname=orders pool_min_conns=1 pool_max_conns=10" + - name: direction + value: "output" # direction of the postgresql binding +``` + +In the YAML file: + +- `spec/type` specifies that PostgreSQL is used for this binding. +- `spec/metadata` defines the connection to the PostgreSQL instance used by the component. + +{{% /codetab %}} + + +{{% codetab %}} + +### Pre-requisites + +For this example, you will need: + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). +- [Latest Node.js installed](https://nodejs.org/download/). + +- [Docker Desktop](https://www.docker.com/products/docker-desktop) + + +### Step 1: Set up the environment + +Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/bindings). + +```bash +git clone https://github.com/dapr/quickstarts.git +``` + +### Step 2: Run PostgreSQL Docker container locally + +Run the [PostgreSQL instance](https://www.postgresql.org/) locally in a Docker container on your machine. The Quickstart sample includes a Docker Compose file to locally customize, build, run, and initialize the `postgres` container with a default `orders` table. + +In a terminal window, from the root of the Quickstarts clone directory, navigate to the `bindings/db` directory. + +```bash +cd bindings/db +``` + +Run the following command to set up the container: + +```bash +docker compose up +``` + +Verify that the container is running locally. + +```bash +docker ps +``` + +The output should include: + +```bash +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +55305d1d378b postgres "docker-entrypoint.s…" 3 seconds ago Up 2 seconds 0.0.0.0:5432->5432/tcp sql_db +``` + +### Step 3: Schedule a Cron job and write to the database + +In a new terminal window, navigate to the SDK directory. + +```bash +cd bindings/javascript/sdk/batch +``` + +Install the dependencies: + +```bash +npm install +``` + +Run the `batch-sdk` service alongside a Dapr sidecar. + +```bash +dapr run --app-id batch-sdk --app-port 5002 --dapr-http-port 3500 --resources-path ../../../components -- node index.js +``` + +The code inside the `process_batch` function is executed every 10 seconds (defined in [`binding-cron.yaml`]({{< ref "#componentsbinding-cronyaml-component-file" >}}) in the `components` directory). The binding trigger looks for a route called via HTTP POST in your application by the Dapr sidecar. + +```javascript +async function start() { + await server.binding.receive(cronBindingName,processBatch); + await server.start(); +} +``` + +The `batch-sdk` service uses the PostgreSQL output binding defined in the [`binding-postgresql.yaml`]({{< ref "##componentsbinding-postgresyaml-component-file" >}}) component to insert the `OrderId`, `Customer`, and `Price` records into the `orders` table. + +```javascript +async function processBatch(){ + const loc = '../../orders.json'; + fs.readFile(loc, 'utf8', (err, data) => { + const orders = JSON.parse(data).orders; + orders.forEach(order => { + let sqlCmd = `insert into orders (orderid, customer, price) values (${order.orderid}, '${order.customer}', ${order.price});`; + let payload = `{ "sql": "${sqlCmd}" } `; + console.log(payload); + client.binding.send(postgresBindingName, "exec", "", JSON.parse(payload)); + }); + console.log('Finished processing batch'); + }); + return 0; +} +``` + +### Step 4: View the output of the job + +Notice, as specified above, the code invokes the output binding with the `OrderId`, `Customer`, and `Price` as a payload. + +Your output binding's `print` statement output: + +``` +== APP == Processing batch.. +== APP == insert into orders (orderid, customer, price) values(1, 'John Smith', 100.32) +== APP == insert into orders (orderid, customer, price) values(2, 'Jane Bond', 15.4) +== APP == insert into orders (orderid, customer, price) values(3, 'Tony James', 35.56) +``` + +In a new terminal, verify the same data has been inserted into the database. Navigate to the `bindings/db` directory. + +```bash +cd bindings/db +``` + +Run the following to start the interactive Postgres CLI: + +```bash +docker exec -i -t postgres psql --username postgres -p 5432 -h localhost --no-password +``` + +At the `admin=#` prompt, change to the `orders` table: + +```bash +\c orders; +``` + +At the `orders=#` prompt, select all rows: + +```bash +select * from orders; +``` + +The output should look like this: + +``` + orderid | customer | price +---------+------------+-------- + 1 | John Smith | 100.32 + 2 | Jane Bond | 15.4 + 3 | Tony James | 35.56 +``` + +#### `components\binding-cron.yaml` component file + +When you execute the `dapr run` command and specify the component path, the Dapr sidecar: + +- Initiates the Cron [binding building block]({{< ref bindings >}}) +- Calls the binding endpoint (`batch`) every 10 seconds + +The Cron `binding-cron.yaml` file included for this Quickstart contains the following: + +```yaml +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: cron + namespace: quickstarts +spec: + type: bindings.cron + version: v1 + metadata: + - name: schedule + value: "@every 10s" # valid cron schedule + - name: direction + value: "input" # direction of the cron binding +``` + +**Note:** The `metadata` section of `binding-cron.yaml` contains a [Cron expression]({{< ref cron.md >}}) that specifies how often the binding is invoked. + +#### `component\binding-postgresql.yaml` component file + +When you execute the `dapr run` command and specify the component path, the Dapr sidecar: + +- Initiates the PostgreSQL [binding building block]({{< ref postgresql.md >}}) +- Connects to PostgreSQL using the settings specified in the `binding-postgresql.yaml` file + +With the `binding-postgresql.yaml` component, you can easily swap out the backend database [binding]({{< ref supported-bindings.md >}}) without making code changes. + +The PostgreSQL `binding-postgresql.yaml` file included for this Quickstart contains the following: + +```yaml +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: sqldb + namespace: quickstarts +spec: + type: bindings.postgresql + version: v1 + metadata: + - name: url # Required + value: "user=postgres password=docker host=localhost port=5432 dbname=orders pool_min_conns=1 pool_max_conns=10" + - name: direction + value: "output" # direction of the postgresql binding +``` + +In the YAML file: + +- `spec/type` specifies that PostgreSQL is used for this binding. +- `spec/metadata` defines the connection to the PostgreSQL instance used by the component. + +{{% /codetab %}} + + +{{% codetab %}} + +### Pre-requisites + +For this example, you will need: + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). +- [.NET SDK or .NET 6 SDK installed](https://dotnet.microsoft.com/download). + +- [Docker Desktop](https://www.docker.com/products/docker-desktop) + + +### Step 1: Set up the environment + +Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/bindings). + +```bash +git clone https://github.com/dapr/quickstarts.git +``` + +### Step 2: Run PostgreSQL Docker container locally + +Run the [PostgreSQL instance](https://www.postgresql.org/) locally in a Docker container on your machine. The Quickstart sample includes a Docker Compose file to locally customize, build, run, and initialize the `postgres` container with a default `orders` table. + +In a terminal window, from the root of the Quickstarts clone directory, navigate to the `bindings/db` directory. + +```bash +cd bindings/db +``` + +Run the following command to set up the container: + +```bash +docker compose up +``` + +Verify that the container is running locally. + +```bash +docker ps +``` + +The output should include: + +```bash +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +55305d1d378b postgres "docker-entrypoint.s…" 3 seconds ago Up 2 seconds 0.0.0.0:5432->5432/tcp sql_db +``` + +### Step 3: Schedule a Cron job and write to the database + +In a new terminal window, navigate to the SDK directory. + +```bash +cd bindings/csharp/sdk/batch +``` + +Install the dependencies: + +```bash +dotnet restore +dotnet build batch.csproj +``` + +Run the `batch-sdk` service alongside a Dapr sidecar. + +```bash +dapr run --app-id batch-sdk --app-port 7002 --resources-path ../../../components -- dotnet run +``` + +The code inside the `process_batch` function is executed every 10 seconds (defined in [`binding-cron.yaml`]({{< ref "#componentsbinding-cronyaml-component-file" >}}) in the `components` directory). The binding trigger looks for a route called via HTTP POST in your application by the Dapr sidecar. + +```csharp +app.MapPost("/" + cronBindingName, async () => { +// ... +}); +``` + +The `batch-sdk` service uses the PostgreSQL output binding defined in the [`binding-postgresql.yaml`]({{< ref "#componentbinding-postgresyaml-component-file" >}}) component to insert the `OrderId`, `Customer`, and `Price` records into the `orders` table. + +```csharp +// ... +string jsonFile = File.ReadAllText("../../../orders.json"); +var ordersArray = JsonSerializer.Deserialize(jsonFile); +using var client = new DaprClientBuilder().Build(); +foreach(Order ord in ordersArray?.orders ?? new Order[] {}){ + var sqlText = $"insert into orders (orderid, customer, price) values ({ord.OrderId}, '{ord.Customer}', {ord.Price});"; + var command = new Dictionary(){ + {"sql", + sqlText} + }; +// ... +} + +// Insert order using Dapr output binding via Dapr Client SDK +await client.InvokeBindingAsync(bindingName: sqlBindingName, operation: "exec", data: "", metadata: command); +``` + +### Step 4: View the output of the job + +Notice, as specified above, the code invokes the output binding with the `OrderId`, `Customer`, and `Price` as a payload. + +Your output binding's `print` statement output: + +``` +== APP == Processing batch.. +== APP == insert into orders (orderid, customer, price) values (1, 'John Smith', 100.32); +== APP == insert into orders (orderid, customer, price) values (2, 'Jane Bond', 15.4); +== APP == insert into orders (orderid, customer, price) values (3, 'Tony James', 35.56); +== APP == Finished processing batch +``` + +In a new terminal, verify the same data has been inserted into the database. Navigate to the `bindings/db` directory. + +```bash +cd bindings/db +``` + +Run the following to start the interactive Postgres CLI: + +```bash +docker exec -i -t postgres psql --username postgres -p 5432 -h localhost --no-password +``` + +At the `admin=#` prompt, change to the `orders` table: + +```bash +\c orders; +``` + +At the `orders=#` prompt, select all rows: + +```bash +select * from orders; +``` + +The output should look like this: + +``` + orderid | customer | price +---------+------------+-------- + 1 | John Smith | 100.32 + 2 | Jane Bond | 15.4 + 3 | Tony James | 35.56 +``` + +#### `components\binding-cron.yaml` component file + +When you execute the `dapr run` command and specify the component path, the Dapr sidecar: + +- Initiates the Cron [binding building block]({{< ref bindings >}}) +- Calls the binding endpoint (`batch`) every 10 seconds + +The Cron `binding-cron.yaml` file included for this Quickstart contains the following: + +```yaml +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: cron + namespace: quickstarts +spec: + type: bindings.cron + version: v1 + metadata: + - name: schedule + value: "@every 10s" # valid cron schedule + - name: direction + value: "input" # direction of the cron binding +``` + +**Note:** The `metadata` section of `binding-cron.yaml` contains a [Cron expression]({{< ref cron.md >}}) that specifies how often the binding is invoked. + +#### `component\binding-postgresql.yaml` component file + +When you execute the `dapr run` command and specify the component path, the Dapr sidecar: + +- Initiates the PostgreSQL [binding building block]({{< ref postgresql.md >}}) +- Connects to PostgreSQL using the settings specified in the `binding-postgresql.yaml` file + +With the `binding-postgresql.yaml` component, you can easily swap out the backend database [binding]({{< ref supported-bindings.md >}}) without making code changes. + +The PostgreSQL `binding-postgresql.yaml` file included for this Quickstart contains the following: + +```yaml +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: sqldb + namespace: quickstarts +spec: + type: bindings.postgresql + version: v1 + metadata: + - name: url # Required + value: "user=postgres password=docker host=localhost port=5432 dbname=orders pool_min_conns=1 pool_max_conns=10" + - name: direction + value: "output" # direction of the postgresql binding +``` + +In the YAML file: + +- `spec/type` specifies that PostgreSQL is used for this binding. +- `spec/metadata` defines the connection to the PostgreSQL instance used by the component. + +{{% /codetab %}} + + +{{% codetab %}} + +### Pre-requisites + +For this example, you will need: + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). +- Java JDK 11 (or greater): + - [Oracle JDK](https://www.oracle.com/java/technologies/downloads), or + - OpenJDK +- [Apache Maven](https://maven.apache.org/install.html), version 3.x. + +- [Docker Desktop](https://www.docker.com/products/docker-desktop) + + +### Step 1: Set up the environment + +Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/bindings). + +```bash +git clone https://github.com/dapr/quickstarts.git +``` + +### Step 2: Run PostgreSQL Docker container locally + +Run the [PostgreSQL instance](https://www.postgresql.org/) locally in a Docker container on your machine. The Quickstart sample includes a Docker Compose file to locally customize, build, run, and initialize the `postgres` container with a default `orders` table. + +In a terminal window, from the root of the Quickstarts clone directory, navigate to the `bindings/db` directory. + +```bash +cd bindings/db +``` + +Run the following command to set up the container: + +```bash +docker compose up +``` + +Verify that the container is running locally. + +```bash +docker ps +``` + +The output should include: + +```bash +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +55305d1d378b postgres "docker-entrypoint.s…" 3 seconds ago Up 2 seconds 0.0.0.0:5432->5432/tcp sql_db +``` + +### Step 3: Schedule a Cron job and write to the database + +In a new terminal window, navigate to the SDK directory. + +```bash +cd bindings/java/sdk/batch +``` + +Install the dependencies: + +```bash +mvn clean install +``` + +Run the `batch-sdk` service alongside a Dapr sidecar. + +```bash +dapr run --app-id batch-sdk --app-port 8080 --resources-path ../../../components -- java -jar target/BatchProcessingService-0.0.1-SNAPSHOT.jar +``` + +The code inside the `process_batch` function is executed every 10 seconds (defined in [`binding-cron.yaml`]({{< ref "#componentsbinding-cronyaml-component-file" >}}) in the `components` directory). The binding trigger looks for a route called via HTTP POST in your application by the Dapr sidecar. + +```java +@PostMapping(path = cronBindingPath, consumes = MediaType.ALL_VALUE) +public ResponseEntity processBatch() throws IOException, Exception +``` + +The `batch-sdk` service uses the PostgreSQL output binding defined in the [`binding-postgresql.yaml`]({{< ref "#componentbinding-postgresyaml-component-file" >}}) component to insert the `OrderId`, `Customer`, and `Price` records into the `orders` table. + +```java +try (DaprClient client = new DaprClientBuilder().build()) { + + for (Order order : ordList.orders) { + String sqlText = String.format( + "insert into orders (orderid, customer, price) " + + "values (%s, '%s', %s);", + order.orderid, order.customer, order.price); + logger.info(sqlText); + + Map metadata = new HashMap(); + metadata.put("sql", sqlText); + + // Invoke sql output binding using Dapr SDK + client.invokeBinding(sqlBindingName, "exec", null, metadata).block(); + } + + logger.info("Finished processing batch"); + + return ResponseEntity.ok("Finished processing batch"); +} +``` + +### Step 4: View the output of the job + +Notice, as specified above, the code invokes the output binding with the `OrderId`, `Customer`, and `Price` as a payload. + +Your output binding's `print` statement output: + +``` +== APP == 2022-06-22 16:39:17.012 INFO 35772 --- [nio-8080-exec-4] c.s.c.BatchProcessingServiceController : Processing batch.. +== APP == 2022-06-22 16:39:17.268 INFO 35772 --- [nio-8080-exec-4] c.s.c.BatchProcessingServiceController : insert into orders (orderid, customer, price) values (1, 'John Smith', 100.32); +== APP == 2022-06-22 16:39:17.838 INFO 35772 --- [nio-8080-exec-4] c.s.c.BatchProcessingServiceController : insert into orders (orderid, customer, price) values (2, 'Jane Bond', 15.4); +== APP == 2022-06-22 16:39:17.844 INFO 35772 --- [nio-8080-exec-4] c.s.c.BatchProcessingServiceController : insert into orders (orderid, customer, price) values (3, 'Tony James', 35.56); +== APP == 2022-06-22 16:39:17.848 INFO 35772 --- [nio-8080-exec-4] c.s.c.BatchProcessingServiceController : Finished processing batch +``` + +In a new terminal, verify the same data has been inserted into the database. Navigate to the `bindings/db` directory. + +```bash +cd bindings/db +``` + +Run the following to start the interactive Postgres CLI: + +```bash +docker exec -i -t postgres psql --username postgres -p 5432 -h localhost --no-password +``` + +At the `admin=#` prompt, change to the `orders` table: + +```bash +\c orders; +``` + +At the `orders=#` prompt, select all rows: + +```bash +select * from orders; +``` + +The output should look like this: + +``` + orderid | customer | price +---------+------------+-------- + 1 | John Smith | 100.32 + 2 | Jane Bond | 15.4 + 3 | Tony James | 35.56 +``` + +#### `components\binding-cron.yaml` component file + +When you execute the `dapr run` command and specify the component path, the Dapr sidecar: + +- Initiates the Cron [binding building block]({{< ref bindings >}}) +- Calls the binding endpoint (`batch`) every 10 seconds + +The Cron `binding-cron.yaml` file included for this Quickstart contains the following: + +```yaml +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: cron + namespace: quickstarts +spec: + type: bindings.cron + version: v1 + metadata: + - name: schedule + value: "@every 10s" # valid cron schedule + - name: direction + value: "input" # direction of the cron binding +``` + +**Note:** The `metadata` section of `binding-cron.yaml` contains a [Cron expression]({{< ref cron.md >}}) that specifies how often the binding is invoked. + +#### `component\binding-postgresql.yaml` component file + +When you execute the `dapr run` command and specify the component path, the Dapr sidecar: + +- Initiates the PostgreSQL [binding building block]({{< ref postgresql.md >}}) +- Connects to PostgreSQL using the settings specified in the `binding-postgresql.yaml` file + +With the `binding-postgresql.yaml` component, you can easily swap out the backend database [binding]({{< ref supported-bindings.md >}}) without making code changes. + +The PostgreSQL `binding-postgresql.yaml` file included for this Quickstart contains the following: + +```yaml +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: sqldb + namespace: quickstarts +spec: + type: bindings.postgresql + version: v1 + metadata: + - name: url # Required + value: "user=postgres password=docker host=localhost port=5432 dbname=orders pool_min_conns=1 pool_max_conns=10" + - name: direction + value: "output" # direction of the postgresql binding +``` + +In the YAML file: + +- `spec/type` specifies that PostgreSQL is used for this binding. +- `spec/metadata` defines the connection to the PostgreSQL instance used by the component. + +{{% /codetab %}} + + +{{% codetab %}} + +### Pre-requisites + +For this example, you will need: + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). +- [Latest version of Go](https://go.dev/dl/). + +- [Docker Desktop](https://www.docker.com/products/docker-desktop) + + +### Step 1: Set up the environment + +Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/bindings). + +```bash +git clone https://github.com/dapr/quickstarts.git +``` + +### Step 2: Run PostgreSQL Docker container locally + +Run the [PostgreSQL instance](https://www.postgresql.org/) locally in a Docker container on your machine. The Quickstart sample includes a Docker Compose file to locally customize, build, run, and initialize the `postgres` container with a default `orders` table. + +In a terminal window, from the root of the Quickstarts clone directory, navigate to the `bindings/db` directory. + +```bash +cd bindings/db +``` + +Run the following command to set up the container: + +```bash +docker compose up +``` + +Verify that the container is running locally. + +```bash +docker ps +``` + +The output should include: + +```bash +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +55305d1d378b postgres "docker-entrypoint.s…" 3 seconds ago Up 2 seconds 0.0.0.0:5432->5432/tcp sql_db +``` + +### Step 3: Schedule a Cron job and write to the database + +In a new terminal window, navigate to the SDK directory. + +```bash +cd bindings/go/sdk/batch +``` + +Install the dependencies: + +```bash +go build . +``` + +Run the `batch-sdk` service alongside a Dapr sidecar. + +```bash +dapr run --app-id batch-sdk --app-port 6002 --dapr-http-port 3502 --dapr-grpc-port 60002 --resources-path ../../../components -- go run . +``` + +The code inside the `process_batch` function is executed every 10 seconds (defined in [`binding-cron.yaml`]({{< ref "#componentsbinding-cronyaml-component-file" >}}) in the `components` directory). The binding trigger looks for a route called via HTTP POST in your application by the Dapr sidecar. + +```go +// Triggered by Dapr input binding +r.HandleFunc("/"+cronBindingName, processBatch).Methods("POST") +``` + +The `batch-sdk` service uses the PostgreSQL output binding defined in the [`binding-postgresql.yaml`]({{< ref "#componentbinding-postgresyaml-component-file" >}}) component to insert the `OrderId`, `Customer`, and `Price` records into the `orders` table. + +```go +func sqlOutput(order Order) (err error) { + + client, err := dapr.NewClient() + if err != nil { + return err + } + + ctx := context.Background() + + sqlCmd := fmt.Sprintf("insert into orders (orderid, customer, price) values (%d, '%s', %s);", order.OrderId, order.Customer, strconv.FormatFloat(order.Price, 'f', 2, 64)) + fmt.Println(sqlCmd) + + // Insert order using Dapr output binding via Dapr SDK + in := &dapr.InvokeBindingRequest{ + Name: sqlBindingName, + Operation: "exec", + Data: []byte(""), + Metadata: map[string]string{"sql": sqlCmd}, + } + err = client.InvokeOutputBinding(ctx, in) + if err != nil { + return err + } + + return nil +} +``` + +### Step 4: View the output of the job + +Notice, as specified above, the code invokes the output binding with the `OrderId`, `Customer`, and `Price` as a payload. + +Your output binding's `print` statement output: + +``` +== APP == Processing batch.. +== APP == insert into orders (orderid, customer, price) values(1, 'John Smith', 100.32) +== APP == insert into orders (orderid, customer, price) values(2, 'Jane Bond', 15.4) +== APP == insert into orders (orderid, customer, price) values(3, 'Tony James', 35.56) +``` + +In a new terminal, verify the same data has been inserted into the database. Navigate to the `bindings/db` directory. + +```bash +cd bindings/db +``` + +Run the following to start the interactive Postgres CLI: + +```bash +docker exec -i -t postgres psql --username postgres -p 5432 -h localhost --no-password +``` + +At the `admin=#` prompt, change to the `orders` table: + +```bash +\c orders; +``` + +At the `orders=#` prompt, select all rows: + +```bash +select * from orders; +``` + +The output should look like this: + +``` + orderid | customer | price +---------+------------+-------- + 1 | John Smith | 100.32 + 2 | Jane Bond | 15.4 + 3 | Tony James | 35.56 +``` + +#### `components\binding-cron.yaml` component file + +When you execute the `dapr run` command and specify the component path, the Dapr sidecar: + +- Initiates the Cron [binding building block]({{< ref bindings >}}) +- Calls the binding endpoint (`batch`) every 10 seconds + +The Cron `binding-cron.yaml` file included for this Quickstart contains the following: + +```yaml +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: cron + namespace: quickstarts +spec: + type: bindings.cron + version: v1 + metadata: + - name: schedule + value: "@every 10s" # valid cron schedule + - name: direction + value: "input" # direction of the cron binding +``` + +**Note:** The `metadata` section of `binding-cron.yaml` contains a [Cron expression]({{< ref cron.md >}}) that specifies how often the binding is invoked. + +#### `component\binding-postgresql.yaml` component file + +When you execute the `dapr run` command and specify the component path, the Dapr sidecar: + +- Initiates the PostgreSQL [binding building block]({{< ref postgresql.md >}}) +- Connects to PostgreSQL using the settings specified in the `binding-postgresql.yaml` file + +With the `binding-postgresql.yaml` component, you can easily swap out the backend database [binding]({{< ref supported-bindings.md >}}) without making code changes. + +The PostgreSQL `binding-postgresql.yaml` file included for this Quickstart contains the following: + +```yaml +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: sqldb + namespace: quickstarts +spec: + type: bindings.postgresql + version: v1 + metadata: + - name: url # Required + value: "user=postgres password=docker host=localhost port=5432 dbname=orders pool_min_conns=1 pool_max_conns=10" + - name: direction + value: "output" # direction of the postgresql binding +``` + +In the YAML file: + +- `spec/type` specifies that PostgreSQL is used for this binding. +- `spec/metadata` defines the connection to the PostgreSQL instance used by the component. + +{{% /codetab %}} + +{{< /tabs >}} + +## Run one application at a time + Select your preferred language-specific Dapr SDK before proceeding with the Quickstart. {{< tabs "Python" "JavaScript" ".NET" "Java" "Go" >}} From c2a1f0fafc3e0123bada34805ba1c825f4d197dd Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Fri, 22 Sep 2023 12:25:04 -0400 Subject: [PATCH 058/162] add to env list Signed-off-by: Hannah Hunter --- daprdocs/content/en/reference/environment/_index.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/daprdocs/content/en/reference/environment/_index.md b/daprdocs/content/en/reference/environment/_index.md index 762bb3592a8..88f018f22ba 100644 --- a/daprdocs/content/en/reference/environment/_index.md +++ b/daprdocs/content/en/reference/environment/_index.md @@ -26,4 +26,5 @@ The following table lists the environment variables used by the Dapr runtime, CL | OTEL_EXPORTER_OTLP_INSECURE | OpenTelemetry Tracing | Sets the connection to the endpoint as unencrypted. (`true`, `false`) | | OTEL_EXPORTER_OTLP_PROTOCOL | OpenTelemetry Tracing | The OTLP protocol to use Transport protocol. (`grpc`, `http/protobuf`, `http/json`) | | DAPR_COMPONENTS_SOCKETS_FOLDER | Dapr runtime and the .NET, Go, and Java pluggable component SDKs | The location or path where Dapr looks for Pluggable Components Unix Domain Socket files. If unset this location defaults to `/tmp/dapr-components-sockets` | -| DAPR_COMPONENTS_SOCKETS_EXTENSION | .NET and Java pluggable component SDKs | A per-SDK configuration that indicates the default file extension applied to socket files created by the SDKs. Not a Dapr-enforced behavior. | \ No newline at end of file +| DAPR_COMPONENTS_SOCKETS_EXTENSION | .NET and Java pluggable component SDKs | A per-SDK configuration that indicates the default file extension applied to socket files created by the SDKs. Not a Dapr-enforced behavior. | +| DAPR_PLACEMENT_METADATA_ENABLED | Dapr placement | A placement service that exposes placement table information. Set to `true` to enable. [Learn more about the Placement API]({{< ref placement_api.md >}}) | \ No newline at end of file From 564cfd8adb1eb856aa6f9a1526c1aab1ed7172ce Mon Sep 17 00:00:00 2001 From: Taction Date: Sat, 23 Sep 2023 01:28:24 +0800 Subject: [PATCH 059/162] Add placement table api doc (#3631) * add placement table api doc Signed-off-by: zhangchao * typo fix Signed-off-by: zhangchao * fix review Signed-off-by: zhangchao * fix example Signed-off-by: zhangchao --------- Signed-off-by: zhangchao Co-authored-by: Mark Fussell Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> --- .../en/concepts/dapr-services/placement.md | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/daprdocs/content/en/concepts/dapr-services/placement.md b/daprdocs/content/en/concepts/dapr-services/placement.md index 85d9c64481c..5cb1d999542 100644 --- a/daprdocs/content/en/concepts/dapr-services/placement.md +++ b/daprdocs/content/en/concepts/dapr-services/placement.md @@ -14,3 +14,72 @@ The placement service Docker container is started automatically as part of [`dap ## Kubernetes mode The placement service is deployed as part of `dapr init -k`, or via the Dapr Helm charts. For more information on running Dapr on Kubernetes, visit the [Kubernetes hosting page]({{< ref kubernetes >}}). + +## Placement tables + +There is an HTTP API `/placement/state` for placement service that exposes placement table information. The API is exposed on the sidecar on the same port as the healthz. This is an unauthenticated endpoint, and is disabled by default. You need to set `DAPR_PLACEMENT_METADATA_ENABLED` environment or `metadata-enabled` command line args to true to enable it. If you are using helm you just need to set `dapr_placement.metadataEnabled` to true. + +### Usecase: +The placement table API can be used for retrieving the current placement table, which contains all the actors registered. This can be helpful for debugging and allows tools to extract and present information about actors. + +### HTTP Request + +``` +GET http://localhost:/placement/state +``` + +### HTTP Response Codes + +Code | Description +---- | ----------- +200 | Placement tables information returned +500 | Placement could not return the placement tables information + +### HTTP Response Body + +**Placement tables API Response Object** + +Name | Type | Description +---- | ---- | ----------- +tableVersion | int | The placement table version +hostList | [Actor Host Info](#actorhostinfo)[] | A json array of registered actors host info. + +**Actor Host Info** + +Name | Type | Description +---- | ---- | ----------- +name | string | The host:port address of the actor. +appId | string | app id. +actorTypes | json string array | List of actor types it hosts. +updatedAt | timestamp | Timestamp of the actor registered/updated. + +### Examples + +```shell + curl localhost:8080/placement/state +``` + +```json +{ + "hostList": [{ + "name": "198.18.0.1:49347", + "appId": "actor1", + "actorTypes": ["testActorType1", "testActorType3"], + "updatedAt": 1690274322325260000 + }, + { + "name": "198.18.0.2:49347", + "appId": "actor2", + "actorTypes": ["testActorType2"], + "updatedAt": 1690274322325260000 + }, + { + "name": "198.18.0.3:49347", + "appId": "actor2", + "actorTypes": ["testActorType2"], + "updatedAt": 1690274322325260000 + } + ], + "tableVersion": 1 +} +``` From 71a303d5a068af4d97ed393fecac9c7758392d82 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Fri, 22 Sep 2023 14:08:58 -0400 Subject: [PATCH 060/162] mark review pt1 Signed-off-by: Hannah Hunter --- .../multi-app-dapr-run/multi-app-overview.md | 9 ++++----- .../multi-app-dapr-run/multi-app-template.md | 2 +- daprdocs/content/en/reference/cli/dapr-run.md | 1 + 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md index e0d6c5511d9..6535d6db628 100644 --- a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md +++ b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md @@ -6,10 +6,6 @@ weight: 1000 description: Run multiple applications with one CLI command --- -{{% alert title="Note" color="primary" %}} - Multi-App Run is currently a preview feature only supported in Linux/MacOS. -{{% /alert %}} - Let's say you want to run several applications locally to test them together, similar to a production scenario. Until Multi-App Run, you'd have to: - Run multiple `dapr run` commands @@ -98,6 +94,8 @@ Watch [this video for an overview on Multi-App Run](https://youtu.be/s1p9MNl4VGo ## Multi-App Run template file +> **Note:** Multi-App Run in Kubernetes is currently a preview feature only supported in Linux/MacOS. + When you execute `dapr run -k -f .` or `dapr run -k -f dapr.yaml`, the applications defined in the `dapr.yaml` multi-app run template file will start in Kubernetes default namespace. The necessary default service and deployment definitions for Kubernetes are generated within the `.dapr/deploy` folder for each app in the `dapr.yaml` template. @@ -168,4 +166,5 @@ Watch [this video for an overview on Multi-App Run in Kubernetes](https://youtu. ## Next steps - [Learn the Multi-App Run template file structure and its properties]({{< ref multi-app-template.md >}}) -- [Try out the Multi-App Run template with the Service Invocation quickstart]({{< ref serviceinvocation-quickstart.md >}}) \ No newline at end of file +- [Try out the self-hosted Multi-App Run template with the Service Invocation quickstart]({{< ref serviceinvocation-quickstart.md >}}) +- [Try out the Kubernetes Multi-App Run template with the `hello-kubernetes` tutorial](https://github.com/dapr/quickstarts/tree/master/tutorials/hello-kubernetes) \ No newline at end of file diff --git a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md index f8f198702d6..fc4c3a5605c 100644 --- a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md +++ b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md @@ -7,7 +7,7 @@ description: Unpack the Multi-App Run template file and its properties --- {{% alert title="Note" color="primary" %}} - Multi-App Run is currently a preview feature only supported in Linux/MacOS. + Multi-App Run for Kubernetes is currently a preview feature only supported in Linux/MacOS. {{% /alert %}} The Multi-App Run template file is a YAML file that you can use to run multiple applications at once. In this guide, you'll learn how to: diff --git a/daprdocs/content/en/reference/cli/dapr-run.md b/daprdocs/content/en/reference/cli/dapr-run.md index 9a519f98c72..ba79d761fed 100644 --- a/daprdocs/content/en/reference/cli/dapr-run.md +++ b/daprdocs/content/en/reference/cli/dapr-run.md @@ -23,6 +23,7 @@ dapr run [flags] [command] | Name | Environment Variable | Default | Description | | ------------------------------ | -------------------- | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| `--kubernetes`, `-k` | | | Running Dapr on Kubernetes, and used for [Multi-App Run template files on Kubernetes]({{< ref multi-app-dapr-run >}}). | | `--app-id`, `-a` | `APP_ID` | | The id for your application, used for service discovery. Cannot contain dots. | | `--app-max-concurrency` | | `unlimited` | The concurrency level of the application; default is unlimited | | `--app-port`, `-p` | `APP_PORT` | | The port your application is listening on | From c79c7d5cd0bc4b10cd9bdc1c48425190f8957566 Mon Sep 17 00:00:00 2001 From: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Date: Fri, 22 Sep 2023 14:17:10 -0400 Subject: [PATCH 061/162] Update daprdocs/content/en/reference/environment/_index.md Co-authored-by: Mark Fussell Signed-off-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> --- daprdocs/content/en/reference/environment/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/reference/environment/_index.md b/daprdocs/content/en/reference/environment/_index.md index 88f018f22ba..f5081802f79 100644 --- a/daprdocs/content/en/reference/environment/_index.md +++ b/daprdocs/content/en/reference/environment/_index.md @@ -27,4 +27,4 @@ The following table lists the environment variables used by the Dapr runtime, CL | OTEL_EXPORTER_OTLP_PROTOCOL | OpenTelemetry Tracing | The OTLP protocol to use Transport protocol. (`grpc`, `http/protobuf`, `http/json`) | | DAPR_COMPONENTS_SOCKETS_FOLDER | Dapr runtime and the .NET, Go, and Java pluggable component SDKs | The location or path where Dapr looks for Pluggable Components Unix Domain Socket files. If unset this location defaults to `/tmp/dapr-components-sockets` | | DAPR_COMPONENTS_SOCKETS_EXTENSION | .NET and Java pluggable component SDKs | A per-SDK configuration that indicates the default file extension applied to socket files created by the SDKs. Not a Dapr-enforced behavior. | -| DAPR_PLACEMENT_METADATA_ENABLED | Dapr placement | A placement service that exposes placement table information. Set to `true` to enable. [Learn more about the Placement API]({{< ref placement_api.md >}}) | \ No newline at end of file +| DAPR_PLACEMENT_METADATA_ENABLED | Dapr placement | Enable an endpoint for the Placement service that exposes placement table information on actor usage. Set to `true` to enable. [Learn more about the Placement API]({{< ref placement_api.md >}}) | \ No newline at end of file From b5d74ff4ebeac723641bbe5c973204c1823bfb26 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Fri, 22 Sep 2023 14:24:12 -0400 Subject: [PATCH 062/162] update intro Signed-off-by: Hannah Hunter --- .../multi-app-dapr-run/multi-app-overview.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md index 6535d6db628..bc6e2db6af0 100644 --- a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md +++ b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md @@ -6,14 +6,12 @@ weight: 1000 description: Run multiple applications with one CLI command --- -Let's say you want to run several applications locally to test them together, similar to a production scenario. Until Multi-App Run, you'd have to: +Let's say you want to run several applications locally to test them together, similar to a production scenario. Multi-App Run allows you to start and stop a set of applications simultaneously, either: +- Locally/self-hosted with processes, or +- By building container images and deploying to a Kubernetes cluster + - You can use a local Kubernetes cluster (KiND) or one deploy to a Cloud (AKS, EKS, and GKE). -- Run multiple `dapr run` commands -- Keep track of all ports opened (you cannot have duplicate ports for different applications). -- Remember the resources folders and configuration files that each application refers to. -- Recall all of the additional flags you used to tweak the `dapr run` command behavior (`--app-health-check-path`, `--dapr-grpc-port`, `--unix-domain-socket`, etc.) - -With Multi-App Run, you can start multiple applications in either self-hosted or Kubernetes mode using a template file and running a single command. The template file describes how to start multiple applications as if you had run many separate CLI `run`commands. By default, this template file is called `dapr.yaml`. +The Multi-App Run template file describes how to start multiple applications as if you had run many separate CLI `run` commands. By default, this template file is called `dapr.yaml`. {{< tabs Self-hosted Kubernetes>}} @@ -96,7 +94,7 @@ Watch [this video for an overview on Multi-App Run](https://youtu.be/s1p9MNl4VGo > **Note:** Multi-App Run in Kubernetes is currently a preview feature only supported in Linux/MacOS. -When you execute `dapr run -k -f .` or `dapr run -k -f dapr.yaml`, the applications defined in the `dapr.yaml` multi-app run template file will start in Kubernetes default namespace. +When you execute `dapr run -k -f .` or `dapr run -k -f dapr.yaml`, the applications defined in the `dapr.yaml` multi-app run template file starts in Kubernetes default namespace. The necessary default service and deployment definitions for Kubernetes are generated within the `.dapr/deploy` folder for each app in the `dapr.yaml` template. From 184af4141a23174c4fbc9be2478f81d461b3d0e1 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Fri, 22 Sep 2023 14:26:21 -0400 Subject: [PATCH 063/162] update preview features table Signed-off-by: Hannah Hunter --- .../content/en/operations/support/support-preview-features.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/operations/support/support-preview-features.md b/daprdocs/content/en/operations/support/support-preview-features.md index 64d7fa1844f..2c96fd5f5cc 100644 --- a/daprdocs/content/en/operations/support/support-preview-features.md +++ b/daprdocs/content/en/operations/support/support-preview-features.md @@ -17,7 +17,7 @@ For CLI there is no explicit opt-in, just the version that this was first made a | --- | --- | --- | --- | --- | | **Streaming for HTTP service invocation** | Enables (partial) support for using streams in HTTP service invocation; see below for more details. | `ServiceInvocationStreaming` | [Details]({{< ref "support-preview-features.md#streaming-for-http-service-invocation" >}}) | v1.10 | | **Pluggable components** | Allows creating self-hosted gRPC-based components written in any language that supports gRPC. The following component APIs are supported: State stores, Pub/sub, Bindings | N/A | [Pluggable components concept]({{}})| v1.9 | -| **Multi-App Run** | Configure multiple Dapr applications from a single configuration file and run from a single command | `dapr run -f` | [Multi-App Run]({{< ref multi-app-dapr-run.md >}}) | v1.10 | +| **Multi-App Run for Kubernetes** | Configure multiple Dapr applications from a single configuration file and run from a single command on Kubernetes | `dapr run -k -f` | [Multi-App Run]({{< ref multi-app-dapr-run.md >}}) | v1.10 | | **Workflows** | Author workflows as code to automate and orchestrate tasks within your application, like messaging, state management, and failure handling | N/A | [Workflows concept]({{< ref "components-concept#workflows" >}})| v1.10 | | **Cryptography** | Encrypt or decrypt data without having to manage secrets keys | N/A | [Cryptography concept]({{< ref "components-concept#cryptography" >}})| v1.11 | | **Service invocation for non-Dapr endpoints** | Allow the invocation of non-Dapr endpoints by Dapr using the [Service invocation API]({{< ref service_invocation_api.md >}}). Read ["How-To: Invoke Non-Dapr Endpoints using HTTP"]({{< ref howto-invoke-non-dapr-endpoints.md >}}) for more information. | N/A | [Service invocation API]({{< ref service_invocation_api.md >}}) | v1.11 | From dbb729cb1c3554aa2cad5afcad8fde440ff46385 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Fri, 22 Sep 2023 14:30:48 -0400 Subject: [PATCH 064/162] add note about restriction, move note Signed-off-by: Hannah Hunter --- .../multi-app-dapr-run/multi-app-overview.md | 8 ++++++-- .../multi-app-dapr-run/multi-app-template.md | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md index bc6e2db6af0..64283705de6 100644 --- a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md +++ b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md @@ -6,6 +6,10 @@ weight: 1000 description: Run multiple applications with one CLI command --- +{{% alert title="Note" color="primary" %}} + Multi-App Run for **Kubernetes** is currently a preview feature only supported in Linux/MacOS. +{{% /alert %}} + Let's say you want to run several applications locally to test them together, similar to a production scenario. Multi-App Run allows you to start and stop a set of applications simultaneously, either: - Locally/self-hosted with processes, or - By building container images and deploying to a Kubernetes cluster @@ -92,9 +96,9 @@ Watch [this video for an overview on Multi-App Run](https://youtu.be/s1p9MNl4VGo ## Multi-App Run template file -> **Note:** Multi-App Run in Kubernetes is currently a preview feature only supported in Linux/MacOS. +When you execute `dapr run -k -f .` or `dapr run -k -f dapr.yaml`, the applications defined in the `dapr.yaml` Multi-App Run template file starts in Kubernetes default namespace. -When you execute `dapr run -k -f .` or `dapr run -k -f dapr.yaml`, the applications defined in the `dapr.yaml` multi-app run template file starts in Kubernetes default namespace. +> **Note:** Currently, the Multi-App Run template can only start applications in the default Kubernetes namespace. The necessary default service and deployment definitions for Kubernetes are generated within the `.dapr/deploy` folder for each app in the `dapr.yaml` template. diff --git a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md index fc4c3a5605c..81cb228e87a 100644 --- a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md +++ b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md @@ -7,7 +7,7 @@ description: Unpack the Multi-App Run template file and its properties --- {{% alert title="Note" color="primary" %}} - Multi-App Run for Kubernetes is currently a preview feature only supported in Linux/MacOS. + Multi-App Run for **Kubernetes** is currently a preview feature only supported in Linux/MacOS. {{% /alert %}} The Multi-App Run template file is a YAML file that you can use to run multiple applications at once. In this guide, you'll learn how to: From d2a912a384991a9d22be7725e5a8e2bdc1d65537 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Fri, 22 Sep 2023 14:44:53 -0400 Subject: [PATCH 065/162] update example Signed-off-by: Hannah Hunter --- .../content/en/reference/api/placement_api.md | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/daprdocs/content/en/reference/api/placement_api.md b/daprdocs/content/en/reference/api/placement_api.md index 9cb86a3ddd9..5a76dd9482e 100644 --- a/daprdocs/content/en/reference/api/placement_api.md +++ b/daprdocs/content/en/reference/api/placement_api.md @@ -45,7 +45,7 @@ appId | string | app id. actorTypes | json string array | List of actor types it hosts. updatedAt | timestamp | Timestamp of the actor registered/updated. -## Examples +### Examples ```shell curl localhost:8080/placement/state @@ -54,11 +54,24 @@ updatedAt | timestamp | Timestamp of the actor registered/updated. ```json { "hostList": [{ - "name": "198.18.0.1:49347", - "appId": "actor", - "actorTypes": ["testActorType"], - "updatedAt": 1690274322325260000 - }], + "name": "198.18.0.1:49347", + "appId": "actor1", + "actorTypes": ["testActorType1", "testActorType3"], + "updatedAt": 1690274322325260000 + }, + { + "name": "198.18.0.2:49347", + "appId": "actor2", + "actorTypes": ["testActorType2"], + "updatedAt": 1690274322325260000 + }, + { + "name": "198.18.0.3:49347", + "appId": "actor2", + "actorTypes": ["testActorType2"], + "updatedAt": 1690274322325260000 + } + ], "tableVersion": 1 } ``` \ No newline at end of file From e388830ceedde4c6587c762f966c8e43a0ae3ed8 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Fri, 22 Sep 2023 14:45:23 -0400 Subject: [PATCH 066/162] update examples header Signed-off-by: Hannah Hunter --- daprdocs/content/en/reference/api/placement_api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/reference/api/placement_api.md b/daprdocs/content/en/reference/api/placement_api.md index 5a76dd9482e..bd7eee3cd0c 100644 --- a/daprdocs/content/en/reference/api/placement_api.md +++ b/daprdocs/content/en/reference/api/placement_api.md @@ -45,7 +45,7 @@ appId | string | app id. actorTypes | json string array | List of actor types it hosts. updatedAt | timestamp | Timestamp of the actor registered/updated. -### Examples +## Examples ```shell curl localhost:8080/placement/state From 6b143996796103001c7cbe632dd11b7e13ecca91 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Fri, 22 Sep 2023 15:42:42 -0400 Subject: [PATCH 067/162] add ryan review Signed-off-by: Hannah Hunter --- daprdocs/content/en/reference/api/workflow_api.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daprdocs/content/en/reference/api/workflow_api.md b/daprdocs/content/en/reference/api/workflow_api.md index a2c0d3b1b0b..9f9c34de81a 100644 --- a/daprdocs/content/en/reference/api/workflow_api.md +++ b/daprdocs/content/en/reference/api/workflow_api.md @@ -57,7 +57,7 @@ The API call will provide a response similar to this: Terminate a running workflow instance with the given name and instance ID. ``` -POST http://localhost:3500/v1.0-beta1/workflow///terminate +POST http://localhost:3500/v1.0-beta1/workflows///terminate ``` ### URL parameters @@ -144,7 +144,7 @@ None. Resume a paused workflow instance. ``` -POST http://localhost:3500/v1.0-beta1/workflow///resume +POST http://localhost:3500/v1.0-beta1/workflows///resume ``` ### URL parameters From 3af5b954bf0226cea8d5cec88aeda39cba8aed85 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Fri, 22 Sep 2023 18:38:08 -0400 Subject: [PATCH 068/162] remove bindings quickstart changes for now and preview alerts Signed-off-by: Hannah Hunter --- .../quickstarts/bindings-quickstart.md | 1072 +---------------- .../quickstarts/pubsub-quickstart.md | 4 - .../serviceinvocation-quickstart.md | 4 - .../quickstarts/statemanagement-quickstart.md | 4 - 4 files changed, 1 insertion(+), 1083 deletions(-) diff --git a/daprdocs/content/en/getting-started/quickstarts/bindings-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/bindings-quickstart.md index 64731de6852..22c56afe25b 100644 --- a/daprdocs/content/en/getting-started/quickstarts/bindings-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/bindings-quickstart.md @@ -11,1080 +11,10 @@ Let's take a look at Dapr's [Bindings building block]({{< ref bindings >}}). Usi - Trigger your app with events coming in from external systems. - Interface with external systems. -In this Quickstart, you will schedule a batch script to run every 10 seconds using an input [Cron]({{< ref cron.md >}}) binding. The script processes a JSON file and outputs data to a SQL database using the [PostgreSQL]({{< ref postgresql.md >}}) Dapr binding. You can run this quickstart by either: -- [Running all applications in this sample simultaneously with the Multi-App Run template file]({{< ref "#run-using-multi-app-run" >}}), or -- [Running one application at a time]({{< ref "#run-one-application-at-a-time" >}}) +In this Quickstart, you will schedule a batch script to run every 10 seconds using an input [Cron]({{< ref cron.md >}}) binding. The script processes a JSON file and outputs data to a SQL database using the [PostgreSQL]({{< ref postgresql.md >}}) Dapr binding. -## Run using Multi-App Run - -{{% alert title="Note" color="primary" %}} - [Multi-App Run]({{< ref multi-app-dapr-run >}}) is currently a preview feature only supported in Linux/MacOS. -{{% /alert %}} - -Select your preferred language-specific Dapr SDK before proceeding with the Quickstart. - -{{< tabs "Python" "JavaScript" ".NET" "Java" "Go" >}} - -{{% codetab %}} - -### Pre-requisites - -For this example, you will need: - -- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). -- [Python 3.7+ installed](https://www.python.org/downloads/). - -- [Docker Desktop](https://www.docker.com/products/docker-desktop) - - -### Step 1: Set up the environment - -Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/bindings). - -```bash -git clone https://github.com/dapr/quickstarts.git -``` - -### Step 2: Run PostgreSQL Docker container locally - -Run the [PostgreSQL instance](https://www.postgresql.org/) locally in a Docker container on your machine. The Quickstart sample includes a Docker Compose file to locally customize, build, run, and initialize the `postgres` container with a default `orders` table. - -In a terminal window, from the root of the Quickstarts clone directory, navigate to the `bindings/db` directory. - -```bash -cd bindings/db -``` - -Run the following command to set up the container: - -```bash -docker compose up -``` - -Verify that the container is running locally. - -```bash -docker ps -``` - -The output should include: - -```bash -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -55305d1d378b postgres "docker-entrypoint.s…" 3 seconds ago Up 2 seconds 0.0.0.0:5432->5432/tcp sql_db -``` - -### Step 3: Schedule a Cron job and write to the database - -In a new terminal window, navigate to the SDK directory. - -```bash -cd bindings/python/sdk/batch -``` - -Install the dependencies: - -```bash -pip3 install -r requirements.txt -``` - -Run the `batch-sdk` service alongside a Dapr sidecar. - -```bash -dapr run --app-id batch-sdk --app-port 50051 --resources-path ../../../components -- python3 app.py -``` - -> **Note**: Since Python3.exe is not defined in Windows, you may need to use `python app.py` instead of `python3 app.py`. - -The code inside the `process_batch` function is executed every 10 seconds (defined in [`binding-cron.yaml`]({{< ref "#componentsbinding-cronyaml-component-file" >}}) in the `components` directory). The binding trigger looks for a route called via HTTP POST in your application by the Dapr sidecar. - -```python -# Triggered by Dapr input binding -@app.route('/' + cron_binding_name, methods=['POST']) -def process_batch(): -``` - -The `batch-sdk` service uses the PostgreSQL output binding defined in the [`binding-postgresql.yaml`]({{< ref "#componentbinding-postgresyaml-component-file" >}}) component to insert the `OrderId`, `Customer`, and `Price` records into the `orders` table. - -```python -with DaprClient() as d: - sqlCmd = ('insert into orders (orderid, customer, price) values ' + - '(%s, \'%s\', %s)' % (order_line['orderid'], - order_line['customer'], - order_line['price'])) - payload = {'sql': sqlCmd} - - print(sqlCmd, flush=True) - - try: - # Insert order using Dapr output binding via HTTP Post - resp = d.invoke_binding(binding_name=sql_binding, operation='exec', - binding_metadata=payload, data='') - return resp - except Exception as e: - print(e, flush=True) - raise SystemExit(e) -``` - -### Step 4: View the output of the job - -Notice, as specified above, the code invokes the output binding with the `OrderId`, `Customer`, and `Price` as a payload. - -Your output binding's `print` statement output: - -``` -== APP == Processing batch.. -== APP == insert into orders (orderid, customer, price) values (1, 'John Smith', 100.32) -== APP == insert into orders (orderid, customer, price) values (2, 'Jane Bond', 15.4) -== APP == insert into orders (orderid, customer, price) values (3, 'Tony James', 35.56) -== APP == Finished processing batch -``` - -In a new terminal, verify the same data has been inserted into the database. Navigate to the `bindings/db` directory. - -```bash -cd bindings/db -``` - -Run the following to start the interactive *psql* CLI: - -```bash -docker exec -i -t postgres psql --username postgres -p 5432 -h localhost --no-password -``` - -At the `admin=#` prompt, change to the `orders` table: - -```bash -\c orders; -``` - -At the `orders=#` prompt, select all rows: - -```bash -select * from orders; -``` - -The output should look like this: - -``` - orderid | customer | price ----------+------------+-------- - 1 | John Smith | 100.32 - 2 | Jane Bond | 15.4 - 3 | Tony James | 35.56 -``` - -#### `components\binding-cron.yaml` component file - -When you execute the `dapr run` command and specify the component path, the Dapr sidecar: - -- Initiates the Cron [binding building block]({{< ref bindings >}}) -- Calls the binding endpoint (`batch`) every 10 seconds - -The Cron `binding-cron.yaml` file included for this Quickstart contains the following: - -```yaml -apiVersion: dapr.io/v1alpha1 -kind: Component -metadata: - name: cron - namespace: quickstarts -spec: - type: bindings.cron - version: v1 - metadata: - - name: schedule - value: "@every 10s" # valid cron schedule - - name: direction - value: "input" # direction of the cron binding -``` - -**Note:** The `metadata` section of `binding-cron.yaml` contains a [Cron expression]({{< ref cron.md >}}) that specifies how often the binding is invoked. - -#### `component\binding-postgresql.yaml` component file - -When you execute the `dapr run` command and specify the component path, the Dapr sidecar: - -- Initiates the PostgreSQL [binding building block]({{< ref postgresql.md >}}) -- Connects to PostgreSQL using the settings specified in the `binding-postgresql.yaml` file - -With the `binding-postgresql.yaml` component, you can easily swap out the backend database [binding]({{< ref supported-bindings.md >}}) without making code changes. - -The PostgreSQL `binding-postgresql.yaml` file included for this Quickstart contains the following: - -```yaml -apiVersion: dapr.io/v1alpha1 -kind: Component -metadata: - name: sqldb - namespace: quickstarts -spec: - type: bindings.postgresql - version: v1 - metadata: - - name: url # Required - value: "user=postgres password=docker host=localhost port=5432 dbname=orders pool_min_conns=1 pool_max_conns=10" - - name: direction - value: "output" # direction of the postgresql binding -``` - -In the YAML file: - -- `spec/type` specifies that PostgreSQL is used for this binding. -- `spec/metadata` defines the connection to the PostgreSQL instance used by the component. - -{{% /codetab %}} - - -{{% codetab %}} - -### Pre-requisites - -For this example, you will need: - -- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). -- [Latest Node.js installed](https://nodejs.org/download/). - -- [Docker Desktop](https://www.docker.com/products/docker-desktop) - - -### Step 1: Set up the environment - -Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/bindings). - -```bash -git clone https://github.com/dapr/quickstarts.git -``` - -### Step 2: Run PostgreSQL Docker container locally - -Run the [PostgreSQL instance](https://www.postgresql.org/) locally in a Docker container on your machine. The Quickstart sample includes a Docker Compose file to locally customize, build, run, and initialize the `postgres` container with a default `orders` table. - -In a terminal window, from the root of the Quickstarts clone directory, navigate to the `bindings/db` directory. - -```bash -cd bindings/db -``` - -Run the following command to set up the container: - -```bash -docker compose up -``` - -Verify that the container is running locally. - -```bash -docker ps -``` - -The output should include: - -```bash -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -55305d1d378b postgres "docker-entrypoint.s…" 3 seconds ago Up 2 seconds 0.0.0.0:5432->5432/tcp sql_db -``` - -### Step 3: Schedule a Cron job and write to the database - -In a new terminal window, navigate to the SDK directory. - -```bash -cd bindings/javascript/sdk/batch -``` - -Install the dependencies: - -```bash -npm install -``` - -Run the `batch-sdk` service alongside a Dapr sidecar. - -```bash -dapr run --app-id batch-sdk --app-port 5002 --dapr-http-port 3500 --resources-path ../../../components -- node index.js -``` - -The code inside the `process_batch` function is executed every 10 seconds (defined in [`binding-cron.yaml`]({{< ref "#componentsbinding-cronyaml-component-file" >}}) in the `components` directory). The binding trigger looks for a route called via HTTP POST in your application by the Dapr sidecar. - -```javascript -async function start() { - await server.binding.receive(cronBindingName,processBatch); - await server.start(); -} -``` - -The `batch-sdk` service uses the PostgreSQL output binding defined in the [`binding-postgresql.yaml`]({{< ref "##componentsbinding-postgresyaml-component-file" >}}) component to insert the `OrderId`, `Customer`, and `Price` records into the `orders` table. - -```javascript -async function processBatch(){ - const loc = '../../orders.json'; - fs.readFile(loc, 'utf8', (err, data) => { - const orders = JSON.parse(data).orders; - orders.forEach(order => { - let sqlCmd = `insert into orders (orderid, customer, price) values (${order.orderid}, '${order.customer}', ${order.price});`; - let payload = `{ "sql": "${sqlCmd}" } `; - console.log(payload); - client.binding.send(postgresBindingName, "exec", "", JSON.parse(payload)); - }); - console.log('Finished processing batch'); - }); - return 0; -} -``` - -### Step 4: View the output of the job - -Notice, as specified above, the code invokes the output binding with the `OrderId`, `Customer`, and `Price` as a payload. - -Your output binding's `print` statement output: - -``` -== APP == Processing batch.. -== APP == insert into orders (orderid, customer, price) values(1, 'John Smith', 100.32) -== APP == insert into orders (orderid, customer, price) values(2, 'Jane Bond', 15.4) -== APP == insert into orders (orderid, customer, price) values(3, 'Tony James', 35.56) -``` - -In a new terminal, verify the same data has been inserted into the database. Navigate to the `bindings/db` directory. - -```bash -cd bindings/db -``` - -Run the following to start the interactive Postgres CLI: - -```bash -docker exec -i -t postgres psql --username postgres -p 5432 -h localhost --no-password -``` - -At the `admin=#` prompt, change to the `orders` table: - -```bash -\c orders; -``` - -At the `orders=#` prompt, select all rows: - -```bash -select * from orders; -``` - -The output should look like this: - -``` - orderid | customer | price ----------+------------+-------- - 1 | John Smith | 100.32 - 2 | Jane Bond | 15.4 - 3 | Tony James | 35.56 -``` - -#### `components\binding-cron.yaml` component file - -When you execute the `dapr run` command and specify the component path, the Dapr sidecar: - -- Initiates the Cron [binding building block]({{< ref bindings >}}) -- Calls the binding endpoint (`batch`) every 10 seconds - -The Cron `binding-cron.yaml` file included for this Quickstart contains the following: - -```yaml -apiVersion: dapr.io/v1alpha1 -kind: Component -metadata: - name: cron - namespace: quickstarts -spec: - type: bindings.cron - version: v1 - metadata: - - name: schedule - value: "@every 10s" # valid cron schedule - - name: direction - value: "input" # direction of the cron binding -``` - -**Note:** The `metadata` section of `binding-cron.yaml` contains a [Cron expression]({{< ref cron.md >}}) that specifies how often the binding is invoked. - -#### `component\binding-postgresql.yaml` component file - -When you execute the `dapr run` command and specify the component path, the Dapr sidecar: - -- Initiates the PostgreSQL [binding building block]({{< ref postgresql.md >}}) -- Connects to PostgreSQL using the settings specified in the `binding-postgresql.yaml` file - -With the `binding-postgresql.yaml` component, you can easily swap out the backend database [binding]({{< ref supported-bindings.md >}}) without making code changes. - -The PostgreSQL `binding-postgresql.yaml` file included for this Quickstart contains the following: - -```yaml -apiVersion: dapr.io/v1alpha1 -kind: Component -metadata: - name: sqldb - namespace: quickstarts -spec: - type: bindings.postgresql - version: v1 - metadata: - - name: url # Required - value: "user=postgres password=docker host=localhost port=5432 dbname=orders pool_min_conns=1 pool_max_conns=10" - - name: direction - value: "output" # direction of the postgresql binding -``` - -In the YAML file: - -- `spec/type` specifies that PostgreSQL is used for this binding. -- `spec/metadata` defines the connection to the PostgreSQL instance used by the component. - -{{% /codetab %}} - - -{{% codetab %}} - -### Pre-requisites - -For this example, you will need: - -- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). -- [.NET SDK or .NET 6 SDK installed](https://dotnet.microsoft.com/download). - -- [Docker Desktop](https://www.docker.com/products/docker-desktop) - - -### Step 1: Set up the environment - -Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/bindings). - -```bash -git clone https://github.com/dapr/quickstarts.git -``` - -### Step 2: Run PostgreSQL Docker container locally - -Run the [PostgreSQL instance](https://www.postgresql.org/) locally in a Docker container on your machine. The Quickstart sample includes a Docker Compose file to locally customize, build, run, and initialize the `postgres` container with a default `orders` table. - -In a terminal window, from the root of the Quickstarts clone directory, navigate to the `bindings/db` directory. - -```bash -cd bindings/db -``` - -Run the following command to set up the container: - -```bash -docker compose up -``` - -Verify that the container is running locally. - -```bash -docker ps -``` - -The output should include: - -```bash -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -55305d1d378b postgres "docker-entrypoint.s…" 3 seconds ago Up 2 seconds 0.0.0.0:5432->5432/tcp sql_db -``` - -### Step 3: Schedule a Cron job and write to the database - -In a new terminal window, navigate to the SDK directory. - -```bash -cd bindings/csharp/sdk/batch -``` - -Install the dependencies: - -```bash -dotnet restore -dotnet build batch.csproj -``` - -Run the `batch-sdk` service alongside a Dapr sidecar. - -```bash -dapr run --app-id batch-sdk --app-port 7002 --resources-path ../../../components -- dotnet run -``` - -The code inside the `process_batch` function is executed every 10 seconds (defined in [`binding-cron.yaml`]({{< ref "#componentsbinding-cronyaml-component-file" >}}) in the `components` directory). The binding trigger looks for a route called via HTTP POST in your application by the Dapr sidecar. - -```csharp -app.MapPost("/" + cronBindingName, async () => { -// ... -}); -``` - -The `batch-sdk` service uses the PostgreSQL output binding defined in the [`binding-postgresql.yaml`]({{< ref "#componentbinding-postgresyaml-component-file" >}}) component to insert the `OrderId`, `Customer`, and `Price` records into the `orders` table. - -```csharp -// ... -string jsonFile = File.ReadAllText("../../../orders.json"); -var ordersArray = JsonSerializer.Deserialize(jsonFile); -using var client = new DaprClientBuilder().Build(); -foreach(Order ord in ordersArray?.orders ?? new Order[] {}){ - var sqlText = $"insert into orders (orderid, customer, price) values ({ord.OrderId}, '{ord.Customer}', {ord.Price});"; - var command = new Dictionary(){ - {"sql", - sqlText} - }; -// ... -} - -// Insert order using Dapr output binding via Dapr Client SDK -await client.InvokeBindingAsync(bindingName: sqlBindingName, operation: "exec", data: "", metadata: command); -``` - -### Step 4: View the output of the job - -Notice, as specified above, the code invokes the output binding with the `OrderId`, `Customer`, and `Price` as a payload. - -Your output binding's `print` statement output: - -``` -== APP == Processing batch.. -== APP == insert into orders (orderid, customer, price) values (1, 'John Smith', 100.32); -== APP == insert into orders (orderid, customer, price) values (2, 'Jane Bond', 15.4); -== APP == insert into orders (orderid, customer, price) values (3, 'Tony James', 35.56); -== APP == Finished processing batch -``` - -In a new terminal, verify the same data has been inserted into the database. Navigate to the `bindings/db` directory. - -```bash -cd bindings/db -``` - -Run the following to start the interactive Postgres CLI: - -```bash -docker exec -i -t postgres psql --username postgres -p 5432 -h localhost --no-password -``` - -At the `admin=#` prompt, change to the `orders` table: - -```bash -\c orders; -``` - -At the `orders=#` prompt, select all rows: - -```bash -select * from orders; -``` - -The output should look like this: - -``` - orderid | customer | price ----------+------------+-------- - 1 | John Smith | 100.32 - 2 | Jane Bond | 15.4 - 3 | Tony James | 35.56 -``` - -#### `components\binding-cron.yaml` component file - -When you execute the `dapr run` command and specify the component path, the Dapr sidecar: - -- Initiates the Cron [binding building block]({{< ref bindings >}}) -- Calls the binding endpoint (`batch`) every 10 seconds - -The Cron `binding-cron.yaml` file included for this Quickstart contains the following: - -```yaml -apiVersion: dapr.io/v1alpha1 -kind: Component -metadata: - name: cron - namespace: quickstarts -spec: - type: bindings.cron - version: v1 - metadata: - - name: schedule - value: "@every 10s" # valid cron schedule - - name: direction - value: "input" # direction of the cron binding -``` - -**Note:** The `metadata` section of `binding-cron.yaml` contains a [Cron expression]({{< ref cron.md >}}) that specifies how often the binding is invoked. - -#### `component\binding-postgresql.yaml` component file - -When you execute the `dapr run` command and specify the component path, the Dapr sidecar: - -- Initiates the PostgreSQL [binding building block]({{< ref postgresql.md >}}) -- Connects to PostgreSQL using the settings specified in the `binding-postgresql.yaml` file - -With the `binding-postgresql.yaml` component, you can easily swap out the backend database [binding]({{< ref supported-bindings.md >}}) without making code changes. - -The PostgreSQL `binding-postgresql.yaml` file included for this Quickstart contains the following: - -```yaml -apiVersion: dapr.io/v1alpha1 -kind: Component -metadata: - name: sqldb - namespace: quickstarts -spec: - type: bindings.postgresql - version: v1 - metadata: - - name: url # Required - value: "user=postgres password=docker host=localhost port=5432 dbname=orders pool_min_conns=1 pool_max_conns=10" - - name: direction - value: "output" # direction of the postgresql binding -``` - -In the YAML file: - -- `spec/type` specifies that PostgreSQL is used for this binding. -- `spec/metadata` defines the connection to the PostgreSQL instance used by the component. - -{{% /codetab %}} - - -{{% codetab %}} - -### Pre-requisites - -For this example, you will need: - -- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). -- Java JDK 11 (or greater): - - [Oracle JDK](https://www.oracle.com/java/technologies/downloads), or - - OpenJDK -- [Apache Maven](https://maven.apache.org/install.html), version 3.x. - -- [Docker Desktop](https://www.docker.com/products/docker-desktop) - - -### Step 1: Set up the environment - -Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/bindings). - -```bash -git clone https://github.com/dapr/quickstarts.git -``` - -### Step 2: Run PostgreSQL Docker container locally - -Run the [PostgreSQL instance](https://www.postgresql.org/) locally in a Docker container on your machine. The Quickstart sample includes a Docker Compose file to locally customize, build, run, and initialize the `postgres` container with a default `orders` table. - -In a terminal window, from the root of the Quickstarts clone directory, navigate to the `bindings/db` directory. - -```bash -cd bindings/db -``` - -Run the following command to set up the container: - -```bash -docker compose up -``` - -Verify that the container is running locally. - -```bash -docker ps -``` - -The output should include: - -```bash -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -55305d1d378b postgres "docker-entrypoint.s…" 3 seconds ago Up 2 seconds 0.0.0.0:5432->5432/tcp sql_db -``` - -### Step 3: Schedule a Cron job and write to the database - -In a new terminal window, navigate to the SDK directory. - -```bash -cd bindings/java/sdk/batch -``` - -Install the dependencies: - -```bash -mvn clean install -``` - -Run the `batch-sdk` service alongside a Dapr sidecar. - -```bash -dapr run --app-id batch-sdk --app-port 8080 --resources-path ../../../components -- java -jar target/BatchProcessingService-0.0.1-SNAPSHOT.jar -``` - -The code inside the `process_batch` function is executed every 10 seconds (defined in [`binding-cron.yaml`]({{< ref "#componentsbinding-cronyaml-component-file" >}}) in the `components` directory). The binding trigger looks for a route called via HTTP POST in your application by the Dapr sidecar. - -```java -@PostMapping(path = cronBindingPath, consumes = MediaType.ALL_VALUE) -public ResponseEntity processBatch() throws IOException, Exception -``` - -The `batch-sdk` service uses the PostgreSQL output binding defined in the [`binding-postgresql.yaml`]({{< ref "#componentbinding-postgresyaml-component-file" >}}) component to insert the `OrderId`, `Customer`, and `Price` records into the `orders` table. - -```java -try (DaprClient client = new DaprClientBuilder().build()) { - - for (Order order : ordList.orders) { - String sqlText = String.format( - "insert into orders (orderid, customer, price) " + - "values (%s, '%s', %s);", - order.orderid, order.customer, order.price); - logger.info(sqlText); - - Map metadata = new HashMap(); - metadata.put("sql", sqlText); - - // Invoke sql output binding using Dapr SDK - client.invokeBinding(sqlBindingName, "exec", null, metadata).block(); - } - - logger.info("Finished processing batch"); - - return ResponseEntity.ok("Finished processing batch"); -} -``` - -### Step 4: View the output of the job - -Notice, as specified above, the code invokes the output binding with the `OrderId`, `Customer`, and `Price` as a payload. - -Your output binding's `print` statement output: - -``` -== APP == 2022-06-22 16:39:17.012 INFO 35772 --- [nio-8080-exec-4] c.s.c.BatchProcessingServiceController : Processing batch.. -== APP == 2022-06-22 16:39:17.268 INFO 35772 --- [nio-8080-exec-4] c.s.c.BatchProcessingServiceController : insert into orders (orderid, customer, price) values (1, 'John Smith', 100.32); -== APP == 2022-06-22 16:39:17.838 INFO 35772 --- [nio-8080-exec-4] c.s.c.BatchProcessingServiceController : insert into orders (orderid, customer, price) values (2, 'Jane Bond', 15.4); -== APP == 2022-06-22 16:39:17.844 INFO 35772 --- [nio-8080-exec-4] c.s.c.BatchProcessingServiceController : insert into orders (orderid, customer, price) values (3, 'Tony James', 35.56); -== APP == 2022-06-22 16:39:17.848 INFO 35772 --- [nio-8080-exec-4] c.s.c.BatchProcessingServiceController : Finished processing batch -``` - -In a new terminal, verify the same data has been inserted into the database. Navigate to the `bindings/db` directory. - -```bash -cd bindings/db -``` - -Run the following to start the interactive Postgres CLI: - -```bash -docker exec -i -t postgres psql --username postgres -p 5432 -h localhost --no-password -``` - -At the `admin=#` prompt, change to the `orders` table: - -```bash -\c orders; -``` - -At the `orders=#` prompt, select all rows: - -```bash -select * from orders; -``` - -The output should look like this: - -``` - orderid | customer | price ----------+------------+-------- - 1 | John Smith | 100.32 - 2 | Jane Bond | 15.4 - 3 | Tony James | 35.56 -``` - -#### `components\binding-cron.yaml` component file - -When you execute the `dapr run` command and specify the component path, the Dapr sidecar: - -- Initiates the Cron [binding building block]({{< ref bindings >}}) -- Calls the binding endpoint (`batch`) every 10 seconds - -The Cron `binding-cron.yaml` file included for this Quickstart contains the following: - -```yaml -apiVersion: dapr.io/v1alpha1 -kind: Component -metadata: - name: cron - namespace: quickstarts -spec: - type: bindings.cron - version: v1 - metadata: - - name: schedule - value: "@every 10s" # valid cron schedule - - name: direction - value: "input" # direction of the cron binding -``` - -**Note:** The `metadata` section of `binding-cron.yaml` contains a [Cron expression]({{< ref cron.md >}}) that specifies how often the binding is invoked. - -#### `component\binding-postgresql.yaml` component file - -When you execute the `dapr run` command and specify the component path, the Dapr sidecar: - -- Initiates the PostgreSQL [binding building block]({{< ref postgresql.md >}}) -- Connects to PostgreSQL using the settings specified in the `binding-postgresql.yaml` file - -With the `binding-postgresql.yaml` component, you can easily swap out the backend database [binding]({{< ref supported-bindings.md >}}) without making code changes. - -The PostgreSQL `binding-postgresql.yaml` file included for this Quickstart contains the following: - -```yaml -apiVersion: dapr.io/v1alpha1 -kind: Component -metadata: - name: sqldb - namespace: quickstarts -spec: - type: bindings.postgresql - version: v1 - metadata: - - name: url # Required - value: "user=postgres password=docker host=localhost port=5432 dbname=orders pool_min_conns=1 pool_max_conns=10" - - name: direction - value: "output" # direction of the postgresql binding -``` - -In the YAML file: - -- `spec/type` specifies that PostgreSQL is used for this binding. -- `spec/metadata` defines the connection to the PostgreSQL instance used by the component. - -{{% /codetab %}} - - -{{% codetab %}} - -### Pre-requisites - -For this example, you will need: - -- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). -- [Latest version of Go](https://go.dev/dl/). - -- [Docker Desktop](https://www.docker.com/products/docker-desktop) - - -### Step 1: Set up the environment - -Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/bindings). - -```bash -git clone https://github.com/dapr/quickstarts.git -``` - -### Step 2: Run PostgreSQL Docker container locally - -Run the [PostgreSQL instance](https://www.postgresql.org/) locally in a Docker container on your machine. The Quickstart sample includes a Docker Compose file to locally customize, build, run, and initialize the `postgres` container with a default `orders` table. - -In a terminal window, from the root of the Quickstarts clone directory, navigate to the `bindings/db` directory. - -```bash -cd bindings/db -``` - -Run the following command to set up the container: - -```bash -docker compose up -``` - -Verify that the container is running locally. - -```bash -docker ps -``` - -The output should include: - -```bash -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -55305d1d378b postgres "docker-entrypoint.s…" 3 seconds ago Up 2 seconds 0.0.0.0:5432->5432/tcp sql_db -``` - -### Step 3: Schedule a Cron job and write to the database - -In a new terminal window, navigate to the SDK directory. - -```bash -cd bindings/go/sdk/batch -``` - -Install the dependencies: - -```bash -go build . -``` - -Run the `batch-sdk` service alongside a Dapr sidecar. - -```bash -dapr run --app-id batch-sdk --app-port 6002 --dapr-http-port 3502 --dapr-grpc-port 60002 --resources-path ../../../components -- go run . -``` - -The code inside the `process_batch` function is executed every 10 seconds (defined in [`binding-cron.yaml`]({{< ref "#componentsbinding-cronyaml-component-file" >}}) in the `components` directory). The binding trigger looks for a route called via HTTP POST in your application by the Dapr sidecar. - -```go -// Triggered by Dapr input binding -r.HandleFunc("/"+cronBindingName, processBatch).Methods("POST") -``` - -The `batch-sdk` service uses the PostgreSQL output binding defined in the [`binding-postgresql.yaml`]({{< ref "#componentbinding-postgresyaml-component-file" >}}) component to insert the `OrderId`, `Customer`, and `Price` records into the `orders` table. - -```go -func sqlOutput(order Order) (err error) { - - client, err := dapr.NewClient() - if err != nil { - return err - } - - ctx := context.Background() - - sqlCmd := fmt.Sprintf("insert into orders (orderid, customer, price) values (%d, '%s', %s);", order.OrderId, order.Customer, strconv.FormatFloat(order.Price, 'f', 2, 64)) - fmt.Println(sqlCmd) - - // Insert order using Dapr output binding via Dapr SDK - in := &dapr.InvokeBindingRequest{ - Name: sqlBindingName, - Operation: "exec", - Data: []byte(""), - Metadata: map[string]string{"sql": sqlCmd}, - } - err = client.InvokeOutputBinding(ctx, in) - if err != nil { - return err - } - - return nil -} -``` - -### Step 4: View the output of the job - -Notice, as specified above, the code invokes the output binding with the `OrderId`, `Customer`, and `Price` as a payload. - -Your output binding's `print` statement output: - -``` -== APP == Processing batch.. -== APP == insert into orders (orderid, customer, price) values(1, 'John Smith', 100.32) -== APP == insert into orders (orderid, customer, price) values(2, 'Jane Bond', 15.4) -== APP == insert into orders (orderid, customer, price) values(3, 'Tony James', 35.56) -``` - -In a new terminal, verify the same data has been inserted into the database. Navigate to the `bindings/db` directory. - -```bash -cd bindings/db -``` - -Run the following to start the interactive Postgres CLI: - -```bash -docker exec -i -t postgres psql --username postgres -p 5432 -h localhost --no-password -``` - -At the `admin=#` prompt, change to the `orders` table: - -```bash -\c orders; -``` - -At the `orders=#` prompt, select all rows: - -```bash -select * from orders; -``` - -The output should look like this: - -``` - orderid | customer | price ----------+------------+-------- - 1 | John Smith | 100.32 - 2 | Jane Bond | 15.4 - 3 | Tony James | 35.56 -``` - -#### `components\binding-cron.yaml` component file - -When you execute the `dapr run` command and specify the component path, the Dapr sidecar: - -- Initiates the Cron [binding building block]({{< ref bindings >}}) -- Calls the binding endpoint (`batch`) every 10 seconds - -The Cron `binding-cron.yaml` file included for this Quickstart contains the following: - -```yaml -apiVersion: dapr.io/v1alpha1 -kind: Component -metadata: - name: cron - namespace: quickstarts -spec: - type: bindings.cron - version: v1 - metadata: - - name: schedule - value: "@every 10s" # valid cron schedule - - name: direction - value: "input" # direction of the cron binding -``` - -**Note:** The `metadata` section of `binding-cron.yaml` contains a [Cron expression]({{< ref cron.md >}}) that specifies how often the binding is invoked. - -#### `component\binding-postgresql.yaml` component file - -When you execute the `dapr run` command and specify the component path, the Dapr sidecar: - -- Initiates the PostgreSQL [binding building block]({{< ref postgresql.md >}}) -- Connects to PostgreSQL using the settings specified in the `binding-postgresql.yaml` file - -With the `binding-postgresql.yaml` component, you can easily swap out the backend database [binding]({{< ref supported-bindings.md >}}) without making code changes. - -The PostgreSQL `binding-postgresql.yaml` file included for this Quickstart contains the following: - -```yaml -apiVersion: dapr.io/v1alpha1 -kind: Component -metadata: - name: sqldb - namespace: quickstarts -spec: - type: bindings.postgresql - version: v1 - metadata: - - name: url # Required - value: "user=postgres password=docker host=localhost port=5432 dbname=orders pool_min_conns=1 pool_max_conns=10" - - name: direction - value: "output" # direction of the postgresql binding -``` - -In the YAML file: - -- `spec/type` specifies that PostgreSQL is used for this binding. -- `spec/metadata` defines the connection to the PostgreSQL instance used by the component. - -{{% /codetab %}} - -{{< /tabs >}} - -## Run one application at a time - Select your preferred language-specific Dapr SDK before proceeding with the Quickstart. {{< tabs "Python" "JavaScript" ".NET" "Java" "Go" >}} diff --git a/daprdocs/content/en/getting-started/quickstarts/pubsub-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/pubsub-quickstart.md index 3581606d0a4..61306891165 100644 --- a/daprdocs/content/en/getting-started/quickstarts/pubsub-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/pubsub-quickstart.md @@ -21,10 +21,6 @@ You can try out this pub/sub quickstart by either: ## Run using Multi-App Run -{{% alert title="Note" color="primary" %}} - [Multi-App Run]({{< ref multi-app-dapr-run >}}) is currently a preview feature only supported in Linux/MacOS. -{{% /alert %}} - Select your preferred language-specific Dapr SDK before proceeding with the Quickstart. {{< tabs "Python" "JavaScript" ".NET" "Java" "Go" >}} diff --git a/daprdocs/content/en/getting-started/quickstarts/serviceinvocation-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/serviceinvocation-quickstart.md index 08014b5e10b..c33c529f952 100644 --- a/daprdocs/content/en/getting-started/quickstarts/serviceinvocation-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/serviceinvocation-quickstart.md @@ -18,10 +18,6 @@ Learn more about Dapr's methods for service invocation in the [overview article] ## Run using Multi-App Run -{{% alert title="Note" color="primary" %}} - [Multi-App Run]({{< ref multi-app-dapr-run >}}) is currently a preview feature only supported in Linux/MacOS. -{{% /alert %}} - Select your preferred language before proceeding with the Quickstart. {{< tabs "Python" "JavaScript" ".NET" "Java" "Go" >}} diff --git a/daprdocs/content/en/getting-started/quickstarts/statemanagement-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/statemanagement-quickstart.md index e360c660076..acf77540798 100644 --- a/daprdocs/content/en/getting-started/quickstarts/statemanagement-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/statemanagement-quickstart.md @@ -16,10 +16,6 @@ While this sample uses Redis, you can swap it out for any one of the [supported ## Run using Multi-App Run -{{% alert title="Note" color="primary" %}} - [Multi-App Run]({{< ref multi-app-dapr-run >}}) is currently a preview feature only supported in Linux/MacOS. -{{% /alert %}} - Select your preferred language-specific Dapr SDK before proceeding with the Quickstart. {{< tabs "Python" "JavaScript" ".NET" "Java" "Go" >}} From 467bd9de2512a5fbec2f94fc6ae36b8dc87c5bbe Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Fri, 22 Sep 2023 18:57:11 -0400 Subject: [PATCH 069/162] update yaml Signed-off-by: Hannah Hunter --- .../quickstarts/statemanagement-quickstart.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/daprdocs/content/en/getting-started/quickstarts/statemanagement-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/statemanagement-quickstart.md index acf77540798..7eedac31e12 100644 --- a/daprdocs/content/en/getting-started/quickstarts/statemanagement-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/statemanagement-quickstart.md @@ -105,11 +105,11 @@ When you run `dapr init`, Dapr creates a default [Multi-App Run template file]({ ```yml version: 1 common: - resourcesPath: ../../../resources/ + resourcesPath: ../../resources/ apps: - appID: order-processor appDirPath: ./order-processor/ - command: ["dotnet", "run"] + command: ["python3" , "app.py"] ``` ##### `statestore.yaml` component file @@ -242,11 +242,11 @@ When you run `dapr init`, Dapr creates a default Multi-App Run template file nam ```yml version: 1 common: - resourcesPath: ../../../resources/ + resourcesPath: ../../resources/ apps: - appID: order-processor appDirPath: ./order-processor/ - command: ["dotnet", "run"] + command: ["npm", "run", "start"] ``` ##### `statestore.yaml` component file @@ -509,11 +509,11 @@ When you run `dapr init`, Dapr creates a default Multi-App Run template file nam ```yml version: 1 common: - resourcesPath: ../../../resources/ + resourcesPath: ../../resources/ apps: - appID: order-processor appDirPath: ./order-processor/ - command: ["dotnet", "run"] + command: ["java", "-jar", "target/OrderProcessingService-0.0.1-SNAPSHOT.jar"] ``` ##### `statestore.yaml` component file @@ -641,11 +641,11 @@ When you run `dapr init`, Dapr creates a default Multi-App Run template file nam ```yml version: 1 common: - resourcesPath: ../../../resources/ + resourcesPath: ../../resources/ apps: - appID: order-processor appDirPath: ./order-processor/ - command: ["dotnet", "run"] + command: ["go", "run", "."] ``` ##### `statestore.yaml` component file From 8faf24ec9e5a1a120e4e4cd20e77badcec109f8f Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Fri, 22 Sep 2023 19:00:23 -0400 Subject: [PATCH 070/162] update Signed-off-by: Hannah Hunter --- .../quickstarts/statemanagement-quickstart.md | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/daprdocs/content/en/getting-started/quickstarts/statemanagement-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/statemanagement-quickstart.md index 7eedac31e12..4d1224c4eb7 100644 --- a/daprdocs/content/en/getting-started/quickstarts/statemanagement-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/statemanagement-quickstart.md @@ -48,12 +48,6 @@ In a terminal window, navigate to the `order-processor` directory. cd state_management/python/sdk/order-processor ``` -Install the dependencies: - -```bash -pip3 install -r requirements.txt -``` - Run the `order-processor` service alongside a Dapr sidecar using [Multi-App Run]({{< ref multi-app-dapr-run >}}). ```bash @@ -176,17 +170,6 @@ In a terminal window, navigate to the `order-processor` directory. cd state_management/javascript/sdk/order-processor ``` -Install dependencies, which will include the `@dapr/dapr` package from the JavaScript SDK: - -```bash -npm install -``` - -Verify you have the following files included in the service directory: - -- `package.json` -- `package-lock.json` - Run the `order-processor` service alongside a Dapr sidecar. ```bash @@ -313,13 +296,6 @@ In a terminal window, navigate to the `order-processor` directory. cd state_management/csharp/sdk/order-processor ``` -Recall NuGet packages: - -```bash -dotnet restore -dotnet build -``` - Run the `order-processor` service alongside a Dapr sidecar. ```bash @@ -447,12 +423,6 @@ In a terminal window, navigate to the `order-processor` directory. cd state_management/java/sdk/order-processor ``` -Install the dependencies: - -```bash -mvn clean install -``` - Run the `order-processor` service alongside a Dapr sidecar. ```bash @@ -580,12 +550,6 @@ In a terminal window, navigate to the `order-processor` directory. cd state_management/go/sdk/order-processor ``` -Install the dependencies and build the application: - -```bash -go build . -``` - Run the `order-processor` service alongside a Dapr sidecar. ```bash From 9e1e5482828e2a1b6d4fe787b278cbb055b71c1b Mon Sep 17 00:00:00 2001 From: Mark Fussell Date: Sun, 24 Sep 2023 20:22:16 -0700 Subject: [PATCH 071/162] Update daprdocs/content/en/getting-started/quickstarts/bindings-quickstart.md Signed-off-by: Mark Fussell --- .../en/getting-started/quickstarts/bindings-quickstart.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/getting-started/quickstarts/bindings-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/bindings-quickstart.md index 22c56afe25b..cbfa248c94b 100644 --- a/daprdocs/content/en/getting-started/quickstarts/bindings-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/bindings-quickstart.md @@ -11,7 +11,7 @@ Let's take a look at Dapr's [Bindings building block]({{< ref bindings >}}). Usi - Trigger your app with events coming in from external systems. - Interface with external systems. -In this Quickstart, you will schedule a batch script to run every 10 seconds using an input [Cron]({{< ref cron.md >}}) binding. The script processes a JSON file and outputs data to a SQL database using the [PostgreSQL]({{< ref postgresql.md >}}) Dapr binding. +In this Quickstart, you schedule a batch script to run every 10 seconds using an input [Cron]({{< ref cron.md >}}) binding. The script processes a JSON file and outputs data to a SQL database using the [PostgreSQL]({{< ref postgresql.md >}}) Dapr binding. From 7fc9c4a248822e8bf99c3cd6b9d886b4813a2acb Mon Sep 17 00:00:00 2001 From: Mark Fussell Date: Sun, 24 Sep 2023 20:44:16 -0700 Subject: [PATCH 072/162] Update daprdocs/content/en/reference/components-reference/supported-pubsub/setup-rabbitmq.md Signed-off-by: Mark Fussell --- .../components-reference/supported-pubsub/setup-rabbitmq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-rabbitmq.md b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-rabbitmq.md index 89a098c4fe6..f25a543a5f1 100644 --- a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-rabbitmq.md +++ b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-rabbitmq.md @@ -100,7 +100,7 @@ The above example uses secrets as plain strings. It is recommended to use a secr | caCert | Required for using TLS | Certificate Authority (CA) certificate in PEM format for verifying server TLS certificates. | `"-----BEGIN CERTIFICATE-----\n\n-----END CERTIFICATE-----"` | clientCert | Required for using TLS | TLS client certificate in PEM format. Must be used with `clientKey`. | `"-----BEGIN CERTIFICATE-----\n\n-----END CERTIFICATE-----"` | clientKey | Required for using TLS | TLS client key in PEM format. Must be used with `clientCert`. Can be `secretKeyRef` to use a secret reference. | `"-----BEGIN RSA PRIVATE KEY-----\n\n-----END RSA PRIVATE KEY-----"` -| clientName | N | This RabbitMQ [client-provided connection name](https://www.rabbitmq.com/connections.html#client-provided-names) is a custom identifier. If set, the identifier will be mentioned in RabbitMQ server log entries and management UI. Can be set to {uuid}, {podName}, or {appID}, which is replaced by Dapr runtime to the real value. | `"app1"`, `{uuid}`, `{podName}`, `{appID}` +| clientName | N | This RabbitMQ [client-provided connection name](https://www.rabbitmq.com/connections.html#client-provided-names) is a custom identifier. If set, the identifier is mentioned in RabbitMQ server log entries and management UI. Can be set to {uuid}, {podName}, or {appID}, which is replaced by Dapr runtime to the real value. | `"app1"`, `{uuid}`, `{podName}`, `{appID}` | heartBeat | N | Defines the heartbeat interval with the server, detecting the aliveness of the peer TCP connection with the RabbitMQ server. Defaults to `10s` . | `"10s"` From ad50b794b0d0dff1cb58562352d3b0984ea1281f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Aguilar-Tablada=20Espinosa?= Date: Mon, 25 Sep 2023 05:52:01 +0200 Subject: [PATCH 073/162] Create 'quorum' queues in RabbitMQ (#3699) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Create 'quorum' queues in RabbitMQ Signed-off-by: Álvaro Aguilar * Update daprdocs/content/en/reference/components-reference/supported-pubsub/setup-rabbitmq.md Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Signed-off-by: Álvaro Aguilar-Tablada Espinosa * Update daprdocs/content/en/reference/components-reference/supported-pubsub/setup-rabbitmq.md Co-authored-by: Mark Fussell Signed-off-by: Álvaro Aguilar-Tablada Espinosa --------- Signed-off-by: Álvaro Aguilar Signed-off-by: Álvaro Aguilar-Tablada Espinosa Signed-off-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Co-authored-by: Mark Fussell --- .../supported-pubsub/setup-rabbitmq.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-rabbitmq.md b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-rabbitmq.md index f2fecc6501c..c966f69885a 100644 --- a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-rabbitmq.md +++ b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-rabbitmq.md @@ -412,6 +412,24 @@ client.PublishEvent(ctx, PUBSUB_NAME, TOPIC_NAME, []byte(strconv.Itoa(orderId)), {{< /tabs >}} +## Use quorum queues + +By default, Dapr creates `classic` queues. To create `quorum` queues, add the following metadata to your pub/sub [subscription]({{< ref subscription-schema.md >}}) + +```yaml +apiVersion: dapr.io/v2alpha1 +kind: Subscription +metadata: + name: pubsub +spec: + topic: checkout + routes: + default: /orders + pubsubname: order-pub-sub + metadata: + queueType: quorum +``` + ## Time-to-live You can set a time-to-live (TTL) value at either the message or component level. Set default component-level TTL using the component spec `ttlInSeconds` field in your component. From cf9fa335dec8c922d06fa5323c076e275647da79 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Mon, 25 Sep 2023 08:44:29 -0400 Subject: [PATCH 074/162] updates from mark/mukundan Signed-off-by: Hannah Hunter --- .../multi-app-dapr-run/multi-app-overview.md | 2 +- .../multi-app-dapr-run/multi-app-template.md | 2 +- daprdocs/content/en/reference/cli/dapr-run.md | 10 ++++++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md index 64283705de6..50c3e8e32dc 100644 --- a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md +++ b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md @@ -7,7 +7,7 @@ description: Run multiple applications with one CLI command --- {{% alert title="Note" color="primary" %}} - Multi-App Run for **Kubernetes** is currently a preview feature only supported in Linux/MacOS. + Multi-App Run for **Kubernetes** is currently a preview feature. {{% /alert %}} Let's say you want to run several applications locally to test them together, similar to a production scenario. Multi-App Run allows you to start and stop a set of applications simultaneously, either: diff --git a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md index 81cb228e87a..19cedc31dec 100644 --- a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md +++ b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md @@ -7,7 +7,7 @@ description: Unpack the Multi-App Run template file and its properties --- {{% alert title="Note" color="primary" %}} - Multi-App Run for **Kubernetes** is currently a preview feature only supported in Linux/MacOS. + Multi-App Run for **Kubernetes** is currently a preview feature. {{% /alert %}} The Multi-App Run template file is a YAML file that you can use to run multiple applications at once. In this guide, you'll learn how to: diff --git a/daprdocs/content/en/reference/cli/dapr-run.md b/daprdocs/content/en/reference/cli/dapr-run.md index ba79d761fed..b6fed8a8265 100644 --- a/daprdocs/content/en/reference/cli/dapr-run.md +++ b/daprdocs/content/en/reference/cli/dapr-run.md @@ -23,7 +23,6 @@ dapr run [flags] [command] | Name | Environment Variable | Default | Description | | ------------------------------ | -------------------- | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | -| `--kubernetes`, `-k` | | | Running Dapr on Kubernetes, and used for [Multi-App Run template files on Kubernetes]({{< ref multi-app-dapr-run >}}). | | `--app-id`, `-a` | `APP_ID` | | The id for your application, used for service discovery. Cannot contain dots. | | `--app-max-concurrency` | | `unlimited` | The concurrency level of the application; default is unlimited | | `--app-port`, `-p` | `APP_PORT` | | The port your application is listening on | @@ -51,6 +50,7 @@ dapr run [flags] [command] | `--unix-domain-socket`, `-u` | | | Path to a unix domain socket dir mount. If specified, communication with the Dapr sidecar uses unix domain sockets for lower latency and greater throughput when compared to using TCP ports. Not available on Windows. | | `--dapr-http-max-request-size` | | `4` | Max size of the request body in MB. | | `--dapr-http-read-buffer-size` | | `4` | Max size of the HTTP read buffer in KB. This also limits the maximum size of HTTP headers. The default 4 KB | +| `--kubernetes`, `-k` | | | Running Dapr on Kubernetes, and used for [Multi-App Run template files on Kubernetes]({{< ref multi-app-dapr-run >}}). | | `--components-path`, `-d` | | Linux/Mac: `$HOME/.dapr/components`
Windows: `%USERPROFILE%\.dapr\components` | **Deprecated** in favor of `--resources-path` | ### Examples @@ -82,4 +82,10 @@ dapr run --app-id myapp --app-port 3000 --enable-api-logging -- node myapp.js # Pass multiple resource paths dapr run --app-id myapp --resources-path path1 --resources-path path2 -``` + +# Run the multi-app run template file +dapr run -f dapr.yaml + +# Run the multi-app run template file on Kubernetes +dapr run -k -f dapr.yaml +``` \ No newline at end of file From cbcda3f29c83c277499fd5f26cbb3f409fd8a161 Mon Sep 17 00:00:00 2001 From: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Date: Mon, 25 Sep 2023 10:52:50 -0400 Subject: [PATCH 075/162] Update daprdocs/content/en/reference/api/placement_api.md Co-authored-by: Mark Fussell Signed-off-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> --- daprdocs/content/en/reference/api/placement_api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/reference/api/placement_api.md b/daprdocs/content/en/reference/api/placement_api.md index bd7eee3cd0c..5aa47a965d5 100644 --- a/daprdocs/content/en/reference/api/placement_api.md +++ b/daprdocs/content/en/reference/api/placement_api.md @@ -8,7 +8,7 @@ weight: 1200 Dapr has an HTTP API `/placement/state` for placement service that exposes placement table information. The API is exposed on the sidecar on the same port as the healthz. This is an unauthenticated endpoint, and is disabled by default. -You need to set `DAPR_PLACEMENT_METADATA_ENABLED` environment or `metadata-enabled` command line args to true to enable it. If you are using helm, set `dapr_placement.metadataEnabled` to true. +You need to set either set`DAPR_PLACEMENT_METADATA_ENABLED` environment variable or `metadata-enabled` command line args on the Placement service to true to enable it. If you are using Helm for deployment of the Placement service then, set `dapr_placement.metadataEnabled` to true. ## Usecase From d202a515a11de14ac8ca4b0549340d7a84bad325 Mon Sep 17 00:00:00 2001 From: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Date: Mon, 25 Sep 2023 10:53:42 -0400 Subject: [PATCH 076/162] Update daprdocs/content/en/reference/environment/_index.md Co-authored-by: Mark Fussell Signed-off-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> --- daprdocs/content/en/reference/environment/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/reference/environment/_index.md b/daprdocs/content/en/reference/environment/_index.md index f5081802f79..9ae9b7b22f1 100644 --- a/daprdocs/content/en/reference/environment/_index.md +++ b/daprdocs/content/en/reference/environment/_index.md @@ -27,4 +27,4 @@ The following table lists the environment variables used by the Dapr runtime, CL | OTEL_EXPORTER_OTLP_PROTOCOL | OpenTelemetry Tracing | The OTLP protocol to use Transport protocol. (`grpc`, `http/protobuf`, `http/json`) | | DAPR_COMPONENTS_SOCKETS_FOLDER | Dapr runtime and the .NET, Go, and Java pluggable component SDKs | The location or path where Dapr looks for Pluggable Components Unix Domain Socket files. If unset this location defaults to `/tmp/dapr-components-sockets` | | DAPR_COMPONENTS_SOCKETS_EXTENSION | .NET and Java pluggable component SDKs | A per-SDK configuration that indicates the default file extension applied to socket files created by the SDKs. Not a Dapr-enforced behavior. | -| DAPR_PLACEMENT_METADATA_ENABLED | Dapr placement | Enable an endpoint for the Placement service that exposes placement table information on actor usage. Set to `true` to enable. [Learn more about the Placement API]({{< ref placement_api.md >}}) | \ No newline at end of file +| DAPR_PLACEMENT_METADATA_ENABLED | Dapr placement | Enable an endpoint for the Placement service that exposes placement table information on actor usage. Set to `true` to enable in self-hosted mode. [Learn more about the Placement API]({{< ref placement_api.md >}}) | \ No newline at end of file From dfd193c9279aff29e76ce1f04ae4a6f9fd2558ed Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Mon, 25 Sep 2023 12:40:23 -0400 Subject: [PATCH 077/162] update per mark Signed-off-by: Hannah Hunter --- daprdocs/content/en/reference/api/placement_api.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/daprdocs/content/en/reference/api/placement_api.md b/daprdocs/content/en/reference/api/placement_api.md index bd7eee3cd0c..0ffd8d77714 100644 --- a/daprdocs/content/en/reference/api/placement_api.md +++ b/daprdocs/content/en/reference/api/placement_api.md @@ -8,7 +8,9 @@ weight: 1200 Dapr has an HTTP API `/placement/state` for placement service that exposes placement table information. The API is exposed on the sidecar on the same port as the healthz. This is an unauthenticated endpoint, and is disabled by default. -You need to set `DAPR_PLACEMENT_METADATA_ENABLED` environment or `metadata-enabled` command line args to true to enable it. If you are using helm, set `dapr_placement.metadataEnabled` to true. +To enable the placement metadata in self-hosted mode you can either set`DAPR_PLACEMENT_METADATA_ENABLED` environment variable or `metadata-enabled` command line args on the Placement service to `true` to. See [how to run the Placement service in self-hosted mode]({{< ref "self-hosted-no-docker/#enable-actors >}}). + +If you are using Helm for deployment of the Placement service on Kubernetes then to enable the placement metadata, set `dapr_placement.metadataEnabled` to `true`. ## Usecase From 6495d52b1f3a875fa75d8bbf3261ab7f34b29655 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Mon, 25 Sep 2023 12:46:50 -0400 Subject: [PATCH 078/162] update link Signed-off-by: Hannah Hunter --- daprdocs/content/en/reference/api/placement_api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/reference/api/placement_api.md b/daprdocs/content/en/reference/api/placement_api.md index 0ffd8d77714..a882eb8618e 100644 --- a/daprdocs/content/en/reference/api/placement_api.md +++ b/daprdocs/content/en/reference/api/placement_api.md @@ -8,7 +8,7 @@ weight: 1200 Dapr has an HTTP API `/placement/state` for placement service that exposes placement table information. The API is exposed on the sidecar on the same port as the healthz. This is an unauthenticated endpoint, and is disabled by default. -To enable the placement metadata in self-hosted mode you can either set`DAPR_PLACEMENT_METADATA_ENABLED` environment variable or `metadata-enabled` command line args on the Placement service to `true` to. See [how to run the Placement service in self-hosted mode]({{< ref "self-hosted-no-docker/#enable-actors >}}). +To enable the placement metadata in self-hosted mode you can either set`DAPR_PLACEMENT_METADATA_ENABLED` environment variable or `metadata-enabled` command line args on the Placement service to `true` to. See [how to run the Placement service in self-hosted mode]({{< ref "self-hosted-no-docker.md#enable-actors" >}}). If you are using Helm for deployment of the Placement service on Kubernetes then to enable the placement metadata, set `dapr_placement.metadataEnabled` to `true`. From b57960d8d44bdac5c4838f00efd9c8d5585d0f16 Mon Sep 17 00:00:00 2001 From: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Date: Mon, 25 Sep 2023 16:19:10 -0400 Subject: [PATCH 079/162] update healthz (#3765) Signed-off-by: Hannah Hunter --- daprdocs/content/en/reference/arguments-annotations-overview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/reference/arguments-annotations-overview.md b/daprdocs/content/en/reference/arguments-annotations-overview.md index a1c044a68d8..68eab9812c3 100644 --- a/daprdocs/content/en/reference/arguments-annotations-overview.md +++ b/daprdocs/content/en/reference/arguments-annotations-overview.md @@ -39,7 +39,7 @@ This table is meant to help users understand the equivalent options for running | `--profiling-port` | `--profiling-port` | | not supported | The port for the profile server (default `7777`) | | `--app-protocol` | `--app-protocol` | `-P` | `dapr.io/app-protocol` | Configures the protocol Dapr uses to communicate with your app. Valid options are `http`, `grpc`, `https` (HTTP with TLS), `grpcs` (gRPC with TLS), `h2c` (HTTP/2 Cleartext). Note that Dapr does not validate TLS certificates presented by the app. Default is `http` | | `--enable-app-health-check` | `--enable-app-health-check` | | `dapr.io/enable-app-health-check` | Boolean that enables the [health checks]({{< ref "app-health.md#configuring-app-health-checks" >}}). Default is `false`. | -| `--app-health-check-path` | `--app-health-check-path` | | `dapr.io/app-health-check-path` | Path that Dapr invokes for health probes when the app channel is HTTP (this value is ignored if the app channel is using gRPC). Requires [app health checks to be enabled]({{< ref "app-health.md#configuring-app-health-checks" >}}). Default is `/health`. | +| `--app-health-check-path` | `--app-health-check-path` | | `dapr.io/app-health-check-path` | Path that Dapr invokes for health probes when the app channel is HTTP (this value is ignored if the app channel is using gRPC). Requires [app health checks to be enabled]({{< ref "app-health.md#configuring-app-health-checks" >}}). Default is `/healthz`. | | `--app-health-probe-interval` | `--app-health-probe-interval` | | `dapr.io/app-health-probe-interval` | Number of *seconds* between each health probe. Requires [app health checks to be enabled]({{< ref "app-health.md#configuring-app-health-checks" >}}). Default is `5` | | `--app-health-probe-timeout` | `--app-health-probe-timeout` | | `dapr.io/app-health-probe-timeout` | Timeout in *milliseconds* for health probe requests. Requires [app health checks to be enabled]({{< ref "app-health.md#configuring-app-health-checks" >}}). Default is `500` | | `--app-health-threshold` | `--app-health-threshold` | | `dapr.io/app-health-threshold"` | Max number of consecutive failures before the app is considered unhealthy. Requires [app health checks to be enabled]({{< ref "app-health.md#configuring-app-health-checks" >}}). Default is `3` | From 7e80a0f9163eec6ef90f383140cfc88ea927f399 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Mon, 25 Sep 2023 16:27:10 -0400 Subject: [PATCH 080/162] add link Signed-off-by: Hannah Hunter --- daprdocs/content/en/concepts/dapr-services/placement.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/daprdocs/content/en/concepts/dapr-services/placement.md b/daprdocs/content/en/concepts/dapr-services/placement.md index 5cb1d999542..7db47a37491 100644 --- a/daprdocs/content/en/concepts/dapr-services/placement.md +++ b/daprdocs/content/en/concepts/dapr-services/placement.md @@ -17,7 +17,7 @@ The placement service is deployed as part of `dapr init -k`, or via the Dapr Hel ## Placement tables -There is an HTTP API `/placement/state` for placement service that exposes placement table information. The API is exposed on the sidecar on the same port as the healthz. This is an unauthenticated endpoint, and is disabled by default. You need to set `DAPR_PLACEMENT_METADATA_ENABLED` environment or `metadata-enabled` command line args to true to enable it. If you are using helm you just need to set `dapr_placement.metadataEnabled` to true. +There is an [HTTP API `/placement/state` for placement service]({{< ref placement_api.md >}}) that exposes placement table information. The API is exposed on the sidecar on the same port as the healthz. This is an unauthenticated endpoint, and is disabled by default. You need to set `DAPR_PLACEMENT_METADATA_ENABLED` environment or `metadata-enabled` command line args to true to enable it. If you are using helm you just need to set `dapr_placement.metadataEnabled` to true. ### Usecase: The placement table API can be used for retrieving the current placement table, which contains all the actors registered. This can be helpful for debugging and allows tools to extract and present information about actors. @@ -83,3 +83,7 @@ updatedAt | timestamp | Timestamp of the actor registered/updated. "tableVersion": 1 } ``` + +## Related links + +[Learn more about the Placement API.]({{< ref placement_api.md >}}) \ No newline at end of file From 85748169bd88e3a91a10d7db733ff09786355967 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Tue, 26 Sep 2023 11:47:56 -0400 Subject: [PATCH 081/162] submodules Signed-off-by: Hannah Hunter --- sdkdocs/dotnet | 2 +- sdkdocs/go | 2 +- sdkdocs/java | 2 +- sdkdocs/js | 2 +- sdkdocs/python | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sdkdocs/dotnet b/sdkdocs/dotnet index 2449bcd6691..99d874a2b13 160000 --- a/sdkdocs/dotnet +++ b/sdkdocs/dotnet @@ -1 +1 @@ -Subproject commit 2449bcd6691eb49825e0e8e9dff50bd50fd41c2e +Subproject commit 99d874a2b138af020df099a0fc0a09a7d0597fae diff --git a/sdkdocs/go b/sdkdocs/go index ad25580bcfb..e16e0350a52 160000 --- a/sdkdocs/go +++ b/sdkdocs/go @@ -1 +1 @@ -Subproject commit ad25580bcfb638d56237faec0543565b4d0e134f +Subproject commit e16e0350a52349b5a05138edc0b58e3be78ee753 diff --git a/sdkdocs/java b/sdkdocs/java index 9dc842faba3..96c04185212 160000 --- a/sdkdocs/java +++ b/sdkdocs/java @@ -1 +1 @@ -Subproject commit 9dc842faba3486e518babc29f7fbbca79248bfab +Subproject commit 96c04185212d29c6c9d6cfa2b9e18062240f5311 diff --git a/sdkdocs/js b/sdkdocs/js index 7686ab039bc..df7eff281a5 160000 --- a/sdkdocs/js +++ b/sdkdocs/js @@ -1 +1 @@ -Subproject commit 7686ab039bcc30f375f922960020d403dd2d3867 +Subproject commit df7eff281a5a1395a7967c658a5707e8dfb2b99e diff --git a/sdkdocs/python b/sdkdocs/python index 64e834b0a06..010a5b34165 160000 --- a/sdkdocs/python +++ b/sdkdocs/python @@ -1 +1 @@ -Subproject commit 64e834b0a06f5b218efc941b8caf3683968b7208 +Subproject commit 010a5b34165b7e8f688b4ad5a8b097c21efacb61 From 1ce09dfecb5e834e7ba02466ec55d1af07fc14f7 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Mon, 2 Oct 2023 14:55:50 -0400 Subject: [PATCH 082/162] add java to features doc Signed-off-by: Hannah Hunter --- .../workflow/workflow-features-concepts.md | 102 +++++++++++++++++- 1 file changed, 100 insertions(+), 2 deletions(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-features-concepts.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-features-concepts.md index e957ade3d56..ce39d4bac96 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-features-concepts.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-features-concepts.md @@ -162,7 +162,7 @@ APIs that generate random numbers, random UUIDs, or the current date are _non-de For example, instead of this: -{{< tabs ".NET" >}} +{{< tabs ".NET" Java >}} {{% codetab %}} @@ -175,11 +175,22 @@ string randomString = GetRandomString(); {{% /codetab %}} +{{% codetab %}} + +```java +// DON'T DO THIS! +Instant currentTime = Instant.now(); +UUID newIdentifier = UUID.randomUUID(); +string randomString = GetRandomString(); +``` + +{{% /codetab %}} + {{< /tabs >}} Do this: -{{< tabs ".NET" >}} +{{< tabs ".NET" Java >}} {{% codetab %}} @@ -192,6 +203,17 @@ string randomString = await context.CallActivityAsync("GetRandomString") {{% /codetab %}} +{{% codetab %}} + +```java +// Do this!! +Instant currentTime = context.getCurrentInstant(); +Guid newIdentifier = context.NewGuid(); +String randomString = context.callActivity(GetRandomString.class.getName(), String.class).await(); +``` + +{{% /codetab %}} + {{< /tabs >}} @@ -202,20 +224,58 @@ Instead, workflows should interact with external state _indirectly_ using workfl For example, instead of this: +{{< tabs ".NET" Java >}} + +{{% codetab %}} + ```csharp // DON'T DO THIS! string configuration = Environment.GetEnvironmentVariable("MY_CONFIGURATION")!; string data = await new HttpClient().GetStringAsync("https://example.com/api/data"); ``` +{{% /codetab %}} + +{{% codetab %}} + +```java +// DON'T DO THIS! +String configuration = System.getenv("MY_CONFIGURATION"); + +HttpRequest request = HttpRequest.newBuilder().uri(new URI("https://postman-echo.com/post")).GET().build(); +HttpResponse response = HttpClient.newBuilder().build().send(request, HttpResponse.BodyHandlers.ofString()); +``` + +{{% /codetab %}} + +{{< /tabs >}} Do this: +{{< tabs ".NET" Java >}} + +{{% codetab %}} + ```csharp // Do this!! string configuation = workflowInput.Configuration; // imaginary workflow input argument string data = await context.CallActivityAsync("MakeHttpCall", "https://example.com/api/data"); ``` +{{% /codetab %}} + +{{% codetab %}} + +```java +// Do this!! +String configuation = ctx.getInput(InputType.class).getConfiguration(); // imaginary workflow input argument +String data = ctx.callActivity(MakeHttpCall.class, "https://example.com/api/data", String.class).await(); +``` + +{{% /codetab %}} + +{{< /tabs >}} + + #### Workflow functions must execute only on the workflow dispatch thread. The implementation of each language SDK requires that all workflow function operations operate on the same thread (goroutine, etc.) that the function was scheduled on. Workflow functions must never: - Schedule background threads, or @@ -225,20 +285,58 @@ Failure to follow this rule could result in undefined behavior. Any background p For example, instead of this: +{{< tabs ".NET" Java >}} + +{{% codetab %}} + ```csharp // DON'T DO THIS! Task t = Task.Run(() => context.CallActivityAsync("DoSomething")); await context.CreateTimer(5000).ConfigureAwait(false); ``` +{{% /codetab %}} + +{{% codetab %}} + +```java +// DON'T DO THIS! +new Thread(() -> { + ctx.callActivity(DoSomethingActivity.class.getName()).await(); +}).start(); +ctx.createTimer(Duration.ofSeconds(5)).await(); +``` + +{{% /codetab %}} + +{{< /tabs >}} Do this: +{{< tabs ".NET" Java >}} + +{{% codetab %}} + ```csharp // Do this!! Task t = context.CallActivityAsync("DoSomething"); await context.CreateTimer(5000).ConfigureAwait(true); ``` +{{% /codetab %}} + +{{% codetab %}} + +```java +// Do this!! +ctx.callActivity(DoSomethingActivity.class.getName()).await(); +ctx.createTimer(Duration.ofSeconds(5)).await(); +``` + +{{% /codetab %}} + +{{< /tabs >}} + + ### Updating workflow code Make sure updates you make to the workflow code maintain its determinism. A couple examples of code updates that can break workflow determinism: From 116e50bbcde441650c2d3e244b520771a39dba73 Mon Sep 17 00:00:00 2001 From: Yaron Schneider Date: Tue, 3 Oct 2023 10:02:09 -0700 Subject: [PATCH 083/162] Add Outbox docs (#3763) * add outbox docs Signed-off-by: yaron2 * update preview features Signed-off-by: yaron2 * Update daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md Co-authored-by: Mark Fussell Signed-off-by: Yaron Schneider * Update daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md Co-authored-by: Mark Fussell Signed-off-by: Yaron Schneider * Update daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md Co-authored-by: Mark Fussell Signed-off-by: Yaron Schneider * updates Signed-off-by: yaron2 * Update daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md Co-authored-by: Mark Fussell Signed-off-by: Yaron Schneider * Update daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md Co-authored-by: Mark Fussell Signed-off-by: Yaron Schneider * Update daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md Co-authored-by: Mark Fussell Signed-off-by: Yaron Schneider * Update daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md Co-authored-by: Mark Fussell Signed-off-by: Yaron Schneider * Update daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md Co-authored-by: Mark Fussell Signed-off-by: Yaron Schneider * add demo Signed-off-by: yaron2 * change redis to mysql Signed-off-by: yaron2 * add outboxDiscardWhenMissingState clarification Signed-off-by: yaron2 * added diagram Signed-off-by: yaron2 * Update daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Signed-off-by: Yaron Schneider * Update daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Signed-off-by: Yaron Schneider * Update daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Signed-off-by: Yaron Schneider * Update daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Signed-off-by: Yaron Schneider * Update daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Signed-off-by: Yaron Schneider * Update daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Signed-off-by: Yaron Schneider * Update daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Signed-off-by: Yaron Schneider * Update daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Signed-off-by: Yaron Schneider --------- Signed-off-by: yaron2 Signed-off-by: Yaron Schneider Co-authored-by: Mark Fussell Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> --- .../building-blocks/pubsub/pubsub-overview.md | 4 + .../state-management/howto-outbox.md | 112 ++++++++++++++++++ .../state-management-overview.md | 4 + .../support/support-preview-features.md | 1 + .../static/images/state-management-outbox.png | Bin 0 -> 376238 bytes 5 files changed, 121 insertions(+) create mode 100644 daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md create mode 100644 daprdocs/static/images/state-management-outbox.png diff --git a/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-overview.md b/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-overview.md index 41c9ac23b6c..ed6b72cc38d 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-overview.md +++ b/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-overview.md @@ -96,6 +96,10 @@ For more information on message routing, read [Dapr pub/sub API reference]({{< r Sometimes, messages can't be processed because of a variety of possible issues, such as erroneous conditions within the producer or consumer application or an unexpected state change that causes an issue with your application code. Dapr allows developers to set dead letter topics to deal with messages that cannot be delivered to an application. This feature is available on all pub/sub components and prevents consumer applications from endlessly retrying a failed message. For more information, read about [dead letter topics]({{< ref "pubsub-deadletter.md">}}) +### Enabling the outbox pattern + +Dapr enables developers to use the outbox pattern for achieving a single transaction across a transactional state store and any message broker. For more information, read [How to enable transactional outbox messaging]({{< ref howto-outbox.md >}}) + ### Namespace consumer groups Dapr solves multi-tenancy at-scale with [namespaces for consumer groups]({{< ref howto-namespace >}}). Simply include the `"{namespace}"` value in your component metadata for consumer groups to allow multiple namespaces with applications of the same `app-id` to publish and subscribe to the same message broker. diff --git a/daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md b/daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md new file mode 100644 index 00000000000..c53930f2acd --- /dev/null +++ b/daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md @@ -0,0 +1,112 @@ +--- +type: docs +title: "How-To: Enable the transactional outbox pattern" +linkTitle: "How-To: Enable the transactional outbox pattern" +weight: 400 +description: "Commit a single transaction across a state store and pub/sub message broker" +--- + +The transactional outbox pattern is a well known design pattern for sending notifications regarding changes in an application's state. The transactional outbox pattern uses a single transaction that spans across the database and the message broker delivering the notification. + +Developers are faced with many difficult technical challenges when trying to implement this pattern on their own, which often involves writing error-prone central coordination managers that, at most, support a combination of one or two databases and message brokers. + +For example, you can use the outbox pattern to: +1. Write a new user record to an account database. +1. Send a notification message that the account was successfully created. + +With Dapr's outbox support, you can notify subscribers when an application's state is created or updated when calling Dapr's [transactions API]({{< ref "state_api.md#state-transactions" >}}). + +The diagram below is an overview of how the outbox feature works: + +1) Service A saves/updates state to the state store using a transaction. +2) A message is written to the broker under the same transaction. When the message is successfully delivered to the message broker, the transaction completes, ensuring the state and message are transacted together. +3) The message broker delivers the message topic to any subscribers - in this case, Service B. + +Diagram showing the steps of the outbox pattern + +## Requirements + +The outbox feature can be used with using any [transactional state store]({{< ref supported-state-stores >}}) supported by Dapr. All [pub/sub brokers]({{< ref supported-pubsub >}}) are supported with the outbox feature. + +{{% alert title="Note" color="primary" %}} +Message brokers that work with the competing consumer pattern (for example, [Apache Kafka]({{< ref setup-apache-kafka>}}) are encouraged to reduce the chances of duplicate events. +{{% /alert %}} + +## Usage + +To enable the outbox feature, add the following required and optional fields on a state store component: + +```yaml +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: mysql-outbox +spec: + type: state.mysql + version: v1 + metadata: + - name: connectionString + value: "" + - name: outboxPublishPubsub # Required + value: "mypubsub" + - name: outboxPublishTopic # Required + value: "newOrder" + - name: outboxPubsub # Optional + value: "myOutboxPubsub" + - name: outboxDiscardWhenMissingState #Optional. Defaults to false + value: false +``` + +### Metadata fields + +| Name | Required | Default Value | Description | +| --------------------|-------------|---------------|------------------------------------------------------- | +| outboxPublishPubsub | Yes | N/A | Sets the name of the pub/sub component to deliver the notifications when publishing state changes +| outboxPublishTopic | Yes | N/A | Sets the topic that receives the state changes on the pub/sub configured with `outboxPublishPubsub`. The message body will be a state transaction item for an `insert` or `update` operation +| outboxPubsub | No | `outboxPublishPubsub` | Sets the pub/sub component used by Dapr to coordinate the state and pub/sub transactions. If not set, the pub/sub component configured with `outboxPublishPubsub` is used. This is useful if you want to separate the pub/sub component used to send the notification state changes from the one used to coordinate the transaction +| outboxDiscardWhenMissingState | No | `false` | By setting `outboxDiscardWhenMissingState` to `true`, Dapr discards the transaction if it cannot find the state in the database and does not retry. This setting can be useful if the state store data has been deleted for any reason before Dapr was able to deliver the message and you would like Dapr to drop the items from the pub/sub and stop retrying to fetch the state + +### Combining outbox and non-outbox messages on the same state store + +If you want to use the same state store for sending both outbox and non-outbox messages, simply define two state store components that connect to the same state store, where one has the outbox feature and the other does not. + +#### MySQL state store without outbox + +```yaml +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: mysql +spec: + type: state.mysql + version: v1 + metadata: + - name: connectionString + value: "" +``` + +#### MySQL state store with outbox + +```yaml +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: mysql-outbox +spec: + type: state.mysql + version: v1 + metadata: + - name: connectionString + value: "" + - name: outboxPublishPubsub # Required + value: "mypubsub" + - name: outboxPublishTopic # Required + value: "newOrder" +``` + +## Demo + +Watch [this video for an overview of the outbox pattern](https://youtu.be/rTovKpG0rhY?t=1338): + +
+ diff --git a/daprdocs/content/en/developing-applications/building-blocks/state-management/state-management-overview.md b/daprdocs/content/en/developing-applications/building-blocks/state-management/state-management-overview.md index afc6bd5f1e4..89c31dc5e1b 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/state-management/state-management-overview.md +++ b/daprdocs/content/en/developing-applications/building-blocks/state-management/state-management-overview.md @@ -116,6 +116,10 @@ Dapr enables states to be: For more details read [How-To: Share state between applications]({{< ref howto-share-state.md >}}), +### Enabling the outbox pattern + +Dapr enables developers to use the outbox pattern for achieving a single transaction across a transactional state store and any message broker. For more information, read [How to enable transactional outbox messaging]({{< ref howto-outbox.md >}}) + ### Querying state There are two ways to query the state: diff --git a/daprdocs/content/en/operations/support/support-preview-features.md b/daprdocs/content/en/operations/support/support-preview-features.md index 2c96fd5f5cc..4d19ea9a194 100644 --- a/daprdocs/content/en/operations/support/support-preview-features.md +++ b/daprdocs/content/en/operations/support/support-preview-features.md @@ -22,6 +22,7 @@ For CLI there is no explicit opt-in, just the version that this was first made a | **Cryptography** | Encrypt or decrypt data without having to manage secrets keys | N/A | [Cryptography concept]({{< ref "components-concept#cryptography" >}})| v1.11 | | **Service invocation for non-Dapr endpoints** | Allow the invocation of non-Dapr endpoints by Dapr using the [Service invocation API]({{< ref service_invocation_api.md >}}). Read ["How-To: Invoke Non-Dapr Endpoints using HTTP"]({{< ref howto-invoke-non-dapr-endpoints.md >}}) for more information. | N/A | [Service invocation API]({{< ref service_invocation_api.md >}}) | v1.11 | | **Actor State TTL** | Allow actors to save records to state stores with Time To Live (TTL) set to automatically clean up old data. In its current implementation, actor state with TTL may not be reflected correctly by clients, read [Actor State Transactions]({{< ref actors_api.md >}}) for more information. | `ActorStateTTL` | [Actor State Transactions]({{< ref actors_api.md >}}) | v1.11 | +| **Transactional Outbox** | Allows state operations for inserts and updates to be published to a configured pub/sub topic using a single transaction across the state store and the pub/sub | N/A | [Transactional Outbox Feature]({{< ref howto-outbox.md >}}) | v1.12 | ### Streaming for HTTP service invocation diff --git a/daprdocs/static/images/state-management-outbox.png b/daprdocs/static/images/state-management-outbox.png new file mode 100644 index 0000000000000000000000000000000000000000..4ad434512b845578f3eb8b5171ab1de352d33b8e GIT binary patch literal 376238 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ⅇ%{Q0CicSI6lIG`hn(bdp$ENL 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 Date: Tue, 3 Oct 2023 19:39:16 -0700 Subject: [PATCH 084/162] add tls authentication for httpendpoint (#3780) Signed-off-by: yaron2 --- .../howto-invoke-non-dapr-endpoints.md | 46 +++++++++++++++++++ .../resource-specs/httpendpoints-schema.md | 14 ++++++ 2 files changed, 60 insertions(+) diff --git a/daprdocs/content/en/developing-applications/building-blocks/service-invocation/howto-invoke-non-dapr-endpoints.md b/daprdocs/content/en/developing-applications/building-blocks/service-invocation/howto-invoke-non-dapr-endpoints.md index 4c5e0224dde..ec48330e35c 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/service-invocation/howto-invoke-non-dapr-endpoints.md +++ b/daprdocs/content/en/developing-applications/building-blocks/service-invocation/howto-invoke-non-dapr-endpoints.md @@ -79,6 +79,52 @@ localhost:3500/v1.0/invoke//method/ curl http://localhost:3602/v1.0/invoke/orderprocessor/method/checkout ``` +## TLS authentication + +Using the [HTTPEndpoint resource]({{< ref httpendpoints-schema.md >}}) allows you to use any combination of a root certificate, client certificate and private key according to the authentication requirements of the remote endpoint. + +### Example using root certificate + +```yaml +apiVersion: dapr.io/v1alpha1 +kind: HTTPEndpoint +metadata: + name: "external-http-endpoint-tls" +spec: + baseUrl: https://service-invocation-external:443 + headers: + - name: "Accept-Language" + value: "en-US" + clientTLS: + rootCA: + secretKeyRef: + name: dapr-tls-client + key: ca.crt +``` + +### Example using client certificate and private key + +```yaml +apiVersion: dapr.io/v1alpha1 +kind: HTTPEndpoint +metadata: + name: "external-http-endpoint-tls" +spec: + baseUrl: https://service-invocation-external:443 + headers: + - name: "Accept-Language" + value: "en-US" + clientTLS: + certificate: + secretKeyRef: + name: dapr-tls-client + key: tls.crt + privateKey: + secretKeyRef: + name: dapr-tls-key + key: tls.key +``` + ## Related Links - [HTTPEndpoint reference]({{< ref httpendpoints-schema.md >}}) diff --git a/daprdocs/content/en/reference/resource-specs/httpendpoints-schema.md b/daprdocs/content/en/reference/resource-specs/httpendpoints-schema.md index f6bced2a3c1..6517d4795d8 100644 --- a/daprdocs/content/en/reference/resource-specs/httpendpoints-schema.md +++ b/daprdocs/content/en/reference/resource-specs/httpendpoints-schema.md @@ -27,6 +27,19 @@ spec: secretKeyRef: name: key: + clientTLS: + rootCA: + secretKeyRef: + name: + key: + certificate: + secretKeyRef: + name: + key: + privateKey: + secretKeyRef: + name: + key: scopes: # Optional - auth: # Optional @@ -39,6 +52,7 @@ auth: # Optional |--------------------|:--------:|---------|---------| | baseUrl | Y | Base URL of the non-Dapr endpoint | `"https://api.github.com"`, `"http://api.github.com"` | headers | N | HTTP request headers for service invocation | `name: "Accept-Language" value: "en-US"`
`name: "Authorization" secretKeyRef.name: "my-secret" secretKeyRef.key: "myGithubToken" ` +| clientTLS | N | Enables TLS authentication to an endpoint with any standard combination of root certificate, client certificate and private key ## Related links From 2aed48cff2cbed8fa5aaca6df59906c0033f0fdc Mon Sep 17 00:00:00 2001 From: Shivam Kumar Date: Thu, 5 Oct 2023 04:12:44 +0530 Subject: [PATCH 085/162] Azure eventhubs bulk subscribe support (#3756) * azure eventhubs bulk subscribe support Signed-off-by: Shivam Kumar * removing localized link Signed-off-by: Shivam Kumar * Apply suggestions from code review Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Signed-off-by: Shivam Kumar * Adding example Signed-off-by: Shivam Kumar --------- Signed-off-by: Shivam Kumar Co-authored-by: Mark Fussell Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> --- .../supported-pubsub/setup-azure-eventhubs.md | 50 ++++++++++++++++++- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-azure-eventhubs.md b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-azure-eventhubs.md index 24aee2d4c1b..40d63bdfe75 100644 --- a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-azure-eventhubs.md +++ b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-azure-eventhubs.md @@ -117,14 +117,60 @@ spec: value: "myeventhubstoragecontainer" ``` -## Sending multiple messages +## Sending and receiving multiple messages -Azure Event Hubs supports sending multiple messages in a single operation. To set the metadata for bulk operations, set the query parameters on the HTTP request or the gRPC metadata as documented [here]({{< ref pubsub_api >}}) +Azure Eventhubs supports sending and receiving multiple messages in a single operation using the bulk pub/sub API. + +### Configuring bulk publish + +To set the metadata for bulk publish operation, set the query parameters on the HTTP request or the gRPC metadata, [as documented in the API reference]({{< ref pubsub_api >}}). | Metadata | Default | |----------|---------| | `metadata.maxBulkPubBytes` | `1000000` | +### Configuring bulk subscribe + +When subscribing to a topic, you can configure `bulkSubscribe` options. Refer to [Subscribing messages in bulk]({{< ref "pubsub-bulk#subscribing-messages-in-bulk" >}}) for more details and to learn more about [the bulk subscribe API]({{< ref pubsub-bulk.md >}}). + +| Configuration | Default | +|---------------|---------| +| `maxMessagesCount` | `100` | +| `maxAwaitDurationMs` | `10000` | + +## Configuring checkpoint frequency + +When subscribing to a topic, you can configure the checkpointing frequency in a partition by [setting the metadata in the HTTP or gRPC subscribe request ]({{< ref "pubsub_api.md#http-request-2" >}}). This metadata enables checkpointing after the configured number of events within a partition event sequence. Disable checkpointing by setting the frequency to `0`. + +[Learn more about checkpointing](https://learn.microsoft.com/azure/event-hubs/event-hubs-features#checkpointing). + +| Metadata | Default | +| -------- | ------- | +| `metadata.checkPointFrequencyPerPartition` | `1` | + +Following example shows a sample subscription file for [Declarative subscription]({{< ref "subscription-methods.md#declarative-subscriptions" >}}) using `checkPointFrequencyPerPartition` metadata. Similarly, you can also pass the metadata in [Programmatic subscriptions]({{< ref "subscription-methods.md#programmatic-subscriptions" >}}) as well. + +```yaml +apiVersion: dapr.io/v2alpha1 +kind: Subscription +metadata: + name: order-pub-sub +spec: + topic: orders + routes: + default: /checkout + pubsubname: order-pub-sub + metadata: + checkPointFrequencyPerPartition: 1 +scopes: +- orderprocessing +- checkout +``` + +{{% alert title="Note" color="primary" %}} +When subscribing to a topic using `BulkSubscribe`, you configure the checkpointing to occur after the specified number of _batches,_ instead of events, where _batch_ means the collection of events received in a single request. +{{% /alert %}} + ## Create an Azure Event Hub Follow the instructions on the [documentation](https://docs.microsoft.com/azure/event-hubs/event-hubs-create) to set up Azure Event Hubs. From 88a109b3d2110e5b256045506cf219189039bc60 Mon Sep 17 00:00:00 2001 From: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Date: Wed, 4 Oct 2023 18:59:29 -0400 Subject: [PATCH 086/162] add java monitor pattern example (#3774) Signed-off-by: Hannah Hunter Co-authored-by: Mark Fussell --- .../workflow/workflow-patterns.md | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md index 49b5fc2db01..9d23a64062f 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md @@ -464,7 +464,41 @@ public override async Task RunAsync(WorkflowContext context, MyEntitySta ```java -todo +public class MonitorWorkflow extends Workflow { + + @Override + public WorkflowStub create() { + return ctx -> { + + Duration nextSleepInterval; + + var status = ctx.callActivity(DemoWorkflowStatusActivity.class.getName(), DemoStatusActivityOutput.class).await(); + var isHealthy = status.getIsHealthy(); + + if (isHealthy) { + // Check less frequently when in a healthy state + nextSleepInterval = Duration.ofMinutes(60); + } else { + + ctx.callActivity(DemoWorkflowAlertActivity.class.getName()).await(); + + // Check more frequently when in an unhealthy state + nextSleepInterval = Duration.ofMinutes(5); + } + + // Put the workflow to sleep until the determined time + // Note: ctx.createTimer() method is not supported in the Java SDK yet + try { + TimeUnit.SECONDS.sleep(nextSleepInterval.getSeconds()); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + // Restart from the beginning with the updated state + ctx.continueAsNew(); + } + } +} ``` {{% /codetab %}} From 863cabc73f3e9cd72df889d54322f53a1d942e43 Mon Sep 17 00:00:00 2001 From: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> Date: Thu, 5 Oct 2023 18:53:54 +0530 Subject: [PATCH 087/162] add docs for template processing in k8s name resolution Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> --- .../nr-kubernetes.md | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-name-resolution/nr-kubernetes.md b/daprdocs/content/en/reference/components-reference/supported-name-resolution/nr-kubernetes.md index 15f22643ff2..d8de965bea4 100644 --- a/daprdocs/content/en/reference/components-reference/supported-name-resolution/nr-kubernetes.md +++ b/daprdocs/content/en/reference/components-reference/supported-name-resolution/nr-kubernetes.md @@ -7,7 +7,22 @@ description: Detailed information on the Kubernetes DNS name resolution componen ## Configuration format -Kubernetes DNS name resolution is configured automatically in [Kubernetes mode]({{< ref kubernetes >}}) by Dapr. There is no configuration needed to use Kubernetes DNS as your name resolution provider. +Generally, Kubernetes DNS name resolution is configured automatically in [Kubernetes mode]({{< ref kubernetes >}}) by Dapr. There is no configuration needed to use Kubernetes DNS as your name resolution provider unless there are some overrides necessary for the Kubernetes name resolution component. + +In the scenario that an override is required, within a [Dapr Configuration]({{< ref configuration-overview.md >}}) CRD, add a `nameResolution` spec and set the `component` field set to `"kubernetes"`. Then other configuration fields can be set as needed in a `configuration` map as seen below. + +```yaml +apiVersion: dapr.io/v1alpha1 +kind: Configuration +metadata: + name: appconfig +spec: + nameResolution: + component: "kubernetes" + configuration: + "clusterDomain": "cluster.local" # Mutually exclusive with the template field + "template": "{{.ID}}-{{.Data.region}}.internal:{{.Port}}" # Mutually exclusive with the clusterDomain field +``` ## Behaviour @@ -15,7 +30,13 @@ The component resolves target apps by using the Kubernetes cluster's DNS provide ## Spec configuration fields -Not applicable, as Kubernetes DNS is configured by Dapr when running in Kubernetes mode. +The configuration spec is fixed to v1.3.0 of the Consul API + +| Field | Required | Type | Details | Examples | +|--------------|:--------:|-----:|:---------|----------| +| clusterDomain | N | `string` | The cluster domain to be used for resolved addresses. This field is mutually exclusive with the `template` file.| `cluster.local` +| template | N | `string` | A template string to be parsed when addresses are resolved using [text/template](https://pkg.go.dev/text/template#Template) . The template will be populated by the fields in the [ResolveRequest](https://github.com/dapr/components-contrib/blob/v1.12.0-rc.3/nameresolution/requests.go#L20) struct. This field is mutually exclusive with `clusterDomain` field. | `{{.ID}}-{{.Data.region}}.{{.Namespace}}.internal:{{.Port}}` + ## Related links From 54434a610c97200482110d809e03febe4e513282 Mon Sep 17 00:00:00 2001 From: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> Date: Thu, 5 Oct 2023 21:29:47 +0530 Subject: [PATCH 088/162] address review comments. Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> --- .../supported-name-resolution/nr-kubernetes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-name-resolution/nr-kubernetes.md b/daprdocs/content/en/reference/components-reference/supported-name-resolution/nr-kubernetes.md index d8de965bea4..4a946ffd4ae 100644 --- a/daprdocs/content/en/reference/components-reference/supported-name-resolution/nr-kubernetes.md +++ b/daprdocs/content/en/reference/components-reference/supported-name-resolution/nr-kubernetes.md @@ -20,8 +20,8 @@ spec: nameResolution: component: "kubernetes" configuration: - "clusterDomain": "cluster.local" # Mutually exclusive with the template field - "template": "{{.ID}}-{{.Data.region}}.internal:{{.Port}}" # Mutually exclusive with the clusterDomain field + clusterDomain: "cluster.local" # Mutually exclusive with the template field + template: "{{.ID}}-{{.Data.region}}.internal:{{.Port}}" # Mutually exclusive with the clusterDomain field ``` ## Behaviour From 2cbca93b78f53495618746fe044bbc4fb11d1fba Mon Sep 17 00:00:00 2001 From: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> Date: Thu, 5 Oct 2023 21:59:20 +0530 Subject: [PATCH 089/162] address review comments. Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> --- .../supported-name-resolution/nr-kubernetes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-name-resolution/nr-kubernetes.md b/daprdocs/content/en/reference/components-reference/supported-name-resolution/nr-kubernetes.md index 4a946ffd4ae..89e30859ddf 100644 --- a/daprdocs/content/en/reference/components-reference/supported-name-resolution/nr-kubernetes.md +++ b/daprdocs/content/en/reference/components-reference/supported-name-resolution/nr-kubernetes.md @@ -35,7 +35,7 @@ The configuration spec is fixed to v1.3.0 of the Consul API | Field | Required | Type | Details | Examples | |--------------|:--------:|-----:|:---------|----------| | clusterDomain | N | `string` | The cluster domain to be used for resolved addresses. This field is mutually exclusive with the `template` file.| `cluster.local` -| template | N | `string` | A template string to be parsed when addresses are resolved using [text/template](https://pkg.go.dev/text/template#Template) . The template will be populated by the fields in the [ResolveRequest](https://github.com/dapr/components-contrib/blob/v1.12.0-rc.3/nameresolution/requests.go#L20) struct. This field is mutually exclusive with `clusterDomain` field. | `{{.ID}}-{{.Data.region}}.{{.Namespace}}.internal:{{.Port}}` +| template | N | `string` | A template string to be parsed when addresses are resolved using [text/template](https://pkg.go.dev/text/template#Template) . The template will be populated by the fields in the [ResolveRequest](https://github.com/dapr/components-contrib/blob/release-{{% dapr-latest-version short="true" %}}/nameresolution/requests.go#L20) struct. This field is mutually exclusive with `clusterDomain` field. | `{{.ID}}-{{.Data.region}}.{{.Namespace}}.internal:{{.Port}}` ## Related links From 38a9f3c716cbd9ef2c73962dac625361cb2ec5b6 Mon Sep 17 00:00:00 2001 From: Oliver Tomlinson Date: Thu, 5 Oct 2023 21:37:46 +0100 Subject: [PATCH 090/162] First draft of debugging docker compose instructions Signed-off-by: Author Name --- .../developing-applications/debugging/debugging-docker-compose.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md diff --git a/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md b/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md new file mode 100644 index 00000000000..e69de29bb2d From 5238808e86b515799942313d30099f45d1e9b348 Mon Sep 17 00:00:00 2001 From: Oliver Tomlinson Date: Thu, 5 Oct 2023 22:19:35 +0100 Subject: [PATCH 091/162] actually push the content --- .../debugging/debugging-docker-compose.md | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md b/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md index e69de29bb2d..8d5c74fb484 100644 --- a/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md +++ b/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md @@ -0,0 +1,79 @@ +--- +type: docs +title: "Debugging dapr Apps running in Docker Compose" +linkTitle: "Debugging Docker Compose" +weight: 300 +description: "Debug Dapr apps locally which are part of a docker compose deployment" +--- + +The goal of this article is to demonstrate a way to debug one or more daprised applications (via your IDE, locally) while the remaining integrated with the other applications that have deployed in the docker compose environment. + +Let's take the minimal example of a docker compose file which contains just two services : +- `nodeapp` - your app +- `nodeapp-dapr` - the dapr sidecar process to your `nodeapp` service + +#### compose.yml +```yaml +services: + nodeapp: + build: ./node + ports: + - "50001:50001" + networks: + - hello-dapr + nodeapp-dapr: + image: "daprio/daprd:edge" + command: [ + "./daprd", + "--app-id", "nodeapp", + "--app-port", "3000", + "--resources-path", "./components" + ] + volumes: + - "./components/:/components" + depends_on: + - nodeapp + network_mode: "service:nodeapp" +networks: + hello-dapr +``` + +When you run this docker file with `docker compose -f compose.yml up` this will deploy to docker and run as normal. + +But how do we debug the `nodeapp` while still integrated to the running dapr sidecar process, and anything else that you may have deployed via the docker compose file? + +Lets start by introducing a *second* docker compose file called `compose.debug.yml`. This second compose file will augment with the first compose file when the `up` command is ran. + +#### compose.debug.yml +```yaml +services: + nodeapp: # Isolate the nodeapp by removing its ports and taking it off the network + ports: !reset [] + networks: !reset + - "" + nodeapp-dapr: + command: ["./daprd", + "--app-id", "nodeapp", + "--app-port", "8080", # This must match the port that your app is exposed on when debugging in the IDE + "--resources-path", "./components", + "--app-channel-address", "host.docker.internal"] # Make the sidecar look on the host for the App Channel + network_mode: !reset "" # Reset the network_mode... + networks: # ... so that the sidecar can go into the normal network + - hello-dapr + ports: + - "3500:3500" # Expose the HTTP port to the host + - "50001:50001" # Expose the GRPC port to the host (Dapr Worfklows depends upon the GRPC channel) + +``` + +Next, ensure that your `nodeapp` is running/debugging in your IDE of choice, and is exposed on the same port that you specifed above in the `compose.debug.yml` - In the example above this is set to port `8080`. + +Next, stop any existing compose sessions you may have started, and run the following command to run both docker compose files combined together : + +`docker compose -f compose.yml -f compose.debug.yml up` + +You should now find that the dapr sidecar and your debugging app will have bi-directional communication with each other as if they were running together as normal in the docker compose environment. + +**Note** : It's important to highlight that the `nodeapp` service in the docker compose environment is actually still running, however it has been removed from the docker network so it is effectively orphaned as nothing can communicate to it. + + From e58ae6a570a7c24ea25ac65393a211ffb2d34239 Mon Sep 17 00:00:00 2001 From: Oliver Tomlinson Date: Thu, 5 Oct 2023 22:21:36 +0100 Subject: [PATCH 092/162] typo --- .../debugging/debugging-docker-compose.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md b/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md index 8d5c74fb484..4266b40f7dd 100644 --- a/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md +++ b/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md @@ -6,7 +6,7 @@ weight: 300 description: "Debug Dapr apps locally which are part of a docker compose deployment" --- -The goal of this article is to demonstrate a way to debug one or more daprised applications (via your IDE, locally) while the remaining integrated with the other applications that have deployed in the docker compose environment. +The goal of this article is to demonstrate a way to debug one or more daprised applications (via your IDE, locally) while remaining integrated with the other applications that have deployed in the docker compose environment. Let's take the minimal example of a docker compose file which contains just two services : - `nodeapp` - your app From 025cdbd987f7428724c6b65d59bc6c1195a0cda4 Mon Sep 17 00:00:00 2001 From: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Date: Thu, 5 Oct 2023 19:18:04 -0400 Subject: [PATCH 093/162] move http streaming out of preview (#3790) Signed-off-by: Hannah Hunter --- .../service-invocation-overview.md | 15 +++++++++++ .../support/support-preview-features.md | 26 ------------------- 2 files changed, 15 insertions(+), 26 deletions(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/service-invocation/service-invocation-overview.md b/daprdocs/content/en/developing-applications/building-blocks/service-invocation/service-invocation-overview.md index edd542ef985..42d6b304d2f 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/service-invocation/service-invocation-overview.md +++ b/daprdocs/content/en/developing-applications/building-blocks/service-invocation/service-invocation-overview.md @@ -94,6 +94,21 @@ The diagram below shows an example of how this works. If you have 1 instance of Dapr can run on a variety of [hosting platforms]({{< ref hosting >}}). To enable service discovery and service invocation, Dapr uses pluggable [name resolution components]({{< ref supported-name-resolution >}}). For example, the Kubernetes name resolution component uses the Kubernetes DNS service to resolve the location of other applications running in the cluster. Self-hosted machines can use the mDNS name resolution component. The Consul name resolution component can be used in any hosting environment, including Kubernetes or self-hosted. +### Streaming for HTTP service invocation + +You can handle data as a stream in HTTP service invocation. This can offer improvements in performance and memory utilization when using Dapr to invoke another service using HTTP with large request or response bodies. + +The diagram below demonstrates the six steps of data flow. + +Diagram showing the steps of service invocation described in the table below + +1. Request: "App A" to "Dapr sidecar A" +1. Request: "Dapr sidecar A" to "Dapr sidecar B" +1. Request: "Dapr sidecar B" to "App B" +1. Response: "App B" to "Dapr sidecar B" +1. Response: "Dapr sidecar B" to "Dapr sidecar A" +1. Response: "Dapr sidecar A" to "App A" + ## Example Architecture Following the above call sequence, suppose you have the applications as described in the [Hello World tutorial](https://github.com/dapr/quickstarts/blob/master/tutorials/hello-world/README.md), where a python app invokes a node.js app. In such a scenario, the python app would be "Service A" , and a Node.js app would be "Service B". diff --git a/daprdocs/content/en/operations/support/support-preview-features.md b/daprdocs/content/en/operations/support/support-preview-features.md index 4d19ea9a194..e290337382f 100644 --- a/daprdocs/content/en/operations/support/support-preview-features.md +++ b/daprdocs/content/en/operations/support/support-preview-features.md @@ -15,7 +15,6 @@ For CLI there is no explicit opt-in, just the version that this was first made a | Feature | Description | Setting | Documentation | Version introduced | | --- | --- | --- | --- | --- | -| **Streaming for HTTP service invocation** | Enables (partial) support for using streams in HTTP service invocation; see below for more details. | `ServiceInvocationStreaming` | [Details]({{< ref "support-preview-features.md#streaming-for-http-service-invocation" >}}) | v1.10 | | **Pluggable components** | Allows creating self-hosted gRPC-based components written in any language that supports gRPC. The following component APIs are supported: State stores, Pub/sub, Bindings | N/A | [Pluggable components concept]({{}})| v1.9 | | **Multi-App Run for Kubernetes** | Configure multiple Dapr applications from a single configuration file and run from a single command on Kubernetes | `dapr run -k -f` | [Multi-App Run]({{< ref multi-app-dapr-run.md >}}) | v1.10 | | **Workflows** | Author workflows as code to automate and orchestrate tasks within your application, like messaging, state management, and failure handling | N/A | [Workflows concept]({{< ref "components-concept#workflows" >}})| v1.10 | @@ -24,28 +23,3 @@ For CLI there is no explicit opt-in, just the version that this was first made a | **Actor State TTL** | Allow actors to save records to state stores with Time To Live (TTL) set to automatically clean up old data. In its current implementation, actor state with TTL may not be reflected correctly by clients, read [Actor State Transactions]({{< ref actors_api.md >}}) for more information. | `ActorStateTTL` | [Actor State Transactions]({{< ref actors_api.md >}}) | v1.11 | | **Transactional Outbox** | Allows state operations for inserts and updates to be published to a configured pub/sub topic using a single transaction across the state store and the pub/sub | N/A | [Transactional Outbox Feature]({{< ref howto-outbox.md >}}) | v1.12 | -### Streaming for HTTP service invocation - -Running Dapr with the `ServiceInvocationStreaming` feature flag enables partial support for handling data as a stream in HTTP service invocation. This can offer improvements in performance and memory utilization when using Dapr to invoke another service using HTTP with large request or response bodies. - -The table below summarizes the current state of support for streaming in HTTP service invocation in Dapr, including the impact of enabling `ServiceInvocationStreaming`, in the example where "app A" is invoking "app B" using Dapr. There are six steps in the data flow, with various levels of support for handling data as a stream: - -Diagram showing the steps of service invocation described in the table below - -| Step | Handles data as a stream | Dapr 1.11 | Dapr 1.11 with
`ServiceInvocationStreaming` | -|:---:|---|:---:|:---:| -| 1 | Request: "App A" to "Dapr sidecar A | | | -| 2 | Request: "Dapr sidecar A" to "Dapr sidecar B | | | -| 3 | Request: "Dapr sidecar B" to "App B" | | | -| 4 | Response: "App B" to "Dapr sidecar B" | | | -| 5 | Response: "Dapr sidecar B" to "Dapr sidecar A | | | -| 6 | Response: "Dapr sidecar A" to "App A | | | - -Important notes: - -- `ServiceInvocationStreaming` needs to be applied on caller sidecars only. - In the example above, streams are used for HTTP service invocation if `ServiceInvocationStreaming` is applied to the configuration of "app A" and its Dapr sidecar, regardless of whether the feature flag is enabled for "app B" and its sidecar. -- When `ServiceInvocationStreaming` is enabled, you should make sure that all services your app invokes using Dapr ("app B") are updated to Dapr 1.10 or higher, even if `ServiceInvocationStreaming` is not enabled for those sidecars. - Invoking an app using Dapr 1.9 or older is still possible, but those calls may fail unless you have applied a Dapr Resiliency policy with retries enabled. - -> Full support for streaming for HTTP service invocation will be completed in a future Dapr version. From eb1b86a4d1de99d3ad4de3a9784701a3ea88ef2e Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Thu, 5 Oct 2023 20:14:05 -0400 Subject: [PATCH 094/162] add java again Signed-off-by: Hannah Hunter --- sdkdocs/java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdkdocs/java b/sdkdocs/java index 96c04185212..80ed5152067 160000 --- a/sdkdocs/java +++ b/sdkdocs/java @@ -1 +1 @@ -Subproject commit 96c04185212d29c6c9d6cfa2b9e18062240f5311 +Subproject commit 80ed51520676c15d884732382b69e826895a5513 From ac04a0f663356e40db6aab3cbb3c7e2d6f262fdb Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Thu, 5 Oct 2023 20:42:35 -0400 Subject: [PATCH 095/162] fix link Signed-off-by: Hannah Hunter --- .../en/getting-started/quickstarts/workflow-quickstart.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md index 2ac77f77146..0a1f2e77900 100644 --- a/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md @@ -10,7 +10,7 @@ description: Get started with the Dapr Workflow building block Dapr Workflow is currently in beta. [See known limitations for {{% dapr-latest-version cli="true" %}}]({{< ref "workflow-overview.md#limitations" >}}). {{% /alert %}} -Let's take a look at the Dapr [Workflow building block]({{< ref workflow >}}). In this Quickstart, you'll create a simple console application to demonstrate Dapr's workflow programming model and the workflow management APIs. +Let's take a look at the Dapr [Workflow building block]({{< ref workflow-overview.md >}}). In this Quickstart, you'll create a simple console application to demonstrate Dapr's workflow programming model and the workflow management APIs. In this guide, you'll: From 37bb85ab1751970fa6ba75deef5b2d9d8c1edb2b Mon Sep 17 00:00:00 2001 From: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Date: Thu, 5 Oct 2023 20:47:56 -0400 Subject: [PATCH 096/162] fix link (#3791) Signed-off-by: Hannah Hunter --- .../en/getting-started/quickstarts/workflow-quickstart.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md index 2ac77f77146..0a1f2e77900 100644 --- a/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/workflow-quickstart.md @@ -10,7 +10,7 @@ description: Get started with the Dapr Workflow building block Dapr Workflow is currently in beta. [See known limitations for {{% dapr-latest-version cli="true" %}}]({{< ref "workflow-overview.md#limitations" >}}). {{% /alert %}} -Let's take a look at the Dapr [Workflow building block]({{< ref workflow >}}). In this Quickstart, you'll create a simple console application to demonstrate Dapr's workflow programming model and the workflow management APIs. +Let's take a look at the Dapr [Workflow building block]({{< ref workflow-overview.md >}}). In this Quickstart, you'll create a simple console application to demonstrate Dapr's workflow programming model and the workflow management APIs. In this guide, you'll: From 67aab22a24d123cc07b19e471c4a7c8dbb644c54 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Fri, 6 Oct 2023 13:20:26 -0400 Subject: [PATCH 097/162] update python; fix link Signed-off-by: Hannah Hunter --- sdkdocs/python | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdkdocs/python b/sdkdocs/python index 010a5b34165..6171b67db60 160000 --- a/sdkdocs/python +++ b/sdkdocs/python @@ -1 +1 @@ -Subproject commit 010a5b34165b7e8f688b4ad5a8b097c21efacb61 +Subproject commit 6171b67db60d51704ed8425ae71dda9226bf1255 From 9f9deb7349ce99fd107e478c25a6c8e107d82808 Mon Sep 17 00:00:00 2001 From: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Date: Mon, 9 Oct 2023 01:48:20 -0400 Subject: [PATCH 098/162] update dotnet to DaprWorkflowClient (#3789) Signed-off-by: Hannah Hunter Co-authored-by: Mark Fussell --- .../building-blocks/workflow/howto-author-workflow.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md index ada6335ec44..98f0df760e9 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/howto-author-workflow.md @@ -399,7 +399,7 @@ builder.Services.AddDaprWorkflow(options => WebApplication app = builder.Build(); // POST starts new order workflow instance -app.MapPost("/orders", async (WorkflowEngineClient client, [FromBody] OrderPayload orderInfo) => +app.MapPost("/orders", async (DaprWorkflowClient client, [FromBody] OrderPayload orderInfo) => { if (orderInfo?.Name == null) { @@ -414,7 +414,7 @@ app.MapPost("/orders", async (WorkflowEngineClient client, [FromBody] OrderPaylo }); // GET fetches state for order workflow to report status -app.MapGet("/orders/{orderId}", async (string orderId, WorkflowEngineClient client) => +app.MapGet("/orders/{orderId}", async (string orderId, DaprWorkflowClient client) => { WorkflowState state = await client.GetWorkflowStateAsync(orderId, true); if (!state.Exists) From b9bb5bd23164cc43040ca58057e95e26e41f1a67 Mon Sep 17 00:00:00 2001 From: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> Date: Mon, 9 Oct 2023 14:27:42 +0530 Subject: [PATCH 099/162] address review comments. Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> --- .../supported-name-resolution/nr-kubernetes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-name-resolution/nr-kubernetes.md b/daprdocs/content/en/reference/components-reference/supported-name-resolution/nr-kubernetes.md index 89e30859ddf..0f36aaacf2a 100644 --- a/daprdocs/content/en/reference/components-reference/supported-name-resolution/nr-kubernetes.md +++ b/daprdocs/content/en/reference/components-reference/supported-name-resolution/nr-kubernetes.md @@ -7,9 +7,9 @@ description: Detailed information on the Kubernetes DNS name resolution componen ## Configuration format -Generally, Kubernetes DNS name resolution is configured automatically in [Kubernetes mode]({{< ref kubernetes >}}) by Dapr. There is no configuration needed to use Kubernetes DNS as your name resolution provider unless there are some overrides necessary for the Kubernetes name resolution component. +Generally, Kubernetes DNS name resolution is configured automatically in [Kubernetes mode]({{< ref kubernetes >}}) by Dapr. There is no configuration needed to use Kubernetes DNS as your name resolution provider unless some overrides are necessary for the Kubernetes name resolution component. -In the scenario that an override is required, within a [Dapr Configuration]({{< ref configuration-overview.md >}}) CRD, add a `nameResolution` spec and set the `component` field set to `"kubernetes"`. Then other configuration fields can be set as needed in a `configuration` map as seen below. +In the scenario that an override is required, within a [Dapr Configuration]({{< ref configuration-overview.md >}}) CRD, add a `nameResolution` spec and set the `component` field to `"kubernetes"`. The other configuration fields can be set as needed in a `configuration` map, as seen below. ```yaml apiVersion: dapr.io/v1alpha1 From 19c8721907b809070752831aca8ef8380f49debb Mon Sep 17 00:00:00 2001 From: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> Date: Mon, 9 Oct 2023 14:55:24 +0530 Subject: [PATCH 100/162] add asb metadata based on PR dapr/components-contrib#2203 Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> --- .../supported-bindings/servicebusqueues.md | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/servicebusqueues.md b/daprdocs/content/en/reference/components-reference/supported-bindings/servicebusqueues.md index 1f8ffe68b7b..cbff263db39 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/servicebusqueues.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/servicebusqueues.md @@ -56,7 +56,7 @@ spec: # value: "5" # - name: publishInitialRetryIntervalInMs # Optional # value: "500" - # - name: direction + # - name: direction # value: "input, output" ``` {{% alert title="Warning" color="warning" %}} @@ -129,6 +129,44 @@ This component supports **output binding** with the following operations: - `create`: publishes a message to the specified queue +## Message metadata + +Azure Service Bus messages extend the Dapr message format with additional contextual metadata. Some metadata fields are set by Azure Service Bus itself (read-only) and others can be set by the client when publishing a message through `Invoke` binding call with `create` operation. + +### Sending a message with metadata + +To set Azure Service Bus metadata when sending a message, set the query parameters on the HTTP request or the gRPC metadata as documented [here](https://docs.dapr.io/reference/api/pubsub_api/#metadata). + +- `metadata.MessageId` +- `metadata.CorrelationId` +- `metadata.SessionId` +- `metadata.Label` +- `metadata.ReplyTo` +- `metadata.PartitionKey` +- `metadata.To` +- `metadata.ContentType` +- `metadata.ScheduledEnqueueTimeUtc` +- `metadata.ReplyToSessionId` + +> **Note:** The `metadata.MessageId` property does not set the `id` property of the cloud event returned by Dapr and should be treated in isolation. + +> **Note:** The `metadata.ScheduledEnqueueTimeUtc` property supports the [RFC1123](https://www.rfc-editor.org/rfc/rfc1123) and [RFC3339](https://www.rfc-editor.org/rfc/rfc3339) timestamp formats. + +### Receiving a message with metadata + +When Dapr calls your application, it will attach Azure Service Bus message metadata to the request using either HTTP headers or gRPC metadata. +In addition to the [settable metadata listed above](#sending-a-message-with-metadata), you can also access the following read-only message metadata. + +- `metadata.DeliveryCount` +- `metadata.LockedUntilUtc` +- `metadata.LockToken` +- `metadata.EnqueuedTimeUtc` +- `metadata.SequenceNumber` + +To find out more details on the purpose of any of these metadata properties, please refer to [the official Azure Service Bus documentation](https://docs.microsoft.com/rest/api/servicebus/message-headers-and-properties#message-headers). + +> Note: that all times are populated by the server and are not adjusted for clock skews. + ## Specifying a TTL per message Time to live can be defined on a per-queue level (as illustrated above) or at the message level. The value defined at message level overwrites any value set at the queue level. From a64c7d8a1ba555a38834ff9bbfd2edbfa9d9d0a4 Mon Sep 17 00:00:00 2001 From: mikeee Date: Mon, 9 Oct 2023 10:46:09 +0100 Subject: [PATCH 101/162] fix: replace --app-ssl flag with --app-protocol Signed-off-by: mikeee --- .../building-blocks/pubsub/howto-publish-subscribe.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/pubsub/howto-publish-subscribe.md b/daprdocs/content/en/developing-applications/building-blocks/pubsub/howto-publish-subscribe.md index c3ceb433307..ad0bce8f934 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/pubsub/howto-publish-subscribe.md +++ b/daprdocs/content/en/developing-applications/building-blocks/pubsub/howto-publish-subscribe.md @@ -219,7 +219,7 @@ namespace CheckoutService.controller Navigate to the directory containing the above code, then run the following command to launch both a Dapr sidecar and the subscriber application: ```bash -dapr run --app-id checkout --app-port 6002 --dapr-http-port 3602 --dapr-grpc-port 60002 --app-ssl dotnet run +dapr run --app-id checkout --app-port 6002 --dapr-http-port 3602 --dapr-grpc-port 60002 --app-protocol https dotnet run ``` {{% /codetab %}} @@ -465,7 +465,7 @@ namespace EventService Navigate to the directory containing the above code, then run the following command to launch both a Dapr sidecar and the publisher application: ```bash -dapr run --app-id orderprocessing --app-port 6001 --dapr-http-port 3601 --dapr-grpc-port 60001 --app-ssl dotnet run +dapr run --app-id orderprocessing --app-port 6001 --dapr-http-port 3601 --dapr-grpc-port 60001 --app-protocol https dotnet run ``` {{% /codetab %}} From 1d91cd24783f25106719177a8735a4238662ce53 Mon Sep 17 00:00:00 2001 From: mikeee Date: Mon, 9 Oct 2023 12:18:57 +0100 Subject: [PATCH 102/162] fix dashboard helm release name conflict Signed-off-by: mikeee --- .../en/operations/hosting/kubernetes/kubernetes-deploy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/operations/hosting/kubernetes/kubernetes-deploy.md b/daprdocs/content/en/operations/hosting/kubernetes/kubernetes-deploy.md index bdc60e48928..d88ec29209d 100644 --- a/daprdocs/content/en/operations/hosting/kubernetes/kubernetes-deploy.md +++ b/daprdocs/content/en/operations/hosting/kubernetes/kubernetes-deploy.md @@ -201,7 +201,7 @@ helm repo add dapr https://dapr.github.io/helm-charts/ helm repo update kubectl create namespace dapr-system # Install the Dapr dashboard -helm install dapr dapr/dapr-dashboard --namespace dapr-system +helm install dapr-dashboard dapr/dapr-dashboard --namespace dapr-system ``` ### Verify installation From dafd3f379cfbe91150230b18d63b3777929b2370 Mon Sep 17 00:00:00 2001 From: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> Date: Mon, 9 Oct 2023 11:04:25 -0700 Subject: [PATCH 103/162] fix typo in multi-app run env var def (#3800) Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> --- .../multi-app-dapr-run/multi-app-overview.md | 18 +++++++++--------- .../multi-app-dapr-run/multi-app-template.md | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md index 50c3e8e32dc..4e48d8a09e4 100644 --- a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md +++ b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-overview.md @@ -11,11 +11,11 @@ description: Run multiple applications with one CLI command {{% /alert %}} Let's say you want to run several applications locally to test them together, similar to a production scenario. Multi-App Run allows you to start and stop a set of applications simultaneously, either: -- Locally/self-hosted with processes, or +- Locally/self-hosted with processes, or - By building container images and deploying to a Kubernetes cluster - You can use a local Kubernetes cluster (KiND) or one deploy to a Cloud (AKS, EKS, and GKE). -The Multi-App Run template file describes how to start multiple applications as if you had run many separate CLI `run` commands. By default, this template file is called `dapr.yaml`. +The Multi-App Run template file describes how to start multiple applications as if you had run many separate CLI `run` commands. By default, this template file is called `dapr.yaml`. {{< tabs Self-hosted Kubernetes>}} @@ -24,7 +24,7 @@ The Multi-App Run template file describes how to start multiple applications as ## Multi-App Run template file -When you execute `dapr run -f .`, it starts the multi-app template file (named `dapr.yaml`) present in the current directory to run all the applications. +When you execute `dapr run -f .`, it starts the multi-app template file (named `dapr.yaml`) present in the current directory to run all the applications. You can name template file with preferred name other than the default. For example `dapr run -f ./.yaml`. @@ -41,7 +41,7 @@ apps: - appID: emit-metrics appDirPath: ../apps/emit-metrics/ daprHTTPPort: 3511 - env: + env: DAPR_HOST_ADD: localhost command: ["go","run", "app.go"] ``` @@ -50,7 +50,7 @@ For a more in-depth example and explanation of the template properties, see [Mul ## Locations for resources and configuration files -You have options on where to place your applications' resources and configuration files when using Multi-App Run. +You have options on where to place your applications' resources and configuration files when using Multi-App Run. ### Point to one file location (with convention) @@ -96,11 +96,11 @@ Watch [this video for an overview on Multi-App Run](https://youtu.be/s1p9MNl4VGo ## Multi-App Run template file -When you execute `dapr run -k -f .` or `dapr run -k -f dapr.yaml`, the applications defined in the `dapr.yaml` Multi-App Run template file starts in Kubernetes default namespace. +When you execute `dapr run -k -f .` or `dapr run -k -f dapr.yaml`, the applications defined in the `dapr.yaml` Multi-App Run template file starts in Kubernetes default namespace. > **Note:** Currently, the Multi-App Run template can only start applications in the default Kubernetes namespace. -The necessary default service and deployment definitions for Kubernetes are generated within the `.dapr/deploy` folder for each app in the `dapr.yaml` template. +The necessary default service and deployment definitions for Kubernetes are generated within the `.dapr/deploy` folder for each app in the `dapr.yaml` template. If the `createService` field is set to `true` in the `dapr.yaml` template for an app, then the `service.yaml` file is generated in the `.dapr/deploy` folder of the app. @@ -132,8 +132,8 @@ apps: containerImage: ghcr.io/dapr/samples/hello-k8s-python:latest ``` -> **Note:** -> - If the `containerImage` field is not specified, `dapr run -k -f` produces an error. +> **Note:** +> - If the `containerImage` field is not specified, `dapr run -k -f` produces an error. > - The `createService` field defines a basic service in Kubernetes (ClusterIP or LoadBalancer) that targets the `--app-port` specified in the template. If `createService` isn't specified, the application is not accessible from outside the cluster. For a more in-depth example and explanation of the template properties, see [Multi-app template]({{< ref multi-app-template.md >}}). diff --git a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md index 19cedc31dec..350ef0f4219 100644 --- a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md +++ b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md @@ -156,7 +156,7 @@ apps: appPort: 3000 unixDomainSocket: "/tmp/test-socket" env: - - DEBUG: false + DEBUG: false command: ["./backend"] ``` @@ -193,7 +193,7 @@ apps: appPort: 3000 unixDomainSocket: "/tmp/test-socket" env: - - DEBUG: false + DEBUG: false ``` The following rules apply for all the paths present in the template file: From cb21f125af47a394d9067692fc08a1426d0910df Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Mon, 9 Oct 2023 14:16:13 -0400 Subject: [PATCH 104/162] java submodule Signed-off-by: Hannah Hunter --- sdkdocs/java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdkdocs/java b/sdkdocs/java index 80ed5152067..5e45aa86b81 160000 --- a/sdkdocs/java +++ b/sdkdocs/java @@ -1 +1 @@ -Subproject commit 80ed51520676c15d884732382b69e826895a5513 +Subproject commit 5e45aa86b81748bf1e6efdbf7f52c20645a12435 From 7f238edb5ffe2cf0024fd4172611db8d2c0e52ef Mon Sep 17 00:00:00 2001 From: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Date: Mon, 9 Oct 2023 18:17:09 -0400 Subject: [PATCH 105/162] [Azure Functions] Update links to the MSFT docs for Dapr extension (#3802) * fix link Signed-off-by: Hannah Hunter * update link for when Dapr Functions ext docs are merged Signed-off-by: Hannah Hunter * remove extra line Signed-off-by: Hannah Hunter --------- Signed-off-by: Hannah Hunter --- .../integrations/Azure/azure-functions.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/daprdocs/content/en/developing-applications/integrations/Azure/azure-functions.md b/daprdocs/content/en/developing-applications/integrations/Azure/azure-functions.md index 7fe9dd013ad..0de36d3eb4e 100644 --- a/daprdocs/content/en/developing-applications/integrations/Azure/azure-functions.md +++ b/daprdocs/content/en/developing-applications/integrations/Azure/azure-functions.md @@ -7,12 +7,13 @@ weight: 3000 --- {{% alert title="Note" color="primary" %}} -The Dapr Functions extension is currently in preview. +The Dapr extension for Azure Functions is currently in preview. {{% /alert %}} +Dapr integrates with the [Azure Functions runtime](https://learn.microsoft.com/azure/azure-functions/functions-overview) via an extension that lets a function seamlessly interact with Dapr. +- **Azure Functions** provides an event-driven programming model. +- **Dapr** provides cloud-native building blocks. -Dapr integrates with the [Azure Functions runtime](https://learn.microsoft.com/azure/azure-functions/functions-overview) via an extension that lets a function seamlessly interact with Dapr. Azure Functions provides an event-driven programming model and Dapr provides cloud-native building blocks. The extension combines the two for serverless and event-driven apps. +The extension combines the two for serverless and event-driven apps. -Try out the [Dapr Functions extension](https://github.com/dapr/azure-functions-extension) samples. - -{{< button text="Learn more about the Dapr Function extension in preview" link="https://cloudblogs.microsoft.com/opensource/2020/07/01/announcing-azure-functions-extension-for-dapr/" >}} +{{< button text="Try out the Dapr extension for Azure Functions" link="https://learn.microsoft.com/azure/azure-functions/functions-bindings-dapr" >}} From adf10bbb2fb7d2b39b9b498a668416c77c174873 Mon Sep 17 00:00:00 2001 From: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> Date: Tue, 10 Oct 2023 04:23:40 +0530 Subject: [PATCH 106/162] add docs for oidc extensions field in kafka Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> --- .../reference/components-reference/supported-bindings/kafka.md | 1 + .../components-reference/supported-pubsub/setup-apache-kafka.md | 1 + 2 files changed, 2 insertions(+) diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/kafka.md b/daprdocs/content/en/reference/components-reference/supported-bindings/kafka.md index 38afe3c503a..7029b546bd8 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/kafka.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/kafka.md @@ -74,6 +74,7 @@ spec: | `oidcScopes` | N | Input/Output | Comma-delimited list of OAuth2/OIDC scopes to request with the access token. Recommended when `authType` is set to `oidc`. Defaults to `"openid"` | `"openid,kafka-prod"` | | `version` | N | Input/Output | Kafka cluster version. Defaults to 2.0.0. Please note that this needs to be mandatorily set to `1.0.0` for EventHubs with Kafka. | `"1.0.0"` | | `direction` | N | Input/Output | The direction of the binding. | `"input"`, `"output"`, `"input, output"` | +| `oidcExtensions` | N | Input/Output | String containing a JSON-encoded dictionary of OAuth2/OIDC extensions to request with the access token | `{"cluster":"kafka","poolid":"kafkapool"}` | #### Note The metadata `version` must be set to `1.0.0` when using Azure EventHubs with Kafka. diff --git a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-apache-kafka.md b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-apache-kafka.md index 48e2876c2d1..431a7bc5406 100644 --- a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-apache-kafka.md +++ b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-apache-kafka.md @@ -80,6 +80,7 @@ spec: | oidcClientID | N | The OAuth2 client ID that has been provisioned in the identity provider. Required when `authType` is set to `oidc` | `dapr-kafka` | | oidcClientSecret | N | The OAuth2 client secret that has been provisioned in the identity provider: Required when `authType` is set to `oidc` | `"KeFg23!"` | | oidcScopes | N | Comma-delimited list of OAuth2/OIDC scopes to request with the access token. Recommended when `authType` is set to `oidc`. Defaults to `"openid"` | `"openid,kafka-prod"` | +| oidcExtensions | N | Input/Output | String containing a JSON-encoded dictionary of OAuth2/OIDC extensions to request with the access token | `{"cluster":"kafka","poolid":"kafkapool"}` | The `secretKeyRef` above is referencing a [kubernetes secrets store]({{< ref kubernetes-secret-store.md >}}) to access the tls information. Visit [here]({{< ref setup-secret-store.md >}}) to learn more about how to configure a secret store component. From d499542298acfbf61eba77b6e3129aab8b10dad0 Mon Sep 17 00:00:00 2001 From: Mike Date: Tue, 10 Oct 2023 05:01:51 +0100 Subject: [PATCH 107/162] feat: '/assign' can be used with go-sdk repo (#3798) * feat: '/assign' can be used with go-sdk repo The assign command can now be used in issues on the go-sdk repo from the merge of the referenced PR. Refs: dapr/go-sdk#460 Signed-off-by: mikeee * clarify applicable commands to each repository Signed-off-by: mikeee --------- Signed-off-by: mikeee Co-authored-by: Mark Fussell --- daprdocs/content/en/contributing/daprbot.md | 26 ++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/daprdocs/content/en/contributing/daprbot.md b/daprdocs/content/en/contributing/daprbot.md index 2be452643df..64a50a664a3 100644 --- a/daprdocs/content/en/contributing/daprbot.md +++ b/daprdocs/content/en/contributing/daprbot.md @@ -6,24 +6,24 @@ weight: 15 description: "List of Dapr bot capabilities." --- -Dapr bot is a GitHub script that helps with common tasks in the Dapr organization. It is set up individually for each repository ([example](https://github.com/dapr/dapr/blob/master/.github/workflows/dapr-bot.yml)) and can be configured to run on specific events. This reference covers the Dapr bot capabilities from the `dapr` and `components-contrib` repositories only. +Dapr bot is triggered by a list of commands that helps with common tasks in the Dapr organization. It is set up individually for each repository ([example](https://github.com/dapr/dapr/blob/master/.github/workflows/dapr-bot.yml)) and can be configured to run on specific events. Below is a list of commands and the list of repositories they are implemented on. ## Command reference -| Command | Target | Description | Who can use | Repository | -|---------|--------|-------------|-------------|------------| -| `/assign` | Issue | Assigns an issue to a user or group of users | Anyone | `dapr`, `components-contrib` | -| `/ok-to-test` | Pull request | `dapr`: trigger end to end tests
`components-contrib`: trigger conformance and certification tests | Users listed in the [bot](https://github.com/dapr/dapr/blob/master/.github/scripts/dapr_bot.js) | `dapr`, `components-contrib` | -| `/ok-to-perf` | Pull request | Trigger performance tests. | Users listed in the [bot](https://github.com/dapr/dapr/blob/master/.github/scripts/dapr_bot.js) | `dapr` | -| `/make-me-laugh` | Issue or pull request | Posts a random joke | Users listed in the [bot](https://github.com/dapr/dapr/blob/master/.github/scripts/dapr_bot.js) | `dapr`, `components-contrib` | +| Command | Target | Description | Who can use | Repository | +| ---------------- | --------------------- | -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | -------------------------------------- | +| `/assign` | Issue | Assigns an issue to a user or group of users | Anyone | `dapr`, `components-contrib`, `go-sdk` | +| `/ok-to-test` | Pull request | `dapr`: trigger end to end tests
`components-contrib`: trigger conformance and certification tests | Users listed in the [bot](https://github.com/dapr/dapr/blob/master/.github/scripts/dapr_bot.js) | `dapr`, `components-contrib` | +| `/ok-to-perf` | Pull request | Trigger performance tests. | Users listed in the [bot](https://github.com/dapr/dapr/blob/master/.github/scripts/dapr_bot.js) | `dapr` | +| `/make-me-laugh` | Issue or pull request | Posts a random joke | Users listed in the [bot](https://github.com/dapr/dapr/blob/master/.github/scripts/dapr_bot.js) | `dapr`, `components-contrib` | ## Label reference You can query issues created by the Dapr bot by using the `created-by/dapr-bot` label ([query](https://github.com/search?q=org%3Adapr%20is%3Aissue%20label%3Acreated-by%2Fdapr-bot%20&type=issues)). -| Label | Target | What does it do? | Repository | -|-------|--------|------------------|------------| -| `docs-needed` | Issue | Creates a new issue in `dapr/docs` to track doc work | `dapr` | -| `sdk-needed` | Issue | Creates new issues across the SDK repos to track SDK work | `dapr` | -| `documentation required` | Issue or pull request | Creates a new issue in `dapr/docs` to track doc work | `components-contrib` | -| `new component` | Issue or pull request | Creates a new issue in `dapr/dapr` to register the new component | `components-contrib` | +| Label | Target | What does it do? | Repository | +| ------------------------ | --------------------- | ---------------------------------------------------------------- | -------------------- | +| `docs-needed` | Issue | Creates a new issue in `dapr/docs` to track doc work | `dapr` | +| `sdk-needed` | Issue | Creates new issues across the SDK repos to track SDK work | `dapr` | +| `documentation required` | Issue or pull request | Creates a new issue in `dapr/docs` to track doc work | `components-contrib` | +| `new component` | Issue or pull request | Creates a new issue in `dapr/dapr` to register the new component | `components-contrib` | From 80661979e6bcc4e8ebbae9069186c44c58d51869 Mon Sep 17 00:00:00 2001 From: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> Date: Tue, 10 Oct 2023 20:11:07 +0530 Subject: [PATCH 108/162] address review comments. Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> --- .../supported-bindings/servicebusqueues.md | 15 +++++++++------ .../setup-azure-servicebus-queues.md | 17 ++++++++++------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/servicebusqueues.md b/daprdocs/content/en/reference/components-reference/supported-bindings/servicebusqueues.md index cbff263db39..73609c8f5ef 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/servicebusqueues.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/servicebusqueues.md @@ -135,7 +135,7 @@ Azure Service Bus messages extend the Dapr message format with additional contex ### Sending a message with metadata -To set Azure Service Bus metadata when sending a message, set the query parameters on the HTTP request or the gRPC metadata as documented [here](https://docs.dapr.io/reference/api/pubsub_api/#metadata). +To set Azure Service Bus metadata when sending a message, set the query parameters on the HTTP request or the gRPC metadata as documented [here]({{< ref "pubsub_api.md#metadata" >}}). - `metadata.MessageId` - `metadata.CorrelationId` @@ -148,13 +148,14 @@ To set Azure Service Bus metadata when sending a message, set the query paramete - `metadata.ScheduledEnqueueTimeUtc` - `metadata.ReplyToSessionId` -> **Note:** The `metadata.MessageId` property does not set the `id` property of the cloud event returned by Dapr and should be treated in isolation. - -> **Note:** The `metadata.ScheduledEnqueueTimeUtc` property supports the [RFC1123](https://www.rfc-editor.org/rfc/rfc1123) and [RFC3339](https://www.rfc-editor.org/rfc/rfc3339) timestamp formats. +{{% alert title="Note" color="primary" %}} +- The `metadata.MessageId` property does not set the `id` property of the cloud event returned by Dapr and should be treated in isolation. +- The `metadata.ScheduledEnqueueTimeUtc` property supports the [RFC1123](https://www.rfc-editor.org/rfc/rfc1123) and [RFC3339](https://www.rfc-editor.org/rfc/rfc3339) timestamp formats. +{{% /alert %}} ### Receiving a message with metadata -When Dapr calls your application, it will attach Azure Service Bus message metadata to the request using either HTTP headers or gRPC metadata. +When Dapr calls your application, it attaches Azure Service Bus message metadata to the request using either HTTP headers or gRPC metadata. In addition to the [settable metadata listed above](#sending-a-message-with-metadata), you can also access the following read-only message metadata. - `metadata.DeliveryCount` @@ -165,7 +166,9 @@ In addition to the [settable metadata listed above](#sending-a-message-with-meta To find out more details on the purpose of any of these metadata properties, please refer to [the official Azure Service Bus documentation](https://docs.microsoft.com/rest/api/servicebus/message-headers-and-properties#message-headers). -> Note: that all times are populated by the server and are not adjusted for clock skews. +{{% alert title="Note" color="primary" %}} +All times are populated by the server and are not adjusted for clock skews. +{{% /alert %}} ## Specifying a TTL per message diff --git a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-azure-servicebus-queues.md b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-azure-servicebus-queues.md index 8ff7dbd5615..e98df4814f3 100644 --- a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-azure-servicebus-queues.md +++ b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-azure-servicebus-queues.md @@ -11,7 +11,7 @@ aliases: To set up Azure Service Bus Queues pub/sub, create a component of type `pubsub.azure.servicebus.queues`. See the [pub/sub broker component file]({{< ref setup-pubsub.md >}}) to learn how ConsumerID is automatically generated. Read the [How-to: Publish and Subscribe guide]({{< ref "howto-publish-subscribe.md#step-1-setup-the-pubsub-component" >}}) on how to create and apply a pub/sub configuration. -> This component uses queues on Azure Service Bus; see the official documentation for the differences between [topics and queues](https://learn.microsoft.com/azure/service-bus-messaging/service-bus-queues-topics-subscriptions). +> This component uses queues on Azure Service Bus; see the official documentation for the differences between [topics and queues](https://learn.microsoft.com/azure/service-bus-messaging/service-bus-queues-topics-subscriptions). > For using topics, see the [Azure Service Bus Topics pubsub component]({{< ref "setup-azure-servicebus-topics" >}}). ### Connection String Authentication @@ -122,7 +122,7 @@ Azure Service Bus messages extend the Dapr message format with additional contex ### Sending a message with metadata -To set Azure Service Bus metadata when sending a message, set the query parameters on the HTTP request or the gRPC metadata as documented [here](https://docs.dapr.io/reference/api/pubsub_api/#metadata). +To set Azure Service Bus metadata when sending a message, set the query parameters on the HTTP request or the gRPC metadata as documented [here]({{< ref "pubsub_api.md#metadata" >}}). - `metadata.MessageId` - `metadata.CorrelationId` @@ -135,13 +135,14 @@ To set Azure Service Bus metadata when sending a message, set the query paramete - `metadata.ScheduledEnqueueTimeUtc` - `metadata.ReplyToSessionId` -> **Note:** The `metadata.MessageId` property does not set the `id` property of the cloud event returned by Dapr and should be treated in isolation. - -> **Note:** The `metadata.ScheduledEnqueueTimeUtc` property supports the [RFC1123](https://www.rfc-editor.org/rfc/rfc1123) and [RFC3339](https://www.rfc-editor.org/rfc/rfc3339) timestamp formats. +{{% alert title="Note" color="primary" %}} +- The `metadata.MessageId` property does not set the `id` property of the cloud event returned by Dapr and should be treated in isolation. +- The `metadata.ScheduledEnqueueTimeUtc` property supports the [RFC1123](https://www.rfc-editor.org/rfc/rfc1123) and [RFC3339](https://www.rfc-editor.org/rfc/rfc3339) timestamp formats. +{{% /alert %}} ### Receiving a message with metadata -When Dapr calls your application, it will attach Azure Service Bus message metadata to the request using either HTTP headers or gRPC metadata. +When Dapr calls your application, it attaches Azure Service Bus message metadata to the request using either HTTP headers or gRPC metadata. In addition to the [settable metadata listed above](#sending-a-message-with-metadata), you can also access the following read-only message metadata. - `metadata.DeliveryCount` @@ -152,7 +153,9 @@ In addition to the [settable metadata listed above](#sending-a-message-with-meta To find out more details on the purpose of any of these metadata properties, please refer to [the official Azure Service Bus documentation](https://docs.microsoft.com/rest/api/servicebus/message-headers-and-properties#message-headers). -> Note: that all times are populated by the server and are not adjusted for clock skews. +{{% alert title="Note" color="primary" %}} +All times are populated by the server and are not adjusted for clock skews. +{{% /alert %}} ## Sending and receiving multiple messages From ea00e24dbccc95b9e9062b7c6dcc124017dde853 Mon Sep 17 00:00:00 2001 From: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> Date: Tue, 10 Oct 2023 08:25:44 -0700 Subject: [PATCH 109/162] add docs for k8s kubeconfig, defaultNamespace (#3803) * add docs for k8s kubeconfig, defaultNamespace Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> * address review comments. Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> --------- Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> Co-authored-by: Mark Fussell --- .../supported-bindings/kubernetes-binding.md | 1 + .../supported-cryptography/kubernetes-secrets.md | 6 +++++- .../supported-secret-stores/kubernetes-secret-store.md | 7 ++++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/kubernetes-binding.md b/daprdocs/content/en/reference/components-reference/supported-bindings/kubernetes-binding.md index 25391a7748d..ee6389e0009 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/kubernetes-binding.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/kubernetes-binding.md @@ -36,6 +36,7 @@ spec: | `namespace` | Y | Input | The Kubernetes namespace to read events from | `"default"` | | `resyncPeriodInSec` | N | Input | The period of time to refresh event list from Kubernetes API server. Defaults to `"10"` | `"15"` | `direction` | N | Input | The direction of the binding | `"input"` +| `kubeconfigPath` | N | Input | The path to the kubeconfig file. If not specified, the binding uses the default in-cluster config value | `"/path/to/kubeconfig"` ## Binding support diff --git a/daprdocs/content/en/reference/components-reference/supported-cryptography/kubernetes-secrets.md b/daprdocs/content/en/reference/components-reference/supported-cryptography/kubernetes-secrets.md index 2e0ebc6f742..0471bd2c143 100644 --- a/daprdocs/content/en/reference/components-reference/supported-cryptography/kubernetes-secrets.md +++ b/daprdocs/content/en/reference/components-reference/supported-cryptography/kubernetes-secrets.md @@ -33,7 +33,11 @@ The above example uses secrets as plain strings. It is recommended to use a secr ## Spec metadata fields -For the Kubernetes secret store component, there are no metadata attributes. +| Field | Required | Details | Example | +|--------------------|:--------:|------------|-----|---------| +| `defaultNamespace` | N | Default namespace to retrieve secrets from. If unset, the namespace must be specified for each key, as `namespace/secretName/key` | `"default-ns"` | +| `kubeconfigPath` | N | The path to the kubeconfig file. If not specified, the component uses the default in-cluster config value | `"/path/to/kubeconfig"` + ## Related links [Cryptography building block]({{< ref cryptography >}}) \ No newline at end of file diff --git a/daprdocs/content/en/reference/components-reference/supported-secret-stores/kubernetes-secret-store.md b/daprdocs/content/en/reference/components-reference/supported-secret-stores/kubernetes-secret-store.md index b629503d827..a44a6de9a60 100644 --- a/daprdocs/content/en/reference/components-reference/supported-secret-stores/kubernetes-secret-store.md +++ b/daprdocs/content/en/reference/components-reference/supported-secret-stores/kubernetes-secret-store.md @@ -32,7 +32,12 @@ spec: ``` ## Spec metadata fields -For the Kubernetes secret store component, there are no metadata attributes. + +| Field | Required | Details | Example | +|--------------------|:--------:|------------|-----|---------| +| `defaultNamespace` | N | Default namespace to retrieve secrets from. If unset, the `namespace` must be specified in each request metadata or via environment variable `NAMESPACE` | `"default-ns"` | +| `kubeconfigPath` | N | The path to the kubeconfig file. If not specified, the store uses the default in-cluster config value | `"/path/to/kubeconfig"` + ## Optional per-request metadata properties From f572f8d74ad6e756a68c3b2789391170032fb286 Mon Sep 17 00:00:00 2001 From: Deepanshu Agarwal Date: Wed, 11 Oct 2023 20:44:56 +0530 Subject: [PATCH 110/162] Optimization Opportunities Signed-off-by: Deepanshu Agarwal --- .../building-blocks/pubsub/pubsub-bulk.md | 38 ++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md b/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md index b6b592a0612..9e44faa716d 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md +++ b/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md @@ -302,10 +302,10 @@ In the example above, `bulkSubscribe` is _optional_. If you use `bulkSubscribe`, - `enabled` is mandatory and enables or disables bulk subscriptions on this topic - You can optionally configure the max number of messages (`maxMessagesCount`) delivered in a bulk message. Default value of `maxMessagesCount` for components not supporting bulk subscribe is 100 i.e. for default bulk events between App and Dapr. Please refer [How components handle publishing and subscribing to bulk messages]({{< ref pubsub-bulk >}}). -If a component supports bulk subscribe, then default value for this parameter can be found in that component doc. Please refer [Supported components]({{< ref pubsub-bulk >}}). +If a component supports bulk subscribe, then default value for this parameter can be found in that component doc. - You can optionally provide the max duration to wait (`maxAwaitDurationMs`) before a bulk message is sent to the app. Default value of `maxAwaitDurationMs` for components not supporting bulk subscribe is 1000 i.e. for default bulk events between App and Dapr. Please refer [How components handle publishing and subscribing to bulk messages]({{< ref pubsub-bulk >}}). -If a component supports bulk subscribe, then default value for this parameter can be found in that component doc. Please refer [Supported components]({{< ref pubsub-bulk >}}). +If a component supports bulk subscribe, then default value for this parameter can be found in that component doc. The application receives an `EntryId` associated with each entry (individual message) in the bulk message. This `EntryId` must be used by the app to communicate the status of that particular entry. If the app fails to notify on an `EntryId` status, it's considered a `RETRY`. @@ -473,9 +473,37 @@ public class BulkMessageController : ControllerBase {{< /tabs >}} ## How components handle publishing and subscribing to bulk messages -Some pub/sub brokers support sending and receiving multiple messages in a single request. When a component supports bulk publish or subscribe operations, Dapr runtime uses them to further optimize the communication between the Dapr sidecar and the underlying pub/sub broker. - -For components that do not have bulk publish or subscribe support, Dapr runtime uses the regular publish and subscribe APIs to send and receive messages one by one. This is still more efficient than directly using the regular publish or subscribe APIs, because applications can still send/receive multiple messages in a single request to/from Dapr. +When talking about event publish/subscribe, there are two kind of network transfers involved. +1. From/To *App* To/From *Dapr*. +2. From/To *Dapr* To/From *Pubsub Broker*. + +And, these are the opportunities where optimization is possible for multiple Single calls v/s Bulk. Here, optimization means that Bulk calls are sent, which reduce number of overall calls and thus increased throughput ad better latency. + +On enabling Bulk Publish and/or Bulk Subscribe, Point 1 i.e. From/To *App* To/From *Dapr* is optimized for All components (depending on whether either one of Bulk Publish/ Bulk Subscribe OR Both are enabled). + +But, optimization of Point 2 i.e. From/To *Dapr* To/From *Pubsub Broker* would depend on a number of factors, i.e. if Broker inherently supports Bulk Publish/Subscribe and if Dapr component is updated to support Batching. Currently, following components are updated to support this level of optimization: +
+ + + + + + + + + + + + + + + + + + + + +
ComponentBulk PublishBulk Subscribe
KafkaYesYes
Azure ServicebusYesYes
Azure EventhubsYesYes
## Demos From 6b7214092a1202e3bd5f03b6643980f813951548 Mon Sep 17 00:00:00 2001 From: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> Date: Wed, 11 Oct 2023 09:17:49 -0700 Subject: [PATCH 111/162] Update daprdocs/content/en/reference/components-reference/supported-bindings/servicebusqueues.md Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> --- .../components-reference/supported-bindings/servicebusqueues.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/servicebusqueues.md b/daprdocs/content/en/reference/components-reference/supported-bindings/servicebusqueues.md index 73609c8f5ef..e2c74a4ba4b 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/servicebusqueues.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/servicebusqueues.md @@ -135,7 +135,7 @@ Azure Service Bus messages extend the Dapr message format with additional contex ### Sending a message with metadata -To set Azure Service Bus metadata when sending a message, set the query parameters on the HTTP request or the gRPC metadata as documented [here]({{< ref "pubsub_api.md#metadata" >}}). +To set Azure Service Bus metadata when sending a message, set the query parameters on the HTTP request or the gRPC metadata as documented [here]({{< ref "bindings_api.md" >}}). - `metadata.MessageId` - `metadata.CorrelationId` From f2a25fc315b336e9d69b62bdba8df72389d95721 Mon Sep 17 00:00:00 2001 From: Pravin Pushkar Date: Wed, 11 Oct 2023 23:26:40 +0530 Subject: [PATCH 112/162] Adding pluggable secret store doc (#3792) Signed-off-by: Pravin Pushkar Co-authored-by: Mark Fussell Co-authored-by: Yaron Schneider Co-authored-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> --- .../pluggable-components/develop-pluggable.md | 18 ++++++++++++------ .../pluggable-components-overview.md | 4 ---- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/daprdocs/content/en/developing-applications/develop-components/pluggable-components/develop-pluggable.md b/daprdocs/content/en/developing-applications/develop-components/pluggable-components/develop-pluggable.md index d25ff95d7a1..5868eabffd0 100644 --- a/daprdocs/content/en/developing-applications/develop-components/pluggable-components/develop-pluggable.md +++ b/daprdocs/content/en/developing-applications/develop-components/pluggable-components/develop-pluggable.md @@ -14,19 +14,21 @@ In order to implement a pluggable component, you need to implement a gRPC servic ### Find the proto definition file -Proto definitions are provided for each supported service interface (state store, pub/sub, bindings). +Proto definitions are provided for each supported service interface (state store, pub/sub, bindings, secret stores). Currently, the following component APIs are supported: - State stores - Pub/sub - Bindings +- Secret stores | Component | Type | gRPC definition | Built-in Reference Implementation | Docs | | :---------: | :--------: | :--------------: | :----------------------------------------------------------------------------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| State Store | `state` | [state.proto] | [Redis](https://github.com/dapr/components-contrib/tree/master/state/redis) | [concept]({{< ref "state-management-overview" >}}), [howto]({{< ref "howto-get-save-state" >}}), [api spec]({{< ref "state_api" >}}) | -| Pub/sub | `pubsub` | [pubsub.proto] | [Redis](https://github.com/dapr/components-contrib/tree/master/pubsub/redis) | [concept]({{< ref "pubsub-overview" >}}), [howto]({{< ref "howto-publish-subscribe" >}}), [api spec]({{< ref "pubsub_api" >}}) | -| Bindings | `bindings` | [bindings.proto] | [Kafka](https://github.com/dapr/components-contrib/tree/master/bindings/kafka) | [concept]({{< ref "bindings-overview" >}}), [input howto]({{< ref "howto-triggers" >}}), [output howto]({{< ref "howto-bindings" >}}), [api spec]({{< ref "bindings_api" >}}) | +| State Store | `state` | [state.proto](https://github.com/dapr/dapr/blob/master/dapr/proto/components/v1/state.proto) | [Redis](https://github.com/dapr/components-contrib/tree/master/state/redis) | [concept]({{< ref "state-management-overview" >}}), [howto]({{< ref "howto-get-save-state" >}}), [api spec]({{< ref "state_api" >}}) | +| Pub/sub | `pubsub` | [pubsub.proto](https://github.com/dapr/dapr/blob/master/dapr/proto/components/v1/pubsub.proto) | [Redis](https://github.com/dapr/components-contrib/tree/master/pubsub/redis) | [concept]({{< ref "pubsub-overview" >}}), [howto]({{< ref "howto-publish-subscribe" >}}), [api spec]({{< ref "pubsub_api" >}}) | +| Bindings | `bindings` | [bindings.proto](https://github.com/dapr/dapr/blob/master/dapr/proto/components/v1/bindings.proto) | [Kafka](https://github.com/dapr/components-contrib/tree/master/bindings/kafka) | [concept]({{< ref "bindings-overview" >}}), [input howto]({{< ref "howto-triggers" >}}), [output howto]({{< ref "howto-bindings" >}}), [api spec]({{< ref "bindings_api" >}}) | +| Secret Store | `secretstores` | [secretstore.proto](https://github.com/dapr/dapr/blob/master/dapr/proto/components/v1/secretstore.proto) | [Hashicorp/Vault](https://github.com/dapr/components-contrib/blob/master/secretstores/hashicorp/vault/vault.go) | [concept]({{< ref "secrets-overview" >}}), [howto-secrets]({{< ref "howto-secrets" >}}), [api spec]({{< ref "secrets_api" >}}) | Below is a snippet of the gRPC service definition for pluggable component state stores ([state.proto]): @@ -95,11 +97,15 @@ Provide a concrete implementation of the desired service. Each component has a g - **Pub/sub** - Pluggable pub/sub components only have a single core service interface defined ([pubsub.proto]). They have no optional service interfaces. + Pluggable pub/sub components only have a single core service interface defined [pubsub.proto](https://github.com/dapr/dapr/blob/master/dapr/proto/components/v1/pubsub.proto). They have no optional service interfaces. - **Bindings** - Pluggable input and output bindings have a single core service definition on [bindings.proto]. They have no optional service interfaces. + Pluggable input and output bindings have a single core service definition on [bindings.proto](https://github.com/dapr/dapr/blob/master/dapr/proto/components/v1/bindings.proto). They have no optional service interfaces. + +- **Secret Store** + + Pluggable Secret store have a single core service definition on [secretstore.proto](https://github.com/dapr/dapr/blob/master/dapr/proto/components/v1/secretstore.proto). They have no optional service interfaces. After generating the above state store example's service scaffolding code using gRPC and protocol buffers tools, you can define concrete implementations for the 9 methods defined under `service StateStore`, along with code to initialize and communicate with your dependencies. diff --git a/daprdocs/content/en/developing-applications/develop-components/pluggable-components/pluggable-components-overview.md b/daprdocs/content/en/developing-applications/develop-components/pluggable-components/pluggable-components-overview.md index 96b1260cc41..bdee6b540d6 100644 --- a/daprdocs/content/en/developing-applications/develop-components/pluggable-components/pluggable-components-overview.md +++ b/daprdocs/content/en/developing-applications/develop-components/pluggable-components/pluggable-components-overview.md @@ -63,7 +63,3 @@ In contrast, pluggable components require additional steps before they can commu - [Implement a pluggable component]({{< ref develop-pluggable.md >}}) - [Pluggable component registration]({{< ref "pluggable-components-registration" >}}) - -[state.proto]: https://github.com/dapr/dapr/blob/master/dapr/proto/components/v1/state.proto -[pubsub.proto]: https://github.com/dapr/dapr/blob/master/dapr/proto/components/v1/pubsub.proto -[bindings.proto]: https://github.com/dapr/dapr/blob/master/dapr/proto/components/v1/bindings.proto From dc93425a056ed794ba6a40087760a135efef7c9d Mon Sep 17 00:00:00 2001 From: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> Date: Wed, 11 Oct 2023 23:33:33 +0530 Subject: [PATCH 113/162] fix version typo for multi-app run k8s Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> --- .../content/en/operations/support/support-preview-features.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/operations/support/support-preview-features.md b/daprdocs/content/en/operations/support/support-preview-features.md index e290337382f..9c9fff69010 100644 --- a/daprdocs/content/en/operations/support/support-preview-features.md +++ b/daprdocs/content/en/operations/support/support-preview-features.md @@ -16,7 +16,7 @@ For CLI there is no explicit opt-in, just the version that this was first made a | Feature | Description | Setting | Documentation | Version introduced | | --- | --- | --- | --- | --- | | **Pluggable components** | Allows creating self-hosted gRPC-based components written in any language that supports gRPC. The following component APIs are supported: State stores, Pub/sub, Bindings | N/A | [Pluggable components concept]({{}})| v1.9 | -| **Multi-App Run for Kubernetes** | Configure multiple Dapr applications from a single configuration file and run from a single command on Kubernetes | `dapr run -k -f` | [Multi-App Run]({{< ref multi-app-dapr-run.md >}}) | v1.10 | +| **Multi-App Run for Kubernetes** | Configure multiple Dapr applications from a single configuration file and run from a single command on Kubernetes | `dapr run -k -f` | [Multi-App Run]({{< ref multi-app-dapr-run.md >}}) | v1.12 | | **Workflows** | Author workflows as code to automate and orchestrate tasks within your application, like messaging, state management, and failure handling | N/A | [Workflows concept]({{< ref "components-concept#workflows" >}})| v1.10 | | **Cryptography** | Encrypt or decrypt data without having to manage secrets keys | N/A | [Cryptography concept]({{< ref "components-concept#cryptography" >}})| v1.11 | | **Service invocation for non-Dapr endpoints** | Allow the invocation of non-Dapr endpoints by Dapr using the [Service invocation API]({{< ref service_invocation_api.md >}}). Read ["How-To: Invoke Non-Dapr Endpoints using HTTP"]({{< ref howto-invoke-non-dapr-endpoints.md >}}) for more information. | N/A | [Service invocation API]({{< ref service_invocation_api.md >}}) | v1.11 | From f9d5a1b89b5558d6bae81724e93aff1248da8e9b Mon Sep 17 00:00:00 2001 From: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> Date: Wed, 11 Oct 2023 12:32:16 -0700 Subject: [PATCH 114/162] Update docs for HTTP binding Fixes #3664 Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> --- .../supported-bindings/http.md | 150 +++++++++--------- 1 file changed, 77 insertions(+), 73 deletions(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/http.md b/daprdocs/content/en/reference/components-reference/supported-bindings/http.md index 39355955457..7f5f2972968 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/http.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/http.md @@ -9,7 +9,7 @@ aliases: ## Alternative -The [service invocation API]({{< ref service_invocation_api.md >}}) allows for the invocation of non-Dapr HTTP endpoints and is the recommended approach. Read ["How-To: Invoke Non-Dapr Endpoints using HTTP"]({{< ref howto-invoke-non-dapr-endpoints.md >}}) for more information. +The [service invocation API]({{< ref service_invocation_api.md >}}) allows invoking non-Dapr HTTP endpoints and is the recommended approach. Read ["How-To: Invoke Non-Dapr Endpoints using HTTP"]({{< ref howto-invoke-non-dapr-endpoints.md >}}) for more information. ## Setup Dapr component @@ -22,66 +22,70 @@ spec: type: bindings.http version: v1 metadata: - - name: url - value: "http://something.com" - - name: MTLSRootCA - value: "/Users/somepath/root.pem" # OPTIONAL Secret store ref, , or - - name: MTLSClientCert - value: "/Users/somepath/client.pem" # OPTIONAL Secret store ref, , or - - name: MTLSClientKey - value: "/Users/somepath/client.key" # OPTIONAL Secret store ref, , or - - name: MTLSRenegotiation - value: "RenegotiateOnceAsClient" # OPTIONAL one of: RenegotiateNever, RenegotiateOnceAsClient, RenegotiateFreelyAsClient - - name: securityToken # OPTIONAL - secretKeyRef: - name: mysecret - key: "mytoken" - - name: securityTokenHeader - value: "Authorization: Bearer" # OPTIONAL
- - name: direction - value: "output" + - name: url + value: "http://something.com" + #- name: maxResponseBodySize + # value: "100Mi" # OPTIONAL maximum amount of data to read from a response + #- name: MTLSRootCA + # value: "/Users/somepath/root.pem" # OPTIONAL path to root CA or PEM-encoded string + #- name: MTLSClientCert + # value: "/Users/somepath/client.pem" # OPTIONAL path to client cert or PEM-encoded string + #- name: MTLSClientKey + # value: "/Users/somepath/client.key" # OPTIONAL path to client key or PEM-encoded string + #- name: MTLSRenegotiation + # value: "RenegotiateOnceAsClient" # OPTIONAL one of: RenegotiateNever, RenegotiateOnceAsClient, RenegotiateFreelyAsClient + #- name: securityToken # OPTIONAL + # secretKeyRef: + # name: mysecret + # key: "mytoken" + #- name: securityTokenHeader + # value: "Authorization: Bearer" # OPTIONAL
``` ## Spec metadata fields | Field | Required | Binding support | Details | Example | |--------------------|:--------:|--------|--------|---------| -| `url` | Y | Output |The base URL of the HTTP endpoint to invoke | `http://host:port/path`, `http://myservice:8000/customers` -| `MTLSRootCA` | N | Output |Secret store reference, path to root ca certificate, or pem encoded string | -| `MTLSClientCert` | N | Output |Secret store reference, path to client certificate, or pem encoded string | -| `MTLSClientKey` | N | Output |Secret store reference, path client private key, or pem encoded string | -| `MTLSRenegotiation` | N | Output |Type of TLS renegotiation to be used | `RenegotiateOnceAsClient` -| `securityToken` | N | Output |The value of a token to be added to an HTTP request as a header. Used together with `securityTokenHeader` | -| `securityTokenHeader`| N | Output |The name of the header for `securityToken` on an HTTP request that | -| `direction`| N | Output |The direction of the binding | `"output"` - -### How to configure MTLS related fields in Metadata +| `url` | Y | Output | The base URL of the HTTP endpoint to invoke | `http://host:port/path`, `http://myservice:8000/customers` | +| `maxResponseBodySize`| N | Output | Maximum length of the response to read. A whole number is interpreted as bytes; units such as `Ki, Mi, Gi` (SI) or `k | M | G` (decimal) can be added for convenience. The default value is `100Mi` | "1Gi", "100Mi", "20Ki", "200" (bytes) | +| `MTLSRootCA` | N | Output | Path to root CA certificate or PEM-encoded string | +| `MTLSClientCert` | N | Output | Path to client certificate or PEM-encoded string | +| `MTLSClientKey` | N | Output | Path client private key or PEM-encoded string | +| `MTLSRenegotiation` | N | Output | Type of mTLS renegotiation to be used | `RenegotiateOnceAsClient` +| `securityToken` | N | Output | The value of a token to be added to a HTTP request as a header. Used together with `securityTokenHeader` | +| `securityTokenHeader` | N | Output | The name of the header for `securityToken` on a HTTP request | + +### How to configure mTLS-related fields in metadata + The values for **MTLSRootCA**, **MTLSClientCert** and **MTLSClientKey** can be provided in three ways: -1. Secret store reference -```yaml -apiVersion: dapr.io/v1alpha1 -kind: Component -metadata: - name: -spec: - type: bindings.http - version: v1 - metadata: - - name: url - value: http://something.com - - name: MTLSRootCA - secretKeyRef: - name: mysecret - key: myrootca -auth: - secretStore: -``` -2. Path to the file: The absolute path to the file can be provided as a value for the field. -3. PEM encoded string: The PEM encoded string can also be provided as a value for the field. + +- Secret store reference: + + ```yaml + apiVersion: dapr.io/v1alpha1 + kind: Component + metadata: + name: + spec: + type: bindings.http + version: v1 + metadata: + - name: url + value: http://something.com + - name: MTLSRootCA + secretKeyRef: + name: mysecret + key: myrootca + auth: + secretStore: + ``` + +- Path to the file: the absolute path to the file can be provided as a value for the field. +- PEM encoded string: the PEM-encoded string can also be provided as a value for the field. {{% alert title="Note" color="primary" %}} -Metadata fields **MTLSRootCA**, **MTLSClientCert** and **MTLSClientKey** are used to configure TLS(m) authentication. -To use mTLS authentication, you must provide all three fields. See [mTLS]({{< ref "#using-mtls-or-enabling-client-tls-authentication-along-with-https" >}}) for more details. You can also provide only **MTLSRootCA**, to enable **HTTPS** connection. See [HTTPS]({{< ref "#install-the-ssl-certificate-in-the-sidecar" >}}) section for more details. +Metadata fields **MTLSRootCA**, **MTLSClientCert** and **MTLSClientKey** are used to configure (m)TLS authentication. +To use mTLS authentication, you must provide all three fields. See [mTLS]({{< ref "#using-mtls-or-enabling-client-tls-authentication-along-with-https" >}}) for more details. You can also provide only **MTLSRootCA**, to enable **HTTPS** connection with a certificate signed by a custom CA. See [HTTPS]({{< ref "#install-the-ssl-certificate-in-the-sidecar" >}}) section for more details. {{% /alert %}} @@ -107,8 +111,8 @@ All of the operations above support the following metadata fields | Field | Required | Details | Example | |--------------------|:--------:|---------|---------| -| path | N | The path to append to the base URL. Used for accessing specific URIs | `"/1234"`, `"/search?lastName=Jones"` -| Headers* | N | Any fields that have a capital first letter are sent as request headers | `"Content-Type"`, `"Accept"` +| `path` | N | The path to append to the base URL. Used for accessing specific URIs. | `"/1234"`, `"/search?lastName=Jones"` +| Field with a capitalized first letter | N | Any fields that have a capital first letter are sent as request headers | `"Content-Type"`, `"Accept"` #### Retrieving data @@ -137,9 +141,9 @@ The response body contains the data returned by the HTTP endpoint. The `data` f | Field | Required | Details | Example | |--------------------|:--------:|---------|---------| -| statusCode | Y | The [HTTP status code](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html) | `200`, `404`, `503` -| status | Y | The status description | `"200 OK"`, `"201 Created"` -| Headers* | N | Any fields that have a capital first letter are sent as request headers | `"Content-Type"` +| `statusCode` | Y | The [HTTP status code](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html) | `200`, `404`, `503` | +| `status` | Y | The status description | `"200 OK"`, `"201 Created"` | +| Field with a capitalized first letter | N | Any fields that have a capital first letter are sent as request headers | `"Content-Type"` | #### Example @@ -168,14 +172,14 @@ curl -d '{ "operation": "get" }' \ {{< tabs Windows Linux >}} {{% codetab %}} -```bash +```sh curl -d "{ \"operation\": \"get\", \"metadata\": { \"path\": \"/things/1234\" } }" \ http://localhost:/v1.0/bindings/ ``` {{% /codetab %}} {{% codetab %}} -```bash +```sh curl -d '{ "operation": "get", "metadata": { "path": "/things/1234" } }' \ http://localhost:/v1.0/bindings/ ``` @@ -210,14 +214,14 @@ For example, the default content type is `application/json; charset=utf-8`. This {{< tabs Windows Linux >}} {{% codetab %}} -```bash +```sh curl -d "{ \"operation\": \"post\", \"data\": \"YOUR_BASE_64_CONTENT\", \"metadata\": { \"path\": \"/things\" } }" \ http://localhost:/v1.0/bindings/ ``` {{% /codetab %}} {{% codetab %}} -```bash +```sh curl -d '{ "operation": "post", "data": "YOUR_BASE_64_CONTENT", "metadata": { "path": "/things" } }' \ http://localhost:/v1.0/bindings/ ``` @@ -229,9 +233,8 @@ curl -d '{ "operation": "post", "data": "YOUR_BASE_64_CONTENT", "metadata": { "p The HTTP binding can also be used with HTTPS endpoints by configuring the Dapr sidecar to trust the server's SSL certificate. - 1. Update the binding URL to use `https` instead of `http`. -1. Refer [How-To: Install certificates in the Dapr sidecar]({{< ref install-certificates >}}), to install the SSL certificate in the sidecar. +1. If you need to add a custom TLS certificate, refer [How-To: Install certificates in the Dapr sidecar]({{< ref install-certificates >}}), to install the TLS certificates in the sidecar. ### Example @@ -251,13 +254,12 @@ spec: value: https://my-secured-website.com # Use HTTPS ``` -#### Install the SSL certificate in the sidecar - +#### Install the TLS certificate in the sidecar {{< tabs Self-Hosted Kubernetes >}} {{% codetab %}} -When the sidecar is not running inside a container, the SSL certificate can be directly installed on the host operating system. +When the sidecar is not running inside a container, the TLS certificate can be directly installed on the host operating system. Below is an example when the sidecar is running as a container. The SSL certificate is located on the host computer at `/tmp/ssl/cert.pem`. @@ -286,7 +288,7 @@ services: {{% codetab %}} -The sidecar can read the SSL certificate from a variety of sources. See [How-to: Mount Pod volumes to the Dapr sidecar]({{< ref kubernetes-volume-mounts >}}) for more. In this example, we store the SSL certificate as a Kubernetes secret. +The sidecar can read the TLS certificate from a variety of sources. See [How-to: Mount Pod volumes to the Dapr sidecar]({{< ref kubernetes-volume-mounts >}}) for more. In this example, we store the TLS certificate as a Kubernetes secret. ```bash kubectl create secret generic myapp-cert --from-file /tmp/ssl/cert.pem @@ -354,24 +356,26 @@ HTTPS binding support can also be configured using the **MTLSRootCA** metadata o {{% /alert %}} ## Using mTLS or enabling client TLS authentication along with HTTPS + You can configure the HTTP binding to use mTLS or client TLS authentication along with HTTPS by providing the `MTLSRootCA`, `MTLSClientCert`, and `MTLSClientKey` metadata fields in the binding component. -These fields can be passed as a file path or as a pem encoded string. +These fields can be passed as a file path or as a pem encoded string: + - If the file path is provided, the file is read and the contents are used. -- If the pem encoded string is provided, the string is used as is. +- If the PEM-encoded string is provided, the string is used as is. + When these fields are configured, the Dapr sidecar uses the provided certificate to authenticate itself with the server during the TLS handshake process. -If the remote server is enforcing TLS renegotiation, you also need to set the metadata field `MTLSRenegotiation`. This field accepts one of following options: +If the remote server is enforcing TLS renegotiation, you also need to set the metadata field `MTLSRenegotiation`. This field accepts one of following options: + - `RenegotiateNever` - `RenegotiateOnceAsClient` -- `RenegotiateFreelyAsClient`. +- `RenegotiateFreelyAsClient` For more details see [the Go `RenegotiationSupport` documentation](https://pkg.go.dev/crypto/tls#RenegotiationSupport). -### When to use: You can use this when the server with which the HTTP binding is configured to communicate requires mTLS or client TLS authentication. - ## Related links - [Basic schema for a Dapr component]({{< ref component-schema >}}) From c8c553ce5167c4b45367ac9a092c0748dc26e415 Mon Sep 17 00:00:00 2001 From: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> Date: Wed, 11 Oct 2023 12:38:16 -0700 Subject: [PATCH 115/162] Update docs for routeralias middleware Fixes #3525 Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> --- .../middleware-routeralias.md | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-middleware/middleware-routeralias.md b/daprdocs/content/en/reference/components-reference/supported-middleware/middleware-routeralias.md index 5b125be48f7..62d3083cf8d 100644 --- a/daprdocs/content/en/reference/components-reference/supported-middleware/middleware-routeralias.md +++ b/daprdocs/content/en/reference/components-reference/supported-middleware/middleware-routeralias.md @@ -7,12 +7,10 @@ aliases: - /developing-applications/middleware/supported-middleware/middleware-routeralias/ --- -The router alias HTTP [middleware]({{< ref middleware.md >}}) component allows you to convert arbitrary HTTP routes arriving to Dapr to valid Dapr API endpoints. +The router alias HTTP [middleware]({{< ref middleware.md >}}) component allows you to convert arbitrary HTTP routes arriving into Dapr to valid Dapr API endpoints. ## Component format -The router alias middleware metadata contains name/value pairs, where the name describes the HTTP route to expect, and the value describes the corresponding Dapr API the request should be sent to. - ```yaml apiVersion: dapr.io/v1alpha1 kind: Component @@ -22,17 +20,24 @@ spec: type: middleware.http.routeralias version: v1 metadata: - - name: "/v1.0/mall/activity/info" - value: "/v1.0/invoke/srv.default/method/mall/activity/info" - - name: "/v1.0/hello/activity/{id}/info" - value: "/v1.0/invoke/srv.default/method/hello/activity/info" - - name: "/v1.0/hello/activity/{id}/user" - value: "/v1.0/invoke/srv.default/method/hello/activity/user" + # String containing a JSON-encoded or YAML-encoded dictionary + # Each key in the dictionary is the incoming path, and the value is the path it's converted to + - name: "routes" + value: | + { + "/mall/activity/info": "/v1.0/invoke/srv.default/method/mall/activity/info", + "/hello/activity/{id}/info": "/v1.0/invoke/srv.default/method/hello/activity/info", + "/hello/activity/{id}/user": "/v1.0/invoke/srv.default/method/hello/activity/user" + } ``` -Example: +In the example above, an incoming HTTP request for `/mall/activity/info?id=123` is transformed into `/v1.0/invoke/srv.default/method/mall/activity/info?id=123`. + +# Spec metadata fields -An incoming HTTP request for `/v1.0/mall/activity/info?id=123` is transformed into `/v1.0/invoke/srv.default/method/mall/activity/info?id=123`. +| Field | Details | Example | +|-------|---------|---------| +| `routes` | String containing a JSON-encoded or YAML-encoded dictionary. Each key in the dictionary is the incoming path, and the value is the path it's converted to. | See example above | ## Dapr configuration From f0765662a0849c6aa95c326434e82db16a7decfb Mon Sep 17 00:00:00 2001 From: "Alessandro (Ale) Segala" <43508+ItalyPaleAle@users.noreply.github.com> Date: Wed, 11 Oct 2023 14:45:13 -0700 Subject: [PATCH 116/162] Update etcd version in table (#3809) * Update etcd version in table Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> * Update daprdocs/data/components/state_stores/generic.yaml Co-authored-by: Mark Fussell Signed-off-by: Alessandro (Ale) Segala <43508+ItalyPaleAle@users.noreply.github.com> --------- Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> Signed-off-by: Alessandro (Ale) Segala <43508+ItalyPaleAle@users.noreply.github.com> Co-authored-by: Mark Fussell --- daprdocs/data/components/state_stores/generic.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daprdocs/data/components/state_stores/generic.yaml b/daprdocs/data/components/state_stores/generic.yaml index a4ffcd52cb2..e0b685b648c 100644 --- a/daprdocs/data/components/state_stores/generic.yaml +++ b/daprdocs/data/components/state_stores/generic.yaml @@ -45,8 +45,8 @@ - component: etcd link: setup-etcd state: Beta - version: v1 - since: "1.11" + version: v2 + since: "1.12" features: crud: true transactions: true From 74cf5ce84a4164a6defc2812a61f7a97783acd00 Mon Sep 17 00:00:00 2001 From: "Alessandro (Ale) Segala" <43508+ItalyPaleAle@users.noreply.github.com> Date: Wed, 11 Oct 2023 15:05:59 -0700 Subject: [PATCH 117/162] Updated docs for postgres and mysql components (#3811) * Updated docs for postgres and mysql components Fixes #3602 Also includes docs for https://github.com/dapr/components-contrib/pull/2975 Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> * Update mysql.md * Update postgresql.md * Update daprdocs/content/en/reference/components-reference/supported-bindings/mysql.md Signed-off-by: Mark Fussell * Update postgresql.md * Update mysql.md --------- Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> Signed-off-by: Mark Fussell Co-authored-by: Artur Souza Co-authored-by: Mark Fussell --- .../supported-bindings/mysql.md | 44 +++- .../supported-bindings/postgresql.md | 79 ++++++-- .../postgresql-configuration-store.md | 189 ++++++++++++------ .../setup-postgresql.md | 91 ++++++--- 4 files changed, 277 insertions(+), 126 deletions(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/mysql.md b/daprdocs/content/en/reference/components-reference/supported-bindings/mysql.md index d03dcfcab89..3c44b53a84c 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/mysql.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/mysql.md @@ -60,16 +60,23 @@ Note that you can not use secret just for username/password. If you use secret, ### SSL connection If your server requires SSL your connection string must end of `&tls=custom` for example: + ```bash ":@tcp(:3306)/?allowNativePasswords=true&tls=custom" ``` - You must replace the `` with a full path to the PEM file. If you are using [MySQL on Azure](http://bit.ly/AzureMySQLSSL) see the Azure [documentation on SSL database connections](http://bit.ly/MySQLSSL), for information on how to download the required certificate. The connection to MySQL will require a minimum TLS version of 1.2. -Also note that by default [MySQL go driver](https://github.com/go-sql-driver/mysql) only supports one SQL statement per query/command. +> You must replace the `` with a full path to the PEM file. If you are using Azure Database for MySQL see the Azure [documentation on SSL database connections](https://learn.microsoft.com/azure/mysql/single-server/how-to-configure-ssl), for information on how to download the required certificate. The connection to MySQL requires a minimum TLS version of 1.2. + +### Multiple statements + +By default, the [MySQL Go driver](https://github.com/go-sql-driver/mysql) only supports one SQL statement per query/command. + To allow multiple statements in one query you need to add `multiStatements=true` to a query string, for example: + ```bash ":@tcp(:3306)/?multiStatements=true" ``` + While this allows batch queries, it also greatly increases the risk of SQL injections. Only the result of the first query is returned, all other results are silently discarded. @@ -81,17 +88,35 @@ This component supports **output binding** with the following operations: - `query` - `close` +### Parametrized queries + +This binding supports parametrized queries, which allow separating the SQL query itself from user-supplied values. The usage of parametrized queries is **strongly recommended** for security reasons, as they prevent [SQL Injection attacks](https://owasp.org/www-community/attacks/SQL_Injection). + +For example: + +```sql +-- ❌ WRONG! Includes values in the query and is vulnerable to SQL Injection attacks. +SELECT * FROM mytable WHERE user_key = 'something'; + +-- ✅ GOOD! Uses parametrized queries. +-- This will be executed with parameters ["something"] +SELECT * FROM mytable WHERE user_key = ?; +``` + ### exec The `exec` operation can be used for DDL operations (like table creation), as well as `INSERT`, `UPDATE`, `DELETE` operations which return only metadata (e.g. number of affected rows). +The `params` property is a string containing a JSON-encoded array of parameters. + **Request** ```json { "operation": "exec", "metadata": { - "sql": "INSERT INTO foo (id, c1, ts) VALUES (1, 'demo', '2020-09-24T11:45:05Z07:00')" + "sql": "INSERT INTO foo (id, c1, ts) VALUES (?, ?, ?)", + "params": "[1, \"demo\", \"2020-09-24T11:45:05Z07:00\"]" } } ``` @@ -106,7 +131,7 @@ The `exec` operation can be used for DDL operations (like table creation), as we "start-time": "2020-09-24T11:13:46.405097Z", "end-time": "2020-09-24T11:13:46.414519Z", "rows-affected": "1", - "sql": "INSERT INTO foo (id, c1, ts) VALUES (1, 'demo', '2020-09-24T11:45:05Z07:00')" + "sql": "INSERT INTO foo (id, c1, ts) VALUES (?, ?, ?)" } } ``` @@ -115,13 +140,16 @@ The `exec` operation can be used for DDL operations (like table creation), as we The `query` operation is used for `SELECT` statements, which returns the metadata along with data in a form of an array of row values. +The `params` property is a string containing a JSON-encoded array of parameters. + **Request** ```json { "operation": "query", "metadata": { - "sql": "SELECT * FROM foo WHERE id < 3" + "sql": "SELECT * FROM foo WHERE id < $1", + "params": "[3]" } } ``` @@ -135,7 +163,7 @@ The `query` operation is used for `SELECT` statements, which returns the metadat "duration": "432µs", "start-time": "2020-09-24T11:13:46.405097Z", "end-time": "2020-09-24T11:13:46.420566Z", - "sql": "SELECT * FROM foo WHERE id < 3" + "sql": "SELECT * FROM foo WHERE id < ?" }, "data": [ {column_name: value, column_name: value, ...}, @@ -150,7 +178,7 @@ or numbers (language specific data type) ### close -Finally, the `close` operation can be used to explicitly close the DB connection and return it to the pool. This operation doesn't have any response. +The `close` operation can be used to explicitly close the DB connection and return it to the pool. This operation doesn't have any response. **Request** @@ -160,8 +188,6 @@ Finally, the `close` operation can be used to explicitly close the DB connection } ``` -> Note, the MySQL binding itself doesn't prevent SQL injection, like with any database application, validate the input before executing query. - ## Related links - [Basic schema for a Dapr component]({{< ref component-schema >}}) diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/postgresql.md b/daprdocs/content/en/reference/components-reference/supported-bindings/postgresql.md index 31c9f230cff..cfaf92ad37c 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/postgresql.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/postgresql.md @@ -22,10 +22,11 @@ spec: type: bindings.postgresql version: v1 metadata: - - name: url # Required - value: "" - - name: direction - value: "" + # Connection string + - name: connectionString + value: "" + - name: direction + value: "" ``` {{% alert title="Warning" color="warning" %}} @@ -34,25 +35,48 @@ The above example uses secrets as plain strings. It is recommended to use a secr ## Spec metadata fields -| Field | Required | Binding support | Details | Example | -|--------------------|:--------:|------------|-----|---------| -| `url` | Y | Output | PostgreSQL connection string See [here](#url-format) for more details | `"user=dapr password=secret host=dapr.example.com port=5432 dbname=dapr sslmode=verify-ca"` | -| `direction` | N | Output | The direction of the binding | `"output"` | +### Authenticate using a connection string + +The following metadata options are **required** to authenticate using a PostgreSQL connection string. + +| Field | Required | Details | Example | +|--------|:--------:|---------|---------| +| `connectionString` | Y | The connection string for the PostgreSQL database. See the PostgreSQL [documentation on database connections](https://www.postgresql.org/docs/current/libpq-connect.html) for information on how to define a connection string. | `"host=localhost user=postgres password=example port=5432 connect_timeout=10 database=my_db"` + +### Authenticate using Azure AD + +Authenticating with Azure AD is supported with Azure Database for PostgreSQL. All authentication methods supported by Dapr can be used, including client credentials ("service principal") and Managed Identity. + +| Field | Required | Details | Example | +|--------|:--------:|---------|---------| +| `useAzureAD` | Y | Must be set to `true` to enable the component to retrieve access tokens from Azure AD. | `"true"` | +| `connectionString` | Y | The connection string for the PostgreSQL database.
This must contain the user, which corresponds to the name of the user created inside PostgreSQL that maps to the Azure AD identity; this is often the name of the corresponding principal (e.g. the name of the Azure AD application). This connection string should not contain any password. | `"host=mydb.postgres.database.azure.com user=myapplication port=5432 database=my_db sslmode=require"` | +| `azureTenantId` | N | ID of the Azure AD tenant | `"cd4b2887-304c-…"` | +| `azureClientId` | N | Client ID (application ID) | `"c7dd251f-811f-…"` | +| `azureClientSecret` | N | Client secret (application password) | `"Ecy3X…"` | + +### Other metadata options + +| Field | Required | Binding support |Details | Example | +|--------------------|:--------:|-----|---|---------| +| `maxConns` | N | Output | Maximum number of connections pooled by this component. Set to 0 or lower to use the default value, which is the greater of 4 or the number of CPUs. | `"4"` +| `connectionMaxIdleTime` | N | Output | Max idle time before unused connections are automatically closed in the connection pool. By default, there's no value and this is left to the database driver to choose. | `"5m"` +| `queryExecMode` | N | Output | Controls the default mode for executing queries. By default Dapr uses the extended protocol and automatically prepares and caches prepared statements. However, this may be incompatible with proxies such as PGBouncer. In this case it may be preferrable to use `exec` or `simple_protocol`. | `"simple_protocol"` ### URL format -The PostgreSQL binding uses [pgx connection pool](https://github.com/jackc/pgx) internally so the `url` parameter can be any valid connection string, either in a `DSN` or `URL` format: +The PostgreSQL binding uses [pgx connection pool](https://github.com/jackc/pgx) internally so the `connectionString` parameter can be any valid connection string, either in a `DSN` or `URL` format: **Example DSN** ```shell -user=dapr password=secret host=dapr.example.com port=5432 dbname=dapr sslmode=verify-ca +user=dapr password=secret host=dapr.example.com port=5432 dbname=my_dapr sslmode=verify-ca ``` **Example URL** ```shell -postgres://dapr:secret@dapr.example.com:5432/dapr?sslmode=verify-ca +postgres://dapr:secret@dapr.example.com:5432/my_dapr?sslmode=verify-ca ``` Both methods also support connection pool configuration variables: @@ -72,17 +96,35 @@ This component supports **output binding** with the following operations: - `query` - `close` +### Parametrized queries + +This binding supports parametrized queries, which allow separating the SQL query itself from user-supplied values. The usage of parametrized queries is **strongly recommended** for security reasons, as they prevent [SQL Injection attacks](https://owasp.org/www-community/attacks/SQL_Injection). + +For example: + +```sql +-- ❌ WRONG! Includes values in the query and is vulnerable to SQL Injection attacks. +SELECT * FROM mytable WHERE user_key = 'something'; + +-- ✅ GOOD! Uses parametrized queries. +-- This will be executed with parameters ["something"] +SELECT * FROM mytable WHERE user_key = $1; +``` + ### exec The `exec` operation can be used for DDL operations (like table creation), as well as `INSERT`, `UPDATE`, `DELETE` operations which return only metadata (e.g. number of affected rows). +The `params` property is a string containing a JSON-encoded array of parameters. + **Request** ```json { "operation": "exec", "metadata": { - "sql": "INSERT INTO foo (id, c1, ts) VALUES (1, 'demo', '2020-09-24T11:45:05Z07:00')" + "sql": "INSERT INTO foo (id, c1, ts) VALUES ($1, $2, $3)", + "params": "[1, \"demo\", \"2020-09-24T11:45:05Z07:00\"]" } } ``` @@ -97,7 +139,7 @@ The `exec` operation can be used for DDL operations (like table creation), as we "start-time": "2020-09-24T11:13:46.405097Z", "end-time": "2020-09-24T11:13:46.414519Z", "rows-affected": "1", - "sql": "INSERT INTO foo (id, c1, ts) VALUES (1, 'demo', '2020-09-24T11:45:05Z07:00')" + "sql": "INSERT INTO foo (id, c1, ts) VALUES ($1, $2, $3)" } } ``` @@ -106,13 +148,16 @@ The `exec` operation can be used for DDL operations (like table creation), as we The `query` operation is used for `SELECT` statements, which returns the metadata along with data in a form of an array of row values. +The `params` property is a string containing a JSON-encoded array of parameters. + **Request** ```json { "operation": "query", "metadata": { - "sql": "SELECT * FROM foo WHERE id < 3" + "sql": "SELECT * FROM foo WHERE id < $1", + "params": "[3]" } } ``` @@ -126,7 +171,7 @@ The `query` operation is used for `SELECT` statements, which returns the metadat "duration": "432µs", "start-time": "2020-09-24T11:13:46.405097Z", "end-time": "2020-09-24T11:13:46.420566Z", - "sql": "SELECT * FROM foo WHERE id < 3" + "sql": "SELECT * FROM foo WHERE id < $1" }, "data": "[ [0,\"test-0\",\"2020-09-24T04:13:46Z\"], @@ -138,7 +183,7 @@ The `query` operation is used for `SELECT` statements, which returns the metadat ### close -Finally, the `close` operation can be used to explicitly close the DB connection and return it to the pool. This operation doesn't have any response. +The `close` operation can be used to explicitly close the DB connection and return it to the pool. This operation doesn't have any response. **Request** @@ -148,8 +193,6 @@ Finally, the `close` operation can be used to explicitly close the DB connection } ``` -> Note, the PostgreSQL binding itself doesn't prevent SQL injection, like with any database application, validate the input before executing query. - ## Related links - [Basic schema for a Dapr component]({{< ref component-schema >}}) diff --git a/daprdocs/content/en/reference/components-reference/supported-configuration-stores/postgresql-configuration-store.md b/daprdocs/content/en/reference/components-reference/supported-configuration-stores/postgresql-configuration-store.md index 43b9820e081..15fa476ae00 100644 --- a/daprdocs/content/en/reference/components-reference/supported-configuration-stores/postgresql-configuration-store.md +++ b/daprdocs/content/en/reference/components-reference/supported-configuration-stores/postgresql-configuration-store.md @@ -21,13 +21,36 @@ spec: type: configuration.postgresql version: v1 metadata: - - name: connectionString - value: "host=localhost user=postgres password=example port=5432 connect_timeout=10 database=config" - - name: table # name of the table which holds configuration information - value: "[your_configuration_table_name]" - - name: connMaxIdleTime # max timeout for connection - value : "15s" - + # Connection string + - name: connectionString + value: "host=localhost user=postgres password=example port=5432 connect_timeout=10 database=config" + # Name of the table which holds configuration information + - name: table + value: "[your_configuration_table_name]" + # Timeout for database operations, in seconds (optional) + #- name: timeoutInSeconds + # value: 20 + # Name of the table where to store the state (optional) + #- name: tableName + # value: "state" + # Name of the table where to store metadata used by Dapr (optional) + #- name: metadataTableName + # value: "dapr_metadata" + # Cleanup interval in seconds, to remove expired rows (optional) + #- name: cleanupIntervalInSeconds + # value: 3600 + # Maximum number of connections pooled by this component (optional) + #- name: maxConns + # value: 0 + # Max idle time for connections before they're closed (optional) + #- name: connectionMaxIdleTime + # value: 0 + # Controls the default mode for executing queries. (optional) + #- name: queryExecMode + # value: "" + # Uncomment this if you wish to use PostgreSQL as a state store for actors (optional) + #- name: actorStateStore + # value: "true" ``` {{% alert title="Warning" color="warning" %}} @@ -36,69 +59,101 @@ The above example uses secrets as plain strings. It is recommended to use a secr ## Spec metadata fields -| Field | Required | Details | Example | +### Authenticate using a connection string + +The following metadata options are **required** to authenticate using a PostgreSQL connection string. + +| Field | Required | Details | Example | +|--------|:--------:|---------|---------| +| `connectionString` | Y | The connection string for the PostgreSQL database. See the PostgreSQL [documentation on database connections](https://www.postgresql.org/docs/current/libpq-connect.html) for information on how to define a connection string. | `"host=localhost user=postgres password=example port=5432 connect_timeout=10 database=my_db"` + +### Authenticate using Azure AD + +Authenticating with Azure AD is supported with Azure Database for PostgreSQL. All authentication methods supported by Dapr can be used, including client credentials ("service principal") and Managed Identity. + +| Field | Required | Details | Example | +|--------|:--------:|---------|---------| +| `useAzureAD` | Y | Must be set to `true` to enable the component to retrieve access tokens from Azure AD. | `"true"` | +| `connectionString` | Y | The connection string for the PostgreSQL database.
This must contain the user, which corresponds to the name of the user created inside PostgreSQL that maps to the Azure AD identity; this is often the name of the corresponding principal (e.g. the name of the Azure AD application). This connection string should not contain any password. | `"host=mydb.postgres.database.azure.com user=myapplication port=5432 database=my_db sslmode=require"` | +| `azureTenantId` | N | ID of the Azure AD tenant | `"cd4b2887-304c-…"` | +| `azureClientId` | N | Client ID (application ID) | `"c7dd251f-811f-…"` | +| `azureClientSecret` | N | Client secret (application password) | `"Ecy3X…"` | + +### Other metadata options + +| Field | Required | Details | Example | |--------------------|:--------:|---------|---------| -| connectionString | Y | The connection string for PostgreSQL. Default pool_max_conns = 5 | `"host=localhost user=postgres password=example port=5432 connect_timeout=10 database=dapr_test pool_max_conns=10"` -| table | Y | Table name for configuration information, must be lowercased. | `configtable` +| `table` | Y | Table name for configuration information, must be lowercased. | `configtable` +| `maxConns` | N | Maximum number of connections pooled by this component. Set to 0 or lower to use the default value, which is the greater of 4 or the number of CPUs. | `"4"` +| `connectionMaxIdleTime` | N | Max idle time before unused connections are automatically closed in the connection pool. By default, there's no value and this is left to the database driver to choose. | `"5m"` +| `queryExecMode` | N | Controls the default mode for executing queries. By default Dapr uses the extended protocol and automatically prepares and caches prepared statements. However, this may be incompatible with proxies such as PGBouncer. In this case it may be preferrable to use `exec` or `simple_protocol`. | `"simple_protocol"` ## Set up PostgreSQL as Configuration Store -1. Start PostgreSQL Database -1. Connect to the PostgreSQL database and setup a configuration table with following schema - +1. Start the PostgreSQL Database +1. Connect to the PostgreSQL database and setup a configuration table with following schema: + + | Field | Datatype | Nullable |Details | + |--------------------|:--------:|---------|---------| + | KEY | VARCHAR | N |Holds `"Key"` of the configuration attribute | + | VALUE | VARCHAR | N |Holds Value of the configuration attribute | + | VERSION | VARCHAR | N | Holds version of the configuration attribute | + | METADATA | JSON | Y | Holds Metadata as JSON | + + ```sql + CREATE TABLE IF NOT EXISTS table_name ( + KEY VARCHAR NOT NULL, + VALUE VARCHAR NOT NULL, + VERSION VARCHAR NOT NULL, + METADATA JSON + ); + ``` + +3. Create a TRIGGER on configuration table. An example function to create a TRIGGER is as follows: + + ```sh + CREATE OR REPLACE FUNCTION configuration_event() RETURNS TRIGGER AS $$ + DECLARE + data json; + notification json; + + BEGIN + + IF (TG_OP = 'DELETE') THEN + data = row_to_json(OLD); + ELSE + data = row_to_json(NEW); + END IF; + + notification = json_build_object( + 'table',TG_TABLE_NAME, + 'action', TG_OP, + 'data', data); + PERFORM pg_notify('config',notification::text); + RETURN NULL; + END; + $$ LANGUAGE plpgsql; + ``` + +4. Create the trigger with data encapsulated in the field labeled as `data`: + + ```sql + notification = json_build_object( + 'table',TG_TABLE_NAME, + 'action', TG_OP, + 'data', data + ); + ``` -| Field | Datatype | Nullable |Details | -|--------------------|:--------:|---------|---------| -| KEY | VARCHAR | N |Holds `"Key"` of the configuration attribute | -| VALUE | VARCHAR | N |Holds Value of the configuration attribute | -| VERSION | VARCHAR | N | Holds version of the configuration attribute -| METADATA | JSON | Y | Holds Metadata as JSON - -```console -CREATE TABLE IF NOT EXISTS table_name ( - KEY VARCHAR NOT NULL, - VALUE VARCHAR NOT NULL, - VERSION VARCHAR NOT NULL, - METADATA JSON ); -``` -3. Create a TRIGGER on configuration table. An example function to create a TRIGGER is as follows - -```console -CREATE OR REPLACE FUNCTION configuration_event() RETURNS TRIGGER AS $$ - DECLARE - data json; - notification json; - - BEGIN - - IF (TG_OP = 'DELETE') THEN - data = row_to_json(OLD); - ELSE - data = row_to_json(NEW); - END IF; - - notification = json_build_object( - 'table',TG_TABLE_NAME, - 'action', TG_OP, - 'data', data); - - PERFORM pg_notify('config',notification::text); - RETURN NULL; - END; -$$ LANGUAGE plpgsql; -``` -4. Create the trigger with data encapsulated in the field labelled as `data` -```ps -notification = json_build_object( - 'table',TG_TABLE_NAME, - 'action', TG_OP, - 'data', data); -``` 5. The channel mentioned as attribute to `pg_notify` should be used when subscribing for configuration notifications 6. Since this is a generic created trigger, map this trigger to `configuration table` -```console -CREATE TRIGGER config -AFTER INSERT OR UPDATE OR DELETE ON configtable - FOR EACH ROW EXECUTE PROCEDURE notify_event(); -``` + + ```sql + CREATE TRIGGER config + AFTER INSERT OR UPDATE OR DELETE ON configtable + FOR EACH ROW EXECUTE PROCEDURE notify_event(); + ``` + 7. In the subscribe request add an additional metadata field with key as `pgNotifyChannel` and value should be set to same `channel name` mentioned in `pg_notify`. From the above example, it should be set to `config` {{% alert title="Note" color="primary" %}} @@ -106,12 +161,14 @@ When calling `subscribe` API, `metadata.pgNotifyChannel` should be used to speci Any number of keys can be added to a subscription request. Each subscription uses an exclusive database connection. It is strongly recommended to subscribe to multiple keys within a single subscription. This helps optimize the number of connections to the database. -Example of subscribe HTTP API - -```ps -curl --location --request GET 'http://:/configuration/mypostgresql/subscribe?key=&key=&metadata.pgNotifyChannel=' +Example of subscribe HTTP API: + +```sh +curl -l 'http://:/configuration/mypostgresql/subscribe?key=&key=&metadata.pgNotifyChannel=' ``` {{% /alert %}} ## Related links + - [Basic schema for a Dapr component]({{< ref component-schema >}}) - [Configuration building block]({{< ref configuration-api-overview >}}) diff --git a/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-postgresql.md b/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-postgresql.md index 6e1bfad9216..0d5c682422e 100644 --- a/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-postgresql.md +++ b/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-postgresql.md @@ -7,13 +7,7 @@ aliases: - "/operations/components/setup-state-store/supported-state-stores/setup-postgresql/" --- -This component allows using PostgreSQL (Postgres) as state store for Dapr. - -## Create a Dapr component - -Create a file called `postgresql.yaml`, paste the following and replace the `` value with your connection string. The connection string is a standard PostgreSQL connection string. For example, `"host=localhost user=postgres password=example port=5432 connect_timeout=10 database=dapr_test"`. See the PostgreSQL [documentation on database connections](https://www.postgresql.org/docs/current/libpq-connect.html) for information on how to define a connection string. - -If you want to also configure PostgreSQL to store actors, add the `actorStateStore` option as in the example below. +This component allows using PostgreSQL (Postgres) as state store for Dapr. See [this guide]({{< ref "howto-get-save-state.md#step-1-setup-a-state-store" >}}) on how to create and apply a state store configuration. ```yaml apiVersion: dapr.io/v1alpha1 @@ -24,42 +18,72 @@ spec: type: state.postgresql version: v1 metadata: - # Connection string - - name: connectionString - value: "" - # Timeout for database operations, in seconds (optional) - #- name: timeoutInSeconds - # value: 20 - # Name of the table where to store the state (optional) - #- name: tableName - # value: "state" - # Name of the table where to store metadata used by Dapr (optional) - #- name: metadataTableName - # value: "dapr_metadata" - # Cleanup interval in seconds, to remove expired rows (optional) - #- name: cleanupIntervalInSeconds - # value: 3600 - # Max idle time for connections before they're closed (optional) - #- name: connectionMaxIdleTime - # value: 0 - # Uncomment this if you wish to use PostgreSQL as a state store for actors (optional) - #- name: actorStateStore - # value: "true" + # Connection string + - name: connectionString + value: "" + # Timeout for database operations, in seconds (optional) + #- name: timeoutInSeconds + # value: 20 + # Name of the table where to store the state (optional) + #- name: tableName + # value: "state" + # Name of the table where to store metadata used by Dapr (optional) + #- name: metadataTableName + # value: "dapr_metadata" + # Cleanup interval in seconds, to remove expired rows (optional) + #- name: cleanupIntervalInSeconds + # value: 3600 + # Maximum number of connections pooled by this component (optional) + #- name: maxConns + # value: 0 + # Max idle time for connections before they're closed (optional) + #- name: connectionMaxIdleTime + # value: 0 + # Controls the default mode for executing queries. (optional) + #- name: queryExecMode + # value: "" + # Uncomment this if you wish to use PostgreSQL as a state store for actors (optional) + #- name: actorStateStore + # value: "true" ``` + {{% alert title="Warning" color="warning" %}} The above example uses secrets as plain strings. It is recommended to use a secret store for the secrets as described [here]({{< ref component-secrets.md >}}). {{% /alert %}} ## Spec metadata fields +### Authenticate using a connection string + +The following metadata options are **required** to authenticate using a PostgreSQL connection string. + +| Field | Required | Details | Example | +|--------|:--------:|---------|---------| +| `connectionString` | Y | The connection string for the PostgreSQL database. See the PostgreSQL [documentation on database connections](https://www.postgresql.org/docs/current/libpq-connect.html) for information on how to define a connection string. | `"host=localhost user=postgres password=example port=5432 connect_timeout=10 database=my_db"` + +### Authenticate using Azure AD + +Authenticating with Azure AD is supported with Azure Database for PostgreSQL. All authentication methods supported by Dapr can be used, including client credentials ("service principal") and Managed Identity. + +| Field | Required | Details | Example | +|--------|:--------:|---------|---------| +| `useAzureAD` | Y | Must be set to `true` to enable the component to retrieve access tokens from Azure AD. | `"true"` | +| `connectionString` | Y | The connection string for the PostgreSQL database.
This must contain the user, which corresponds to the name of the user created inside PostgreSQL that maps to the Azure AD identity; this is often the name of the corresponding principal (e.g. the name of the Azure AD application). This connection string should not contain any password. | `"host=mydb.postgres.database.azure.com user=myapplication port=5432 database=my_db sslmode=require"` | +| `azureTenantId` | N | ID of the Azure AD tenant | `"cd4b2887-304c-…"` | +| `azureClientId` | N | Client ID (application ID) | `"c7dd251f-811f-…"` | +| `azureClientSecret` | N | Client secret (application password) | `"Ecy3X…"` | + +### Other metadata options + | Field | Required | Details | Example | |--------------------|:--------:|---------|---------| -| `connectionString` | Y | The connection string for the PostgreSQL database | `"host=localhost user=postgres password=example port=5432 connect_timeout=10 database=dapr_test"` | `timeoutInSeconds` | N | Timeout, in seconds, for all database operations. Defaults to `20` | `30` | `tableName` | N | Name of the table where the data is stored. Defaults to `state`. Can optionally have the schema name as prefix, such as `public.state` | `"state"`, `"public.state"` | `metadataTableName` | N | Name of the table Dapr uses to store a few metadata properties. Defaults to `dapr_metadata`. Can optionally have the schema name as prefix, such as `public.dapr_metadata` | `"dapr_metadata"`, `"public.dapr_metadata"` | `cleanupIntervalInSeconds` | N | Interval, in seconds, to clean up rows with an expired TTL. Default: `3600` (i.e. 1 hour). Setting this to values <=0 disables the periodic cleanup. | `1800`, `-1` +| `maxConns` | N | Maximum number of connections pooled by this component. Set to 0 or lower to use the default value, which is the greater of 4 or the number of CPUs. | `"4"` | `connectionMaxIdleTime` | N | Max idle time before unused connections are automatically closed in the connection pool. By default, there's no value and this is left to the database driver to choose. | `"5m"` +| `queryExecMode` | N | Controls the default mode for executing queries. By default Dapr uses the extended protocol and automatically prepares and caches prepared statements. However, this may be incompatible with proxies such as PGBouncer. In this case it may be preferrable to use `exec` or `simple_protocol`. | `"simple_protocol"` | `actorStateStore` | N | Consider this state store for actors. Defaults to `"false"` | `"true"`, `"false"` ## Setup PostgreSQL @@ -70,20 +94,21 @@ The above example uses secrets as plain strings. It is recommended to use a secr 1. Run an instance of PostgreSQL. You can run a local instance of PostgreSQL in Docker CE with the following command: - This example does not describe a production configuration because it sets the password in plain text and the user name is left as the PostgreSQL default of "postgres". - ```bash docker run -p 5432:5432 -e POSTGRES_PASSWORD=example postgres ``` + > This example does not describe a production configuration because it sets the password in plain text and the user name is left as the PostgreSQL default of "postgres". + 2. Create a database for state data. Either the default "postgres" database can be used, or create a new database for storing state data. To create a new database in PostgreSQL, run the following SQL command: - ```SQL - CREATE DATABASE dapr_test; + ```sql + CREATE DATABASE my_dapr; ``` + {{% /codetab %}} {{% /tabs %}} From 34f0989a10d7b885d63087d78b74be55c507f56e Mon Sep 17 00:00:00 2001 From: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Date: Wed, 11 Oct 2023 18:07:38 -0400 Subject: [PATCH 118/162] Update daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md Co-authored-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> Signed-off-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> --- .../building-blocks/pubsub/pubsub-bulk.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md b/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md index 9e44faa716d..854955712e9 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md +++ b/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md @@ -477,7 +477,7 @@ When talking about event publish/subscribe, there are two kind of network transf 1. From/To *App* To/From *Dapr*. 2. From/To *Dapr* To/From *Pubsub Broker*. -And, these are the opportunities where optimization is possible for multiple Single calls v/s Bulk. Here, optimization means that Bulk calls are sent, which reduce number of overall calls and thus increased throughput ad better latency. +These are the opportunities where optimization is possible. When optimized, a Bulk requests are, which reduce number of overall calls and thus increase throughput and provide better latency. On enabling Bulk Publish and/or Bulk Subscribe, Point 1 i.e. From/To *App* To/From *Dapr* is optimized for All components (depending on whether either one of Bulk Publish/ Bulk Subscribe OR Both are enabled). From b5eca24317990db61b179a6b7aed1657e1b9adf9 Mon Sep 17 00:00:00 2001 From: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Date: Wed, 11 Oct 2023 18:07:48 -0400 Subject: [PATCH 119/162] Update daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md Co-authored-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> Signed-off-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> --- .../building-blocks/pubsub/pubsub-bulk.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md b/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md index 854955712e9..30d30944639 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md +++ b/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md @@ -479,7 +479,7 @@ When talking about event publish/subscribe, there are two kind of network transf These are the opportunities where optimization is possible. When optimized, a Bulk requests are, which reduce number of overall calls and thus increase throughput and provide better latency. -On enabling Bulk Publish and/or Bulk Subscribe, Point 1 i.e. From/To *App* To/From *Dapr* is optimized for All components (depending on whether either one of Bulk Publish/ Bulk Subscribe OR Both are enabled). +On enabling Bulk Publish and/or Bulk Subscribe, the communication between the App and Dapr sidecar (Point 1 above) is optimized for **all components**. But, optimization of Point 2 i.e. From/To *Dapr* To/From *Pubsub Broker* would depend on a number of factors, i.e. if Broker inherently supports Bulk Publish/Subscribe and if Dapr component is updated to support Batching. Currently, following components are updated to support this level of optimization: From 1a2f5673b788f0a486ce1c40620921f6b9fc0e63 Mon Sep 17 00:00:00 2001 From: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Date: Wed, 11 Oct 2023 18:07:56 -0400 Subject: [PATCH 120/162] Update daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md Co-authored-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> Signed-off-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> --- .../building-blocks/pubsub/pubsub-bulk.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md b/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md index 30d30944639..ef994f4328d 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md +++ b/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md @@ -481,7 +481,11 @@ These are the opportunities where optimization is possible. When optimized, a Bu On enabling Bulk Publish and/or Bulk Subscribe, the communication between the App and Dapr sidecar (Point 1 above) is optimized for **all components**. -But, optimization of Point 2 i.e. From/To *Dapr* To/From *Pubsub Broker* would depend on a number of factors, i.e. if Broker inherently supports Bulk Publish/Subscribe and if Dapr component is updated to support Batching. Currently, following components are updated to support this level of optimization: +Optimization from Dapr sidecar to the pub/sub broker would depend on a number of factors, for example: +- If the broker inherently supports Bulk pub/sub +- If the Dapr component is updated to support the use of bulk APIs provided by the broker. + +Currently, the following components are updated to support this level of optimization:
From 5146413dda5b96feb55f1931a5cd39e8ae374152 Mon Sep 17 00:00:00 2001 From: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Date: Wed, 11 Oct 2023 18:08:34 -0400 Subject: [PATCH 121/162] Update daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md Signed-off-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> --- .../building-blocks/pubsub/pubsub-bulk.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md b/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md index ef994f4328d..05bc1bc59c1 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md +++ b/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md @@ -475,7 +475,7 @@ public class BulkMessageController : ControllerBase When talking about event publish/subscribe, there are two kind of network transfers involved. 1. From/To *App* To/From *Dapr*. -2. From/To *Dapr* To/From *Pubsub Broker*. +1. From/To *Dapr* To/From *Pubsub Broker*. These are the opportunities where optimization is possible. When optimized, a Bulk requests are, which reduce number of overall calls and thus increase throughput and provide better latency. From c47d29df084cdf75a7ccd3eed3b0367776af305b Mon Sep 17 00:00:00 2001 From: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Date: Wed, 11 Oct 2023 18:08:53 -0400 Subject: [PATCH 122/162] Update daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md Signed-off-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> --- .../building-blocks/pubsub/pubsub-bulk.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md b/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md index 05bc1bc59c1..3961d37570d 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md +++ b/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md @@ -473,7 +473,7 @@ public class BulkMessageController : ControllerBase {{< /tabs >}} ## How components handle publishing and subscribing to bulk messages -When talking about event publish/subscribe, there are two kind of network transfers involved. +For event publish/subscribe, two kinds of network transfers are involved. 1. From/To *App* To/From *Dapr*. 1. From/To *Dapr* To/From *Pubsub Broker*. From 1a260817d0f6288ef74f58aded663bcea16502ec Mon Sep 17 00:00:00 2001 From: Mark Fussell Date: Wed, 11 Oct 2023 15:18:01 -0700 Subject: [PATCH 123/162] Update http.md --- .../reference/components-reference/supported-bindings/http.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/http.md b/daprdocs/content/en/reference/components-reference/supported-bindings/http.md index 7f5f2972968..a724e30b93a 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/http.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/http.md @@ -40,6 +40,8 @@ spec: # key: "mytoken" #- name: securityTokenHeader # value: "Authorization: Bearer" # OPTIONAL
+ #- name: direction + # value: "output" ``` ## Spec metadata fields @@ -54,6 +56,7 @@ spec: | `MTLSRenegotiation` | N | Output | Type of mTLS renegotiation to be used | `RenegotiateOnceAsClient` | `securityToken` | N | Output | The value of a token to be added to a HTTP request as a header. Used together with `securityTokenHeader` | | `securityTokenHeader` | N | Output | The name of the header for `securityToken` on a HTTP request | +| `direction` | N | Output |The direction of the binding | `"output"` ### How to configure mTLS-related fields in metadata From 9e09c132ed91582490354d718f426231bb0e14d0 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 11 Oct 2023 18:42:28 -0400 Subject: [PATCH 124/162] update support table Signed-off-by: Hannah Hunter --- .../operations/support/support-release-policy.md | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/daprdocs/content/en/operations/support/support-release-policy.md b/daprdocs/content/en/operations/support/support-release-policy.md index dc3f8d080eb..8dd71a45723 100644 --- a/daprdocs/content/en/operations/support/support-release-policy.md +++ b/daprdocs/content/en/operations/support/support-release-policy.md @@ -45,7 +45,7 @@ The table below shows the versions of Dapr releases that have been tested togeth | Release date | Runtime | CLI | SDKs | Dashboard | Status | Release notes | |--------------------|:--------:|:--------|---------|---------|---------|------------| -| September 26th 2023 | 1.12.0
| 1.12.0 | Java 1.9.0
Go 1.8.0
PHP 1.1.0
Python 1.10.0
.NET 1.11.0
JS 3.1.0 | 0.13.0 | Supported (current) | [v1.12.0 release notes](https://github.com/dapr/dapr/releases/tag/v1.12.0) | +| October 11th 2023 | 1.12.0
| 1.12.0 | Java 1.10.0
Go 1.9.0
PHP 1.1.0
Python 1.11.0
.NET 1.12.0
JS 3.1.2 | 0.13.0 | Supported (current) | [v1.12.0 release notes](https://github.com/dapr/dapr/releases/tag/v1.12.0) | | August 31st 2023 | 1.11.3
| 1.11.0 | Java 1.9.0
Go 1.8.0
PHP 1.1.0
Python 1.10.0
.NET 1.11.0
JS 3.1.0 | 0.13.0 | Supported | [v1.11.3 release notes](https://github.com/dapr/dapr/releases/tag/v1.11.3) | | July 20th 2023 | 1.11.2
| 1.11.0 | Java 1.9.0
Go 1.8.0
PHP 1.1.0
Python 1.10.0
.NET 1.11.0
JS 3.1.0 | 0.13.0 | Supported | [v1.11.2 release notes](https://github.com/dapr/dapr/releases/tag/v1.11.2) | | June 22nd 2023 | 1.11.1
| 1.11.0 | Java 1.9.0
Go 1.8.0
PHP 1.1.0
Python 1.10.0
.NET 1.11.0
JS 3.1.0 | 0.13.0 | Supported | [v1.11.1 release notes](https://github.com/dapr/dapr/releases/tag/v1.11.1) | @@ -82,16 +82,6 @@ The table below shows the versions of Dapr releases that have been tested togeth | Apr 20th 2022 | 1.6.2
| 1.6.0 | Java 1.4.0
Go 1.3.1
PHP 1.1.0
Python 1.5.0
.NET 1.6.0
JS 2.0.0 | 0.9.0 | Unsupported | | | Mar 25th 2022 | 1.6.1
| 1.6.0 | Java 1.4.0
Go 1.3.1
PHP 1.1.0
Python 1.5.0
.NET 1.6.0
JS 2.0.0 | 0.9.0 | Unsupported | | | Jan 25th 2022 | 1.6.0
| 1.6.0 | Java 1.4.0
Go 1.3.1
PHP 1.1.0
Python 1.5.0
.NET 1.6.0
JS 2.0.0 | 0.9.0 | Unsupported | | -| Mar 25th 2022 | 1.5.2
| 1.6.0 | Java 1.3.0
Go 1.3.0
PHP 1.1.0
Python 1.4.0
.NET 1.5.0
JS 1.0.2 | 0.9.0 | Unsupported | | -| Dec 6th 2021 | 1.5.1
| 1.5.1 | Java 1.3.0
Go 1.3.0
PHP 1.1.0
Python 1.4.0
.NET 1.5.0
JS 1.0.2 | 0.9.0 | Unsupported | | -| Nov 11th 2021 | 1.5.0
| 1.5.0 | Java 1.3.0
Go 1.3.0
PHP 1.1.0
Python 1.4.0
.NET 1.5.0
JS 1.0.2 | 0.9.0 | Unsupported | | -| Dev 6th 2021 | 1.4.4
| 1.4.0 | Java 1.3.0
Go 1.2.0
PHP 1.1.0
Python 1.3.0
.NET 1.4.0 | 0.8.0 | Unsupported | | -| Oct 7th 2021 | 1.4.3
| 1.4.0 | Java 1.3.0
Go 1.2.0
PHP 1.1.0
Python 1.3.0
.NET 1.4.0 | 0.8.0 | Unsupported | | -| Sep 24th 2021 | 1.4.2
| 1.4.0 | Java 1.3.0
Go 1.2.0
PHP 1.1.0
Python 1.3.0
.NET 1.4.0 | 0.8.0 | Unsupported | | -| Sep 22nd 2021 | 1.4.1
| 1.4.0 | Java 1.3.0
Go 1.2.0
PHP 1.1.0
Python 1.3.0
.NET 1.4.0 | 0.8.0 | Unsupported | | -| Sep 15th 2021 | 1.4
| 1.4.0 | Java 1.3.0
Go 1.2.0
PHP 1.1.0
Python 1.3.0
.NET 1.4.0 | 0.8.0 | Unsupported | | -| Sep 14th 2021 | 1.3.1
| 1.3.0 | Java 1.2.0
Go 1.2.0
PHP 1.1.0
Python 1.2.0
.NET 1.3.0 | 0.7.0 | Unsupported | | -| Jul 26th 2021 | 1.3
| 1.3.0 | Java 1.2.0
Go 1.2.0
PHP 1.1.0
Python 1.2.0
.NET 1.3.0 | 0.7.0 | Unsupported | | ## Upgrade paths @@ -123,7 +113,7 @@ General guidance on upgrading can be found for [self hosted mode]({{< ref self-h | 1.8.0 to 1.8.6 | N/A | 1.9.6 | | 1.9.0 | N/A | 1.9.6 | | 1.10.0 | N/A | 1.10.8 | -| 1.11.0 | N/A | 1.11.3 | +| 1.11.0 | N/A | 1.11.4 | | 1.12.0 | N/A | 1.12.0 | From 84e9dc1595c2407cbf633803073079b051e41a76 Mon Sep 17 00:00:00 2001 From: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> Date: Wed, 11 Oct 2023 22:52:56 +0000 Subject: [PATCH 125/162] Remove direction metadata from output-only bindings Signed-off-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> --- .../building-blocks/bindings/bindings-overview.md | 4 ++-- .../supported-bindings/alicloudoss.md | 5 +---- .../supported-bindings/alicloudsls.md | 5 +---- .../supported-bindings/alicloudtablestore.md | 5 +---- .../components-reference/supported-bindings/apns.md | 7 ++++--- .../supported-bindings/blobstorage.md | 3 --- .../supported-bindings/cloudflare-queues.md | 4 ---- .../supported-bindings/commercetools.md | 7 +------ .../components-reference/supported-bindings/cosmosdb.md | 4 ---- .../supported-bindings/cosmosdbgremlinapi.md | 4 ---- .../components-reference/supported-bindings/dynamodb.md | 3 --- .../components-reference/supported-bindings/gcpbucket.md | 3 --- .../components-reference/supported-bindings/graghql.md | 3 --- .../components-reference/supported-bindings/http.md | 3 --- .../supported-bindings/huawei-obs.md | 3 --- .../components-reference/supported-bindings/influxdb.md | 3 --- .../components-reference/supported-bindings/kitex.md | 5 +---- .../supported-bindings/localstorage.md | 6 +----- .../components-reference/supported-bindings/mysql.md | 4 ---- .../supported-bindings/postgresql.md | 2 -- .../components-reference/supported-bindings/postmark.md | 3 --- .../components-reference/supported-bindings/redis.md | 4 ---- .../components-reference/supported-bindings/s3.md | 3 --- .../components-reference/supported-bindings/sendgrid.md | 3 --- .../components-reference/supported-bindings/ses.md | 3 --- .../components-reference/supported-bindings/signalr.md | 3 --- .../components-reference/supported-bindings/smtp.md | 3 --- .../components-reference/supported-bindings/sns.md | 3 --- .../components-reference/supported-bindings/twilio.md | 5 ----- .../components-reference/supported-bindings/wasm.md | 9 +++------ .../supported-bindings/zeebe-command.md | 3 --- .../supported-bindings/zeebe-jobworker.md | 6 +++--- 32 files changed, 18 insertions(+), 113 deletions(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/bindings/bindings-overview.md b/daprdocs/content/en/developing-applications/building-blocks/bindings/bindings-overview.md index 980a39f79a3..5df403fb7b4 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/bindings/bindings-overview.md +++ b/daprdocs/content/en/developing-applications/building-blocks/bindings/bindings-overview.md @@ -76,14 +76,14 @@ Read the [Use output bindings to interface with external resources guide]({{< re ## Binding directions (optional) -You can provide the `direction` metadata field to indicate the direction(s) supported by the binding component. In doing so, the Dapr sidecar avoids the `"wait for the app to become ready"` state reducing the lifecycle dependency between the Dapr sidecar and the application: +You can provide the `direction` metadata field to indicate the direction(s) supported by the binding component. In doing so, the Dapr sidecar avoids the `"wait for the app to become ready"` state, reducing the lifecycle dependency between the Dapr sidecar and the application: - `"input"` - `"output"` - `"input, output"` {{% alert title="Note" color="primary" %}} -It is highly recommended that all bindings should include the `direction` property. +It is highly recommended that all input bindings should include the `direction` property. {{% /alert %}} [See a full example of the bindings `direction` metadata.]({{< ref "bindings_api.md#binding-direction-optional" >}}) diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/alicloudoss.md b/daprdocs/content/en/reference/components-reference/supported-bindings/alicloudoss.md index 4036bb03741..0857b6dcbd9 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/alicloudoss.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/alicloudoss.md @@ -28,8 +28,6 @@ spec: value: "[access-key]" - name: bucket value: "[bucket]" - - name: direction - value: "output" ``` {{% alert title="Warning" color="warning" %}} @@ -44,13 +42,12 @@ The above example uses secrets as plain strings. It is recommended to use a secr | `accessKeyID` | Y | Output | Access key ID credential. | | `accessKey` | Y | Output | Access key credential. | | `bucket` | Y | Output | Name of the storage bucket. | -| `direction` | N | Output | Direction of the binding. | `"output"` ## Binding support This component supports **output binding** with the following operations: -- `create`: [Create object](#create-object) +- `create`: [Create object](#create-object) ### Create object diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/alicloudsls.md b/daprdocs/content/en/reference/components-reference/supported-bindings/alicloudsls.md index b81db6d3cce..df500758ec2 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/alicloudsls.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/alicloudsls.md @@ -26,8 +26,6 @@ spec: value: "[accessKey-secret]" - name: Endpoint value: "[endpoint]" - - name: direction - value: "output" ``` ## Spec metadata fields @@ -37,13 +35,12 @@ spec: | `AccessKeyID` | Y | Output | Access key ID credential. | | `AccessKeySecret` | Y | Output | Access key credential secret | | `Endpoint` | Y | Output | Alicloud SLS endpoint. | -| `direction` | N | Output | Direction of the binding. | `"output"` ## Binding support This component supports **output binding** with the following operations: -- `create`: [Create object](#create-object) +- `create`: [Create object](#create-object) ### Request format diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/alicloudtablestore.md b/daprdocs/content/en/reference/components-reference/supported-bindings/alicloudtablestore.md index 61daf950eb6..67aa3fc16d1 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/alicloudtablestore.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/alicloudtablestore.md @@ -32,8 +32,6 @@ spec: value: "[table]" - name: endpoint value: "[endpoint]" - - name: direction - value: "output" ``` {{% alert title="Warning" color="warning" %}} @@ -49,13 +47,12 @@ The above example uses secrets as plain strings. It is recommended to use a secr | `accessKey` | Y | Output | Access key credential. | | `instanceName` | Y | Output | Name of the instance. | | `tableName` | Y | Output | Name of the table. | -| `direction` | N | Output | Direction of the binding. | `"output"` ## Binding support This component supports **output binding** with the following operations: -- `create`: [Create object](#create-object) +- `create`: [Create object](#create-object) ### Create object diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/apns.md b/daprdocs/content/en/reference/components-reference/supported-bindings/apns.md index 3b534cc55ea..a50100d8210 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/apns.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/apns.md @@ -30,8 +30,6 @@ spec: secretKeyRef: name: key: "" - - name: direction - value: "output" ``` ## Spec metadata fields @@ -41,14 +39,15 @@ spec: | `key-id` | Y | Output | The identifier for the private key from the Apple Developer Portal | `"private-key-id`" | | `team-id` | Y | Output | The identifier for the organization or author from the Apple Developer Portal | `"team-id"` | | `private-key` | Y | Output| Is a PKCS #8-formatted private key. It is intended that the private key is stored in the secret store and not exposed directly in the configuration. See [here](#private-key) for more details | `"pem file"` | -| `direction` | N | Output| The direction of the binding. | `"output"` | ### Private key + The APNS binding needs a cryptographic private key in order to generate authentication tokens for the APNS service. The private key can be generated from the Apple Developer Portal and is provided as a PKCS #8 file with the private key stored in PEM format. The private key should be stored in the Dapr secret store and not stored directly in the binding's configuration file. A sample configuration file for the APNS binding is shown below: + ```yaml apiVersion: dapr.io/v1alpha1 kind: Component @@ -68,7 +67,9 @@ spec: name: apns-secrets key: private-key ``` + If using Kubernetes, a sample secret configuration may look like this: + ```yaml apiVersion: v1 kind: Secret diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/blobstorage.md b/daprdocs/content/en/reference/components-reference/supported-bindings/blobstorage.md index b5ed204388e..3df3e28048b 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/blobstorage.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/blobstorage.md @@ -33,8 +33,6 @@ spec: # value: # - name: publicAccessLevel # value: -# - name: direction -# value: "output" ``` {{% alert title="Warning" color="warning" %}} The above example uses secrets as plain strings. It is recommended to use a secret store for the secrets as described [here]({{< ref component-secrets.md >}}). @@ -51,7 +49,6 @@ The above example uses secrets as plain strings. It is recommended to use a secr | `decodeBase64` | N | Output | Configuration to decode base64 file content before saving to Blob Storage. (In case of saving a file with binary content). Defaults to `false` | `true`, `false` | | `getBlobRetryCount` | N | Output | Specifies the maximum number of HTTP GET requests that will be made while reading from a RetryReader Defaults to `10` | `1`, `2` | `publicAccessLevel` | N | Output | Specifies whether data in the container may be accessed publicly and the level of access (only used if the container is created by Dapr). Defaults to `none` | `blob`, `container`, `none` -| `direction` | N | Output | The direction of the binding. | `"output"` ### Azure Active Directory (AAD) authentication diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/cloudflare-queues.md b/daprdocs/content/en/reference/components-reference/supported-bindings/cloudflare-queues.md index b1196b54feb..2a1420b6df6 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/cloudflare-queues.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/cloudflare-queues.md @@ -46,9 +46,6 @@ spec: # URL of the Worker (required if the Worker has been pre-created outside of Dapr) - name: workerUrl value: "" - # Direction of the binding - - name: direction - value: "output" ``` {{% alert title="Warning" color="warning" %}} @@ -64,7 +61,6 @@ The above example uses secrets as plain strings. It is recommended to use a secr | `cfAccountID` | Y/N | Output | Cloudflare account ID. Required to have Dapr manage the worker. | `"456789abcdef8b5588f3d134f74ac"def` | `cfAPIToken` | Y/N | Output | API token for Cloudflare. Required to have Dapr manage the Worker. | `"secret-key"` | `workerUrl` | Y/N | Output | URL of the Worker. Required if the Worker has been pre-provisioned outside of Dapr. | `"https://mydaprqueue.mydomain.workers.dev"` -| `direction` | N | Output | Direction of the binding. | `"output"` > When you configure Dapr to create your Worker for you, you may need to set a longer value for the `initTimeout` property of the component, to allow enough time for the Worker script to be deployed. For example: `initTimeout: "120s"` diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/commercetools.md b/daprdocs/content/en/reference/components-reference/supported-bindings/commercetools.md index 94fd95d1484..90ea8edfbbc 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/commercetools.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/commercetools.md @@ -11,8 +11,6 @@ aliases: To setup commercetools GraphQL binding create a component of type `bindings.commercetools`. See [this guide]({{< ref "howto-bindings.md#1-create-a-binding" >}}) on how to create and apply a binding configuration. - - ```yaml apiVersion: dapr.io/v1alpha1 kind: Component @@ -34,9 +32,8 @@ spec: value: "*****************" - name: scopes # required. value: "" - - name: direction - value: "output" ``` + {{% alert title="Warning" color="warning" %}} The above example uses secrets as plain strings. It is recommended to use a secret store for the secrets as described [here]({{< ref component-secrets.md >}}). {{% /alert %}} @@ -51,7 +48,6 @@ The above example uses secrets as plain strings. It is recommended to use a secr | `clientID` | Y | Output | The commercetools client ID for the project | | | `clientSecret` | Y | Output | The commercetools client secret for the project | | | `scopes` | Y | Output | The commercetools scopes for the project | `"manage_project:project-key"` | -| `direction` | N | Output | The direction of the binding | `"output"` | For more information see [commercetools - Creating an API Client](https://docs.commercetools.com/getting-started/create-api-client#create-an-api-client) and [commercetools - Regions](https://docs.commercetools.com/api/general-concepts#regions). @@ -61,7 +57,6 @@ This component supports **output binding** with the following operations: - `create` - ## Related links - [Basic schema for a Dapr component]({{< ref component-schema >}}) diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/cosmosdb.md b/daprdocs/content/en/reference/components-reference/supported-bindings/cosmosdb.md index 111ecab83c0..661c75e8e3f 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/cosmosdb.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/cosmosdb.md @@ -11,7 +11,6 @@ aliases: To setup Azure Cosmos DB binding create a component of type `bindings.azure.cosmosdb`. See [this guide]({{< ref "howto-bindings.md#1-create-a-binding" >}}) on how to create and apply a binding configuration. - ```yaml apiVersion: dapr.io/v1alpha1 kind: Component @@ -31,8 +30,6 @@ spec: value: "Orders" - name: partitionKey value: "" - - name: direction - value: "output" ``` {{% alert title="Warning" color="warning" %}} @@ -48,7 +45,6 @@ The above example uses secrets as plain strings. It is recommended to use a secr | `database` | Y | Output | The name of the Cosmos DB database | `"OrderDb"` | | `collection` | Y | Output | The name of the container inside the database. | `"Orders"` | | `partitionKey` | Y | Output | The name of the key to extract from the payload (document to be created) that is used as the partition key. This name must match the partition key specified upon creation of the Cosmos DB container. | `"OrderId"`, `"message"` | -| `direction` | N | Output | The direction of the binding. | `"output"` | For more information see [Azure Cosmos DB resource model](https://docs.microsoft.com/azure/cosmos-db/account-databases-containers-items). diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/cosmosdbgremlinapi.md b/daprdocs/content/en/reference/components-reference/supported-bindings/cosmosdbgremlinapi.md index 505bc5ca6bc..47aa5b506ec 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/cosmosdbgremlinapi.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/cosmosdbgremlinapi.md @@ -9,7 +9,6 @@ description: "Detailed documentation on the Azure Cosmos DB (Gremlin API) bindin To setup an Azure Cosmos DB (Gremlin API) binding create a component of type `bindings.azure.cosmosdb.gremlinapi`. See [this guide]({{< ref "howto-bindings.md#1-create-a-binding" >}}) on how to create and apply a binding configuration. - ```yaml apiVersion: dapr.io/v1alpha1 kind: Component @@ -25,8 +24,6 @@ spec: value: "*****" - name: username value: "*****" - - name: direction - value: "output" ``` {{% alert title="Warning" color="warning" %}} @@ -40,7 +37,6 @@ The above example uses secrets as plain strings. It is recommended to use a secr | `url` | Y | Output | The Cosmos DB url for Gremlin APIs | `"wss://******.gremlin.cosmos.azure.com:443/"` | | `masterKey` | Y | Output | The Cosmos DB account master key | `"masterKey"` | | `username` | Y | Output | The username of the Cosmos DB database | `"/dbs//colls/"` | -| `direction` | N | Output | The direction of the binding | `"output"` | For more information see [Quickstart: Azure Cosmos Graph DB using Gremlin](https://docs.microsoft.com/azure/cosmos-db/graph/create-graph-console). diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/dynamodb.md b/daprdocs/content/en/reference/components-reference/supported-bindings/dynamodb.md index 63654df5c87..348d15515c8 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/dynamodb.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/dynamodb.md @@ -32,8 +32,6 @@ spec: value: "*****************" - name: sessionToken value: "*****************" - - name: direction - value: "output" ``` {{% alert title="Warning" color="warning" %}} @@ -49,7 +47,6 @@ The above example uses secrets as plain strings. It is recommended to use a secr | `accessKey` | Y | Output | The AWS Access Key to access this resource | `"key"` | | `secretKey` | Y | Output | The AWS Secret Access Key to access this resource | `"secretAccessKey"` | | `sessionToken` | N | Output | The AWS session token to use | `"sessionToken"` | -| `direction` | N | Output | The direction of the binding | `"output"` | {{% alert title="Important" color="warning" %}} When running the Dapr sidecar (daprd) with your application on EKS (AWS Kubernetes), if you're using a node/pod that has already been attached to an IAM policy defining access to AWS resources, you **must not** provide AWS access-key, secret-key, and tokens in the definition of the component spec you're using. diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/gcpbucket.md b/daprdocs/content/en/reference/components-reference/supported-bindings/gcpbucket.md index c4097a525c6..f2d14d320b3 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/gcpbucket.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/gcpbucket.md @@ -47,8 +47,6 @@ spec: value: "" - name: encodeBase64 value: "" - - name: direction - value: "output" ``` {{% alert title="Warning" color="warning" %}} @@ -72,7 +70,6 @@ The above example uses secrets as plain strings. It is recommended to use a secr | `client_x509_cert_url` | Y | Output | GCP credentials project x509 cert url | `https://www.googleapis.com/robot/v1/metadata/x509/.iam.gserviceaccount.com` | `decodeBase64` | N | Output | Configuration to decode base64 file content before saving to bucket storage. (In case of saving a file with binary content). `true` is the only allowed positive value. Other positive variations like `"True", "1"` are not acceptable. Defaults to `false` | `true`, `false` | | `encodeBase64` | N | Output | Configuration to encode base64 file content before return the content. (In case of opening a file with binary content). `true` is the only allowed positive value. Other positive variations like `"True", "1"` are not acceptable. Defaults to `false` | `true`, `false` | -| `direction` | N | Output | The direction of the binding. | `"output"` ## Binding support diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/graghql.md b/daprdocs/content/en/reference/components-reference/supported-bindings/graghql.md index 06ed28b0ae2..ee13e035470 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/graghql.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/graghql.md @@ -27,8 +27,6 @@ spec: value: "adminkey" - name: header:Cache-Control value: "no-cache" - - name: direction - value: "output" ``` {{% alert title="Warning" color="warning" %}} @@ -42,7 +40,6 @@ The above example uses secrets as plain strings. It is recommended to use a secr | `endpoint` | Y | Output | GraphQL endpoint string See [here](#url-format) for more details | `"http://localhost:4000/graphql/graphql"` | | `header:[HEADERKEY]` | N | Output | GraphQL header. Specify the header key in the `name`, and the header value in the `value`. | `"no-cache"` (see above) | | `variable:[VARIABLEKEY]` | N | Output | GraphQL query variable. Specify the variable name in the `name`, and the variable value in the `value`. | `"123"` (see below) | -| `direction` | N | Output | The direction of the binding | `"output"` | ### Endpoint and Header format diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/http.md b/daprdocs/content/en/reference/components-reference/supported-bindings/http.md index a724e30b93a..7f5f2972968 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/http.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/http.md @@ -40,8 +40,6 @@ spec: # key: "mytoken" #- name: securityTokenHeader # value: "Authorization: Bearer" # OPTIONAL
- #- name: direction - # value: "output" ``` ## Spec metadata fields @@ -56,7 +54,6 @@ spec: | `MTLSRenegotiation` | N | Output | Type of mTLS renegotiation to be used | `RenegotiateOnceAsClient` | `securityToken` | N | Output | The value of a token to be added to a HTTP request as a header. Used together with `securityTokenHeader` | | `securityTokenHeader` | N | Output | The name of the header for `securityToken` on a HTTP request | -| `direction` | N | Output |The direction of the binding | `"output"` ### How to configure mTLS-related fields in metadata diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/huawei-obs.md b/daprdocs/content/en/reference/components-reference/supported-bindings/huawei-obs.md index 5c4e063f7a8..41212384c0b 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/huawei-obs.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/huawei-obs.md @@ -30,8 +30,6 @@ spec: # optional fields - name: region value: "" - - name: direction - value: "" ``` {{% alert title="Warning" color="warning" %}} @@ -47,7 +45,6 @@ The above example uses secrets as plain strings. It is recommended to use a secr | `accessKey` | Y | Output | The Huawei Access Key (AK) to access this resource | `"************"` | | `secretKey` | Y | Output | The Huawei Secret Key (SK) to access this resource | `"************"` | | `region` | N | Output | The specific Huawei region of the bucket | `"cn-north-4"` | -| `direction` | N | Output | The direction of the binding | `"output"` | ## Binding support diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/influxdb.md b/daprdocs/content/en/reference/components-reference/supported-bindings/influxdb.md index f6fa6e45d47..13d355be930 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/influxdb.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/influxdb.md @@ -29,8 +29,6 @@ spec: value: "" - name: bucket # Required value: "" - - name: direction - value: "" ``` {{% alert title="Warning" color="warning" %}} @@ -45,7 +43,6 @@ The above example uses secrets as plain strings. It is recommended to use a secr | `token` | Y | Output | The authorization token for InfluxDB | `"mytoken"` | | `org` | Y | Output | The InfluxDB organization | `"myorg"` | | `bucket` | Y | Output | Bucket name to write to | `"mybucket"` | -| `direction` | N | Output | Direction of the binding | `"output"` | ## Binding support diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/kitex.md b/daprdocs/content/en/reference/components-reference/supported-bindings/kitex.md index ec4a19c1fb6..0c674f6bf4a 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/kitex.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/kitex.md @@ -34,13 +34,12 @@ spec: value: "echo" - name: version value: "0.5.0" - - name: direction - value: "output" ``` ## Spec metadata fields The `InvokeRequest.Metadata` for `bindings.kitex` requires the client to fill in four required items when making a call: + - `hostPorts` - `destService` - `methodName` @@ -52,8 +51,6 @@ The `InvokeRequest.Metadata` for `bindings.kitex` requires the client to fill in | `destService` | Y | Output | Service name of the Kitex server (Thrift) | `"echo"` | | `methodName` | Y | Output | Method name under a specific service name of the Kitex server (Thrift) | `"echo"` | | `version` | Y | Output | Kitex version | `"0.5.0"` | -| `direction` | N | Output | Direction of the binding | `"output"` | - ## Binding support diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/localstorage.md b/daprdocs/content/en/reference/components-reference/supported-bindings/localstorage.md index 5290a69c357..233783ff27b 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/localstorage.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/localstorage.md @@ -23,16 +23,13 @@ spec: metadata: - name: rootPath value: "" - - name: direction - value: "" ``` ## Spec metadata fields | Field | Required | Binding support | Details | Example | |--------------------|:--------:|--------|---------|---------| -| `rootPath` | Y | Input / Output | The root path anchor to which files can be read / saved | `"/temp/files"` | -| `direction` | N | Input / Output | The direction of the binding | `"output"` | +| `rootPath` | Y | Output | The root path anchor to which files can be read / saved | `"/temp/files"` | ## Binding support @@ -265,6 +262,5 @@ By default the Local Storage output binding auto generates a UUID as the file na - [Basic schema for a Dapr component]({{< ref component-schema >}}) - [Bindings building block]({{< ref bindings >}}) -- [How-To: Trigger application with input binding]({{< ref howto-triggers.md >}}) - [How-To: Use bindings to interface with external resources]({{< ref howto-bindings.md >}}) - [Bindings API reference]({{< ref bindings_api.md >}}) diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/mysql.md b/daprdocs/content/en/reference/components-reference/supported-bindings/mysql.md index 3c44b53a84c..f2c11899630 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/mysql.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/mysql.md @@ -36,8 +36,6 @@ spec: value: "" - name: connMaxIdleTime value: "" - - name: direction - value: "" ``` {{% alert title="Warning" color="warning" %}} @@ -55,7 +53,6 @@ Note that you can not use secret just for username/password. If you use secret, | `maxOpenConns` | N | Output | The max open connections. Integer greater than 0 | `"10"` | | `connMaxLifetime` | N | Output | The max connection lifetime. Duration string | `"12s"` | | `connMaxIdleTime` | N | Output | The max connection idel time. Duration string | `"12s"` | -| `direction` | N | Output | The direction of the binding | `"output"` | ### SSL connection @@ -192,6 +189,5 @@ The `close` operation can be used to explicitly close the DB connection and retu - [Basic schema for a Dapr component]({{< ref component-schema >}}) - [Bindings building block]({{< ref bindings >}}) -- [How-To: Trigger application with input binding]({{< ref howto-triggers.md >}}) - [How-To: Use bindings to interface with external resources]({{< ref howto-bindings.md >}}) - [Bindings API reference]({{< ref bindings_api.md >}}) diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/postgresql.md b/daprdocs/content/en/reference/components-reference/supported-bindings/postgresql.md index cfaf92ad37c..0a21d93b663 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/postgresql.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/postgresql.md @@ -25,8 +25,6 @@ spec: # Connection string - name: connectionString value: "" - - name: direction - value: "" ``` {{% alert title="Warning" color="warning" %}} diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/postmark.md b/daprdocs/content/en/reference/components-reference/supported-bindings/postmark.md index 03edb8db2ab..2dac3847b16 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/postmark.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/postmark.md @@ -30,8 +30,6 @@ spec: value: "dave@dapr.io" # optional - name: subject value: "Hello!" # optional - - name: direction - value: "output" # optional ``` {{% alert title="Warning" color="warning" %}} The above example uses secrets as plain strings. It is recommended to use a secret store for the secrets as described [here]({{< ref component-secrets.md >}}). @@ -48,7 +46,6 @@ The above example uses secrets as plain strings. It is recommended to use a secr | `emailCc` | N | Output | If set this specifies the 'cc' email address of the email message | `"me@example.com"` | | `emailBcc` | N | Output | If set this specifies the 'bcc' email address of the email message | `"me@example.com"` | | `subject` | N | Output | If set this specifies the subject of the email message | `"me@example.com"` | -| `direction` | N | Output | The direction of the binding | `"output"` | You can specify any of the optional metadata properties on the output binding request too (e.g. `emailFrom`, `emailTo`, `subject`, etc.) diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/redis.md b/daprdocs/content/en/reference/components-reference/supported-bindings/redis.md index 4b966a75e03..0f74473d38c 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/redis.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/redis.md @@ -27,8 +27,6 @@ spec: value: "**************" - name: enableTLS value: "" - - name: direction - value: "output" ``` {{% alert title="Warning" color="warning" %}} @@ -61,7 +59,6 @@ The above example uses secrets as plain strings. It is recommended to use a secr | `minIdleConns` | N | Output | Minimum number of idle connections to keep open in order to avoid the performance degradation associated with creating new connections. Defaults to `"0"`. | `"2"` | `idleCheckFrequency` | N | Output | Frequency of idle checks made by idle connections reaper. Default is `"1m"`. `"-1"` disables idle connections reaper. | `"-1"` | `idleTimeout` | N | Output | Amount of time after which the client closes idle connections. Should be less than server's timeout. Default is `"5m"`. `"-1"` disables idle timeout check. | `"10m"` -| `direction` | N | Output | Direction of the binding. | `"output"` ## Binding support @@ -226,6 +223,5 @@ The Dapr CLI automatically deploys a local redis instance in self hosted mode as - [Basic schema for a Dapr component]({{< ref component-schema >}}) - [Bindings building block]({{< ref bindings >}}) -- [How-To: Trigger application with input binding]({{< ref howto-triggers.md >}}) - [How-To: Use bindings to interface with external resources]({{< ref howto-bindings.md >}}) - [Bindings API reference]({{< ref bindings_api.md >}}) diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/s3.md b/daprdocs/content/en/reference/components-reference/supported-bindings/s3.md index 1c01459c3ba..7e7b93a8ecd 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/s3.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/s3.md @@ -44,8 +44,6 @@ spec: value: "" - name: insecureSSL value: "" - - name: direction - value: "output" ``` {{% alert title="Warning" color="warning" %}} @@ -67,7 +65,6 @@ The above example uses secrets as plain strings. It is recommended to use a secr | `encodeBase64` | N | Output | Configuration to encode base64 file content before return the content. (In case of opening a file with binary content). `"true"` is the only allowed positive value. Other positive variations like `"True", "1"` are not acceptable. Defaults to `"false"` | `"true"`, `"false"` | | `disableSSL` | N | Output | Allows to connect to non `https://` endpoints. Defaults to `"false"` | `"true"`, `"false"` | | `insecureSSL` | N | Output | When connecting to `https://` endpoints, accepts invalid or self-signed certificates. Defaults to `"false"` | `"true"`, `"false"` | -| `direction` | N | Output | The direction of the binding | `"output"` | {{% alert title="Important" color="warning" %}} When running the Dapr sidecar (daprd) with your application on EKS (AWS Kubernetes), if you're using a node/pod that has already been attached to an IAM policy defining access to AWS resources, you **must not** provide AWS access-key, secret-key, and tokens in the definition of the component spec you're using. diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/sendgrid.md b/daprdocs/content/en/reference/components-reference/supported-bindings/sendgrid.md index 64014018725..3c18f0fe395 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/sendgrid.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/sendgrid.md @@ -41,8 +41,6 @@ spec: value: '{"customer":{"name":"John Smith"}}' # optional - name: apiKey value: "YOUR_API_KEY" # required, this is your SendGrid key - - name: direction - value: "output" ``` {{% alert title="Warning" color="warning" %}} @@ -61,7 +59,6 @@ The above example uses secrets as plain strings. It is recommended to use a secr | `emailCc` | N | Output | If set this specifies the 'cc' email address of the email message. Only a single email address is allowed. Optional field, see [below](#example-request-payload) | `"me@example.com"` | | `emailBcc` | N | Output | If set this specifies the 'bcc' email address of the email message. Only a single email address is allowed. Optional field, see [below](#example-request-payload) | `"me@example.com"` | | `subject` | N | Output | If set this specifies the subject of the email message. Optional field, see [below](#example-request-payload) | `"subject of the email"` | -| `direction` | N | Output | The direction of the binding | `"output"` | ## Binding support diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/ses.md b/daprdocs/content/en/reference/components-reference/supported-bindings/ses.md index 7f63892fb41..696dd01b820 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/ses.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/ses.md @@ -40,8 +40,6 @@ spec: value: "bcc@example.com" - name: subject value: "subject" - - name: direction - value: "output" ``` {{% alert title="Warning" color="warning" %}} @@ -61,7 +59,6 @@ The above example uses secrets as plain strings. It is recommended to use a secr | `emailCc` | N | Output | If set, this specifies the email address to CC in. See [also](#example-request) | `"me@example.com"` | | `emailBcc` | N | Output | If set, this specifies email address to BCC in. See [also](#example-request) | `"me@example.com"` | | `subject` | N | Output | If set, this specifies the subject of the email message. See [also](#example-request) | `"subject of mail"` | -| `direction` | N | Output | The direction of the binding | `"output"` | {{% alert title="Important" color="warning" %}} When running the Dapr sidecar (daprd) with your application on EKS (AWS Kubernetes), if you're using a node/pod that has already been attached to an IAM policy defining access to AWS resources, you **must not** provide AWS access-key, secret-key, and tokens in the definition of the component spec you're using. diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/signalr.md b/daprdocs/content/en/reference/components-reference/supported-bindings/signalr.md index 2da23916d5d..560ed30fcc9 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/signalr.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/signalr.md @@ -25,8 +25,6 @@ spec: value: "Endpoint=https://.service.signalr.net;AccessKey=;Version=1.0;" - name: hub # Optional value: "" - - name: direction - value: "" ``` {{% alert title="Warning" color="warning" %}} @@ -41,7 +39,6 @@ The above example uses secrets as plain strings. It is recommended to use a secr | `hub` | N | Output | Defines the hub in which the message will be send. The hub can be dynamically defined as a metadata value when publishing to an output binding (key is "hub") | `"myhub"` | | `endpoint` | N | Output | Endpoint of Azure SignalR; required if not included in the `connectionString` or if using Azure AD | `"https://.service.signalr.net"` | `accessKey` | N | Output | Access key | `"your-access-key"` -| `direction` | N | Output | The direction of the binding | `"output"` ### Azure Active Directory (Azure AD) authentication diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/smtp.md b/daprdocs/content/en/reference/components-reference/supported-bindings/smtp.md index 67af19ba455..54879886c6d 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/smtp.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/smtp.md @@ -43,8 +43,6 @@ spec: value: "subject" - name: priority value: "[value 1-5]" - - name: direction - value: "output" ``` {{% alert title="Warning" color="warning" %}} @@ -66,7 +64,6 @@ The example configuration shown above, contain a username and password as plain- | `emailBcc` | N | Output | If set, this specifies email address to BCC in. See [also](#example-request) | `"me@example.com"` | | `subject` | N | Output | If set, this specifies the subject of the email message. See [also](#example-request) | `"subject of mail"` | | `priority` | N | Output | If set, this specifies the priority (X-Priority) of the email message, from 1 (lowest) to 5 (highest) (default value: 3). See [also](#example-request) | `"1"` | -| `direction` | N | Output | The direction of the binding | `"output"` | ## Binding support diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/sns.md b/daprdocs/content/en/reference/components-reference/supported-bindings/sns.md index dd6d704fbd2..6e2500aa6d4 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/sns.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/sns.md @@ -32,8 +32,6 @@ spec: value: "*****************" - name: sessionToken value: "*****************" - - name: direction - value: "output" ``` {{% alert title="Warning" color="warning" %}} @@ -49,7 +47,6 @@ The above example uses secrets as plain strings. It is recommended to use a secr | `accessKey` | Y | Output | The AWS Access Key to access this resource | `"key"` | | `secretKey` | Y | Output | The AWS Secret Access Key to access this resource | `"secretAccessKey"` | | `sessionToken` | N | Output | The AWS session token to use | `"sessionToken"` | -| `direction` | N | Output | The direction of the binding | `"output"` | {{% alert title="Important" color="warning" %}} When running the Dapr sidecar (daprd) with your application on EKS (AWS Kubernetes), if you're using a node/pod that has already been attached to an IAM policy defining access to AWS resources, you **must not** provide AWS access-key, secret-key, and tokens in the definition of the component spec you're using. diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/twilio.md b/daprdocs/content/en/reference/components-reference/supported-bindings/twilio.md index de30015c945..96c552e82ab 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/twilio.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/twilio.md @@ -11,8 +11,6 @@ aliases: To setup Twilio SMS binding create a component of type `bindings.twilio.sms`. See [this guide]({{< ref "howto-bindings.md#1-create-a-binding" >}}) on how to create and apply a binding configuration. - - ```yaml apiVersion: dapr.io/v1alpha1 kind: Component @@ -30,8 +28,6 @@ spec: value: "*****************" - name: authToken # required. value: "*****************" - - name: direction - value: "output" ``` {{% alert title="Warning" color="warning" %}} The above example uses secrets as plain strings. It is recommended to use a secret store for the secrets as described [here]({{< ref component-secrets.md >}}). @@ -45,7 +41,6 @@ The above example uses secrets as plain strings. It is recommended to use a secr | `fromNumber` | Y | Output | The sender phone number | `"222-222-2222"` | | `accountSid` | Y | Output | The Twilio account SID | `"account sid"` | | `authToken` | Y | Output | The Twilio auth token | `"auth token"` | -| `direction` | N | Output | The direction of the binding | `"output"` | ## Binding support diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/wasm.md b/daprdocs/content/en/reference/components-reference/supported-bindings/wasm.md index 4f1fd1bb63d..c2fb5468be5 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/wasm.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/wasm.md @@ -71,18 +71,15 @@ spec: type: bindings.wasm version: v1 metadata: - - name: url - value: "file://uppercase.wasm" - - name: direction - value: "output" + - name: url + value: "file://uppercase.wasm" ``` ## Spec metadata fields | Field | Details | Required | Example | |-------|----------------------------------------------------------------|----------|----------------| -| url | The URL of the resource including the Wasm binary to instantiate. The supported schemes include `file://`, `http://`, and `https://`. The path of a `file://` URL is relative to the Dapr process unless it begins with `/`. | true | `file://hello.wasm`, `https://example.com/hello.wasm` | -| `direction` | The direction of the binding | false | `"output"` | +| `url` | The URL of the resource including the Wasm binary to instantiate. The supported schemes include `file://`, `http://`, and `https://`. The path of a `file://` URL is relative to the Dapr process unless it begins with `/`. | true | `file://hello.wasm`, `https://example.com/hello.wasm` | ## Binding support diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/zeebe-command.md b/daprdocs/content/en/reference/components-reference/supported-bindings/zeebe-command.md index 6ea57ee1dff..780bfaebefe 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/zeebe-command.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/zeebe-command.md @@ -28,8 +28,6 @@ spec: value: "true" - name: caCertificatePath value: "/path/to/ca-cert" - - name: direction - value: "output" ``` ## Spec metadata fields @@ -40,7 +38,6 @@ spec: | `gatewayKeepAlive` | N | Output | Sets how often keep alive messages should be sent to the gateway. Defaults to 45 seconds | `"45s"` | | `usePlainTextConnection` | N | Output | Whether to use a plain text connection or not | `"true"`, `"false"` | | `caCertificatePath` | N | Output | The path to the CA cert | `"/path/to/ca-cert"` | -| `direction` | N | Output | The direction of the binding | `"output"` | ## Binding support diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/zeebe-jobworker.md b/daprdocs/content/en/reference/components-reference/supported-bindings/zeebe-jobworker.md index b84188090c4..7b2fba07d7f 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/zeebe-jobworker.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/zeebe-jobworker.md @@ -47,10 +47,10 @@ spec: - name: fetchVariables value: "productId, productName, productKey" - name: autocomplete - value: "true" + value: "true" - name: retryBackOff - value: "30s" - - name: direction + value: "30s" + - name: direction value: "input" ``` From 7045520e575c989bcf5450b6370bcbe371065313 Mon Sep 17 00:00:00 2001 From: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> Date: Thu, 12 Oct 2023 08:46:42 +0530 Subject: [PATCH 126/162] fix phrasing issues Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> --- .../building-blocks/pubsub/pubsub-bulk.md | 74 ++++++++----------- 1 file changed, 29 insertions(+), 45 deletions(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md b/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md index 3961d37570d..5228b5975e3 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md +++ b/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-bulk.md @@ -81,13 +81,13 @@ async function start() { { entryID: "entry-2", contentType: "application/cloudevents+json", - event: { + event: { specversion: "1.0", source: "/some/source", type: "example", - id: "1234", - data: "foo message 2", - datacontenttype: "text/plain" + id: "1234", + data: "foo message 2", + datacontenttype: "text/plain" }, }, { @@ -115,7 +115,7 @@ using System.Collections.Generic; using Dapr.Client; const string PubsubName = "my-pubsub-name"; -const string TopicName = "topic-a"; +const string TopicName = "topic-a"; IReadOnlyList BulkPublishData = new List() { new { Id = "17", Amount = 10m }, new { Id = "18", Amount = 20m }, @@ -130,10 +130,10 @@ if (res == null) { } if (res.FailedEntries.Count > 0) { - Console.WriteLine("Some events failed to be published!"); + Console.WriteLine("Some events failed to be published!"); foreach (var failedEntry in res.FailedEntries) { - Console.WriteLine("EntryId: " + failedEntry.Entry.EntryId + " Error message: " + + Console.WriteLine("EntryId: " + failedEntry.Entry.EntryId + " Error message: " + failedEntry.ErrorMessage); } } @@ -205,7 +205,7 @@ func main() { { "entryId": "b1f40bd6-4af2-11ed-b878-0242ac120002", "event": { - "message": "second JSON message" + "message": "second JSON message" }, "contentType": "application/json" } @@ -236,7 +236,7 @@ curl -X POST http://localhost:3500/v1.0-alpha1/publish/bulk/my-pubsub-name/topic { "entryId": "b1f40bd6-4af2-11ed-b878-0242ac120002", "event": { - "message": "second JSON message" + "message": "second JSON message" }, "contentType": "application/json" }, @@ -258,7 +258,7 @@ Invoke-RestMethod -Method Post -ContentType 'application/json' -Uri 'http://loca { "entryId": "b1f40bd6-4af2-11ed-b878-0242ac120002", "event": { - "message": "second JSON message" + "message": "second JSON message" }, "contentType": "application/json" }, @@ -271,7 +271,7 @@ Invoke-RestMethod -Method Post -ContentType 'application/json' -Uri 'http://loca ## Subscribing messages in bulk -The bulk subscribe API allows you to subscribe multiple messages from a topic in a single request. +The bulk subscribe API allows you to subscribe multiple messages from a topic in a single request. As we know from [How to: Publish & Subscribe to topics]({{< ref howto-publish-subscribe.md >}}), there are two ways to subscribe to topic(s): - **Declaratively** - subscriptions are defined in an external file. @@ -286,7 +286,7 @@ metadata: name: order-pub-sub spec: topic: orders - routes: + routes: default: /checkout pubsubname: order-pub-sub bulkSubscribe: @@ -300,11 +300,11 @@ scopes: In the example above, `bulkSubscribe` is _optional_. If you use `bulkSubscribe`, then: - `enabled` is mandatory and enables or disables bulk subscriptions on this topic -- You can optionally configure the max number of messages (`maxMessagesCount`) delivered in a bulk message. -Default value of `maxMessagesCount` for components not supporting bulk subscribe is 100 i.e. for default bulk events between App and Dapr. Please refer [How components handle publishing and subscribing to bulk messages]({{< ref pubsub-bulk >}}). +- You can optionally configure the max number of messages (`maxMessagesCount`) delivered in a bulk message. +Default value of `maxMessagesCount` for components not supporting bulk subscribe is 100 i.e. for default bulk events between App and Dapr. Please refer [How components handle publishing and subscribing to bulk messages]({{< ref pubsub-bulk >}}). If a component supports bulk subscribe, then default value for this parameter can be found in that component doc. - You can optionally provide the max duration to wait (`maxAwaitDurationMs`) before a bulk message is sent to the app. -Default value of `maxAwaitDurationMs` for components not supporting bulk subscribe is 1000 i.e. for default bulk events between App and Dapr. Please refer [How components handle publishing and subscribing to bulk messages]({{< ref pubsub-bulk >}}). +Default value of `maxAwaitDurationMs` for components not supporting bulk subscribe is 1000 i.e. for default bulk events between App and Dapr. Please refer [How components handle publishing and subscribing to bulk messages]({{< ref pubsub-bulk >}}). If a component supports bulk subscribe, then default value for this parameter can be found in that component doc. The application receives an `EntryId` associated with each entry (individual message) in the bulk message. This `EntryId` must be used by the app to communicate the status of that particular entry. If the app fails to notify on an `EntryId` status, it's considered a `RETRY`. @@ -313,16 +313,16 @@ A JSON-encoded payload body with the processing status against each entry needs ```json { - "statuses": - [ + "statuses": + [ { "entryId": "", "status": "" - }, + }, { "entryId": "", "status": "" - } + } ] } ``` @@ -477,37 +477,21 @@ For event publish/subscribe, two kinds of network transfers are involved. 1. From/To *App* To/From *Dapr*. 1. From/To *Dapr* To/From *Pubsub Broker*. -These are the opportunities where optimization is possible. When optimized, a Bulk requests are, which reduce number of overall calls and thus increase throughput and provide better latency. +These are the opportunities where optimization is possible. When optimized, Bulk requests are made, which reduce the overall number of calls and thus increases throughput and provides better latency. On enabling Bulk Publish and/or Bulk Subscribe, the communication between the App and Dapr sidecar (Point 1 above) is optimized for **all components**. -Optimization from Dapr sidecar to the pub/sub broker would depend on a number of factors, for example: -- If the broker inherently supports Bulk pub/sub -- If the Dapr component is updated to support the use of bulk APIs provided by the broker. +Optimization from Dapr sidecar to the pub/sub broker depends on a number of factors, for example: +- Broker must inherently support Bulk pub/sub +- The Dapr component must be updated to support the use of bulk APIs provided by the broker Currently, the following components are updated to support this level of optimization: -
Component
- - - - - - - - - - - - - - - - - - - - -
ComponentBulk PublishBulk Subscribe
KafkaYesYes
Azure ServicebusYesYes
Azure EventhubsYesYes
+ +| Component | Bulk Publish | Bulk Subscribe | +|:--------------------:|:--------:|--------| +| Kafka | Yes | Yes | +| Azure Servicebus | Yes | Yes | +| Azure Eventhubs | Yes | Yes | ## Demos From 2d593e6291882dfe7d4a67bb00bd82a9bf7cbbcb Mon Sep 17 00:00:00 2001 From: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> Date: Thu, 12 Oct 2023 08:57:28 -0700 Subject: [PATCH 127/162] fix supported versions (#3821) Signed-off-by: Mukundan Sundararajan <65565396+mukundansundar@users.noreply.github.com> --- .../support/support-release-policy.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/daprdocs/content/en/operations/support/support-release-policy.md b/daprdocs/content/en/operations/support/support-release-policy.md index 8dd71a45723..32ee3f1e7bf 100644 --- a/daprdocs/content/en/operations/support/support-release-policy.md +++ b/daprdocs/content/en/operations/support/support-release-policy.md @@ -45,7 +45,7 @@ The table below shows the versions of Dapr releases that have been tested togeth | Release date | Runtime | CLI | SDKs | Dashboard | Status | Release notes | |--------------------|:--------:|:--------|---------|---------|---------|------------| -| October 11th 2023 | 1.12.0
| 1.12.0 | Java 1.10.0
Go 1.9.0
PHP 1.1.0
Python 1.11.0
.NET 1.12.0
JS 3.1.2 | 0.13.0 | Supported (current) | [v1.12.0 release notes](https://github.com/dapr/dapr/releases/tag/v1.12.0) | +| October 11th 2023 | 1.12.0
| 1.12.0 | Java 1.10.0
Go 1.9.0
PHP 1.1.0
Python 1.11.0
.NET 1.12.0
JS 3.1.2 | 0.14.0 | Supported (current) | [v1.12.0 release notes](https://github.com/dapr/dapr/releases/tag/v1.12.0) | | August 31st 2023 | 1.11.3
| 1.11.0 | Java 1.9.0
Go 1.8.0
PHP 1.1.0
Python 1.10.0
.NET 1.11.0
JS 3.1.0 | 0.13.0 | Supported | [v1.11.3 release notes](https://github.com/dapr/dapr/releases/tag/v1.11.3) | | July 20th 2023 | 1.11.2
| 1.11.0 | Java 1.9.0
Go 1.8.0
PHP 1.1.0
Python 1.10.0
.NET 1.11.0
JS 3.1.0 | 0.13.0 | Supported | [v1.11.2 release notes](https://github.com/dapr/dapr/releases/tag/v1.11.2) | | June 22nd 2023 | 1.11.1
| 1.11.0 | Java 1.9.0
Go 1.8.0
PHP 1.1.0
Python 1.10.0
.NET 1.11.0
JS 3.1.0 | 0.13.0 | Supported | [v1.11.1 release notes](https://github.com/dapr/dapr/releases/tag/v1.11.1) | @@ -60,12 +60,12 @@ The table below shows the versions of Dapr releases that have been tested togeth | February 24 2023 | 1.10.2
| 1.10.0 | Java 1.8.0
Go 1.6.0
PHP 1.1.0
Python 1.9.0
.NET 1.10.0
JS 2.5.0 | 0.11.0 | Supported | | | February 20 2023 | 1.10.1
| 1.10.0 | Java 1.8.0
Go 1.6.0
PHP 1.1.0
Python 1.9.0
.NET 1.10.0
JS 2.5.0 | 0.11.0 | Supported | | | February 14 2023 | 1.10.0
| 1.10.0 | Java 1.8.0
Go 1.6.0
PHP 1.1.0
Python 1.9.0
.NET 1.10.0
JS 2.5.0 | 0.11.0 | Supported| | -| December 2nd 2022 | 1.9.5
| 1.9.1 | Java 1.7.0
Go 1.6.0
PHP 1.1.0
Python 1.8.3
.NET 1.9.0
JS 2.4.2 | 0.11.0 | Supported | | -| November 17th 2022 | 1.9.4
| 1.9.1 | Java 1.7.0
Go 1.6.0
PHP 1.1.0
Python 1.8.3
.NET 1.9.0
JS 2.4.2 | 0.11.0 | Supported | | -| November 4th 2022 | 1.9.3
| 1.9.1 | Java 1.7.0
Go 1.6.0
PHP 1.1.0
Python 1.8.3
.NET 1.9.0
JS 2.4.2 | 0.11.0 | Supported | | -| November 1st 2022 | 1.9.2
| 1.9.1 | Java 1.7.0
Go 1.6.0
PHP 1.1.0
Python 1.8.1
.NET 1.9.0
JS 2.4.2 | 0.11.0 | Supported | | -| October 26th 2022 | 1.9.1
| 1.9.1 | Java 1.7.0
Go 1.6.0
PHP 1.1.0
Python 1.8.1
.NET 1.9.0
JS 2.4.2 | 0.11.0 | Supported | | -| October 13th 2022 | 1.9.0
| 1.9.1 | Java 1.7.0
Go 1.6.0
PHP 1.1.0
Python 1.8.3
.NET 1.9.0
JS 2.4.2 | 0.11.0 | Supported | | +| December 2nd 2022 | 1.9.5
| 1.9.1 | Java 1.7.0
Go 1.6.0
PHP 1.1.0
Python 1.8.3
.NET 1.9.0
JS 2.4.2 | 0.11.0 | Unsupported | | +| November 17th 2022 | 1.9.4
| 1.9.1 | Java 1.7.0
Go 1.6.0
PHP 1.1.0
Python 1.8.3
.NET 1.9.0
JS 2.4.2 | 0.11.0 | Unsupported | | +| November 4th 2022 | 1.9.3
| 1.9.1 | Java 1.7.0
Go 1.6.0
PHP 1.1.0
Python 1.8.3
.NET 1.9.0
JS 2.4.2 | 0.11.0 | Unsupported | | +| November 1st 2022 | 1.9.2
| 1.9.1 | Java 1.7.0
Go 1.6.0
PHP 1.1.0
Python 1.8.1
.NET 1.9.0
JS 2.4.2 | 0.11.0 | Unsupported | | +| October 26th 2022 | 1.9.1
| 1.9.1 | Java 1.7.0
Go 1.6.0
PHP 1.1.0
Python 1.8.1
.NET 1.9.0
JS 2.4.2 | 0.11.0 | Unsupported | | +| October 13th 2022 | 1.9.0
| 1.9.1 | Java 1.7.0
Go 1.6.0
PHP 1.1.0
Python 1.8.3
.NET 1.9.0
JS 2.4.2 | 0.11.0 | Unsupported | | | October 26th 2022 | 1.8.6
| 1.8.1 | Java 1.6.0
Go 1.5.0
PHP 1.1.0
Python 1.7.0
.NET 1.8.0
JS 2.3.0 | 0.11.0 | Unsupported | | | October 13th 2022 | 1.8.5
| 1.8.1 | Java 1.6.0
Go 1.5.0
PHP 1.1.0
Python 1.7.0
.NET 1.8.0
JS 2.3.0 | 0.11.0 | Unsupported | | | August 10th 2022 | 1.8.4
| 1.8.1 | Java 1.6.0
Go 1.5.0
PHP 1.1.0
Python 1.7.0
.NET 1.8.0
JS 2.3.0 | 0.11.0 | Unsupported | | @@ -121,9 +121,9 @@ General guidance on upgrading can be found for [self hosted mode]({{< ref self-h Dapr can support multiple hosting platforms for production. With the 1.0 release the two supported platforms are Kubernetes and physical machines. For Kubernetes upgrades see [Production guidelines on Kubernetes]({{< ref kubernetes-production.md >}}) -### Supported versions of dependencies +### Supported versions of dependencies -Below is a list of software that the latest version of Dapr (v{{% dapr-latest-version long="true" %}}) has been tested against. +Below is a list of software that the latest version of Dapr (v{{% dapr-latest-version long="true" %}}) has been tested against. | Dependency | Supported Version | |-----------------------|----------------------------------------------------------------------------------------------------------------------------------| From c6ceb6122fe9eee9ae1799b08cd1ec96840e3902 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Thu, 12 Oct 2023 14:36:25 -0400 Subject: [PATCH 128/162] clarify limitations Signed-off-by: Hannah Hunter --- .../building-blocks/workflow/workflow-overview.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md index 923bc37947e..7aece0f3b23 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md @@ -109,8 +109,9 @@ Want to skip the quickstarts? Not a problem. You can try out the workflow buildi With Dapr Workflow in beta stage comes the following limitation(s): -- **State stores:** For the {{% dapr-latest-version cli="true" %}} beta release of Dapr Workflow, you're not able to use NoSQL databases. Only SQL databases are supported in the latest release. -- **Application instances:** For the {{% dapr-latest-version cli="true" %}} beta release of Dapr Workflow, only a maximum of 2 application instances is supported. +- **State stores:** For the {{% dapr-latest-version cli="true" %}} beta release of Dapr Workflow, using the NoSQL databases as a state store results in limitations around storing internal states. For example, CosmosDB has a maximum single operation item limit of only 100 states in a single request. + +- **Horizontal scaling:** For the {{% dapr-latest-version cli="true" %}} beta release of Dapr Workflow, if you scale out Dapr sidecars or your application pods to more than 2, then the concurrency of the workflow execution drops. It is recommended to test with 1 or 2 instances, and no more than 2. ## Watch the demo From 0ffc2e74331def666403d46c4e7d84d613a25bec Mon Sep 17 00:00:00 2001 From: greenie-msft <56556602+greenie-msft@users.noreply.github.com> Date: Thu, 12 Oct 2023 14:15:42 -0700 Subject: [PATCH 129/162] Update config.toml (#3826) Signed-off-by: greenie-msft <56556602+greenie-msft@users.noreply.github.com> --- daprdocs/config.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/config.toml b/daprdocs/config.toml index 5b500e5e12f..3aa6d95f85a 100644 --- a/daprdocs/config.toml +++ b/daprdocs/config.toml @@ -181,7 +181,7 @@ url_latest_version = "https://docs.dapr.io" url = "https://v1-13.docs.dapr.io" [[params.versions]] version = "v1.12 (latest)" - url = "https://docs.dapr.io" + url = "#" [[params.versions]] version = "v1.11" url = "https://v1-11.docs.dapr.io" From 7891547798881e89250905f4fdf1114b6ac82a34 Mon Sep 17 00:00:00 2001 From: Florian van Dillen Date: Mon, 16 Oct 2023 18:26:15 +0200 Subject: [PATCH 130/162] Fix typo in secret-scope.md (#3831) Fix typo in secret-scope.md Signed-off-by: Florian van Dillen --- daprdocs/content/en/operations/configuration/secret-scope.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/operations/configuration/secret-scope.md b/daprdocs/content/en/operations/configuration/secret-scope.md index 37ba0ff1873..b31d5e9fd3c 100644 --- a/daprdocs/content/en/operations/configuration/secret-scope.md +++ b/daprdocs/content/en/operations/configuration/secret-scope.md @@ -44,7 +44,7 @@ The `allowedSecrets` and `deniedSecrets` list values take priorty over the `defa |----- | ------- | -----------| ----------| ------------ | 1 - Only default access | deny/allow | empty | empty | deny/allow | 2 - Default deny with allowed list | deny | ["s1"] | empty | only "s1" can be accessed -| 3 - Default allow with deneied list | allow | empty | ["s1"] | only "s1" cannot be accessed +| 3 - Default allow with denied list | allow | empty | ["s1"] | only "s1" cannot be accessed | 4 - Default allow with allowed list | allow | ["s1"] | empty | only "s1" can be accessed | 5 - Default deny with denied list | deny | empty | ["s1"] | deny | 6 - Default deny/allow with both lists | deny/allow | ["s1"] | ["s2"] | only "s1" can be accessed From 718ea0a2697af524225f88926fa5cb269d9e9e5f Mon Sep 17 00:00:00 2001 From: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Date: Mon, 16 Oct 2023 19:54:25 -0400 Subject: [PATCH 131/162] remove section (#3832) Signed-off-by: Hannah Hunter --- .../building-blocks/workflow/workflow-overview.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md index 923bc37947e..8bf36ac3ea9 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-overview.md @@ -63,10 +63,6 @@ When you create an application with workflow code and run it with Dapr, you can [Learn more about how manage a workflow using HTTP calls.]({{< ref workflow_api.md >}}) -### Manage other workflow runtimes with workflow components - -You can call other workflow runtimes (for example, Temporal and Netflix Conductor) by writing your own workflow component. - ## Workflow patterns Dapr Workflow simplifies complex, stateful coordination requirements in microservice architectures. The following sections describe several application patterns that can benefit from Dapr Workflow. From e83b4d3158d6a24ca779e30e915e270544d538c1 Mon Sep 17 00:00:00 2001 From: frodera <219724+frodera@users.noreply.github.com> Date: Tue, 17 Oct 2023 16:04:14 +1100 Subject: [PATCH 132/162] Update setup-nats-streaming.md (#3833) Signed-off-by: frodera <219724+frodera@users.noreply.github.com> --- .../supported-pubsub/setup-nats-streaming.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-nats-streaming.md b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-nats-streaming.md index 3a2f9d21972..6f997f2b32a 100644 --- a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-nats-streaming.md +++ b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-nats-streaming.md @@ -10,9 +10,9 @@ aliases: ## ⚠️ Deprecation notice {{% alert title="Warning" color="warning" %}} -This component is **deprecated** because the [NATS Streaming Server](hhttps://nats-io.gitbook.io/legacy-nats-docs/nats-streaming-server-aka-stan/developing-with-stan) was deprecated in June 2023 and no longer receives updates. Users are encouraged to switch to using [JetStream]({{< ref setup-jetstream >}} as an alternative. +This component is **deprecated** because the [NATS Streaming Server](https://nats-io.gitbook.io/legacy-nats-docs/nats-streaming-server-aka-stan/developing-with-stan) was deprecated in June 2023 and no longer receives updates. Users are encouraged to switch to using [JetStream]({{< ref setup-jetstream >}}) as an alternative. -This component will be **removed in the Dapr v1.13 release. +This component will be **removed in the Dapr v1.13 release**. {{% /alert %}} ## Component format From 8ba23898c498e74b29ba473c2d1a87f333a73f12 Mon Sep 17 00:00:00 2001 From: Shivam Kumar Date: Tue, 17 Oct 2023 13:57:01 +0530 Subject: [PATCH 133/162] fix typo Signed-off-by: Shivam Kumar --- .../postgresql-configuration-store.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-configuration-stores/postgresql-configuration-store.md b/daprdocs/content/en/reference/components-reference/supported-configuration-stores/postgresql-configuration-store.md index 15fa476ae00..b9bc3de8328 100644 --- a/daprdocs/content/en/reference/components-reference/supported-configuration-stores/postgresql-configuration-store.md +++ b/daprdocs/content/en/reference/components-reference/supported-configuration-stores/postgresql-configuration-store.md @@ -112,7 +112,7 @@ Authenticating with Azure AD is supported with Azure Database for PostgreSQL. Al 3. Create a TRIGGER on configuration table. An example function to create a TRIGGER is as follows: ```sh - CREATE OR REPLACE FUNCTION configuration_event() RETURNS TRIGGER AS $$ + CREATE OR REPLACE FUNCTION notify_event() RETURNS TRIGGER AS $$ DECLARE data json; notification json; From ed12d04a877322c73d777b48be584bf7e90d2f8f Mon Sep 17 00:00:00 2001 From: Francesco Belacca Date: Wed, 18 Oct 2023 09:41:11 +0200 Subject: [PATCH 134/162] Fix embed youtube URL in howto-outbox.md Signed-off-by: Francesco Belacca --- .../building-blocks/state-management/howto-outbox.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md b/daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md index c53930f2acd..9f26c496b88 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md +++ b/daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md @@ -109,4 +109,4 @@ spec: Watch [this video for an overview of the outbox pattern](https://youtu.be/rTovKpG0rhY?t=1338):
- + From 501867e82eda402f40b923dfab0a48dd21d287b0 Mon Sep 17 00:00:00 2001 From: Francesco Belacca Date: Wed, 18 Oct 2023 16:17:46 +0200 Subject: [PATCH 135/162] Update daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md Co-authored-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Signed-off-by: Francesco Belacca --- .../building-blocks/state-management/howto-outbox.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md b/daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md index 9f26c496b88..2831802729a 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md +++ b/daprdocs/content/en/developing-applications/building-blocks/state-management/howto-outbox.md @@ -109,4 +109,4 @@ spec: Watch [this video for an overview of the outbox pattern](https://youtu.be/rTovKpG0rhY?t=1338):
- + From e82d51c2caf2016fc4bea29853a769292881f1f9 Mon Sep 17 00:00:00 2001 From: Oliver Tomlinson Date: Thu, 19 Oct 2023 14:37:50 +0100 Subject: [PATCH 136/162] update to use the default kafka version to avoid uneccessary errors (#3837) updated kafka pubsub component docs Signed-off-by: Oliver Tomlinson --- .../reference/components-reference/supported-bindings/kafka.md | 2 +- .../components-reference/supported-pubsub/setup-apache-kafka.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/kafka.md b/daprdocs/content/en/reference/components-reference/supported-bindings/kafka.md index 7029b546bd8..43f030799ce 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/kafka.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/kafka.md @@ -46,7 +46,7 @@ spec: - name: maxMessageBytes # Optional. value: "1024" - name: version # Optional. - value: "1.0.0" + value: "2.0.0" - name: direction value: "input, output" ``` diff --git a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-apache-kafka.md b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-apache-kafka.md index 431a7bc5406..05ff835208e 100644 --- a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-apache-kafka.md +++ b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-apache-kafka.md @@ -46,7 +46,7 @@ spec: - name: consumeRetryInterval # Optional. value: 200ms - name: version # Optional. - value: 0.10.2.0 + value: 2.0.0 - name: disableTls # Optional. Disable TLS. This is not safe for production!! You should read the `Mutual TLS` section for how to use TLS. value: "true" ``` From 2ed23abb54c8c81d6e15c95de31370aa45c38c24 Mon Sep 17 00:00:00 2001 From: Andreas Eriksson Date: Fri, 20 Oct 2023 08:52:44 +0200 Subject: [PATCH 137/162] Update security-concept.md Clarified listening addresses for dapr sidecar. Signed-off-by: Andreas Eriksson --- daprdocs/content/en/concepts/security-concept.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/concepts/security-concept.md b/daprdocs/content/en/concepts/security-concept.md index d7ceef443b4..64667e34d4b 100644 --- a/daprdocs/content/en/concepts/security-concept.md +++ b/daprdocs/content/en/concepts/security-concept.md @@ -81,7 +81,7 @@ The diagram below shows how the Sentry system service issues certificates for ap ### Preventing IP addresses on Dapr -To prevent Dapr sidecars from being called on any IP address (especially in production environments such as Kubernetes), Dapr restricts its listening IP addresses only to `localhost`. Use the [dapr-listen-addresses]({{}}) setting you need to enable other addresses. +To prevent Dapr sidecars from being called on any IP address (especially in production environments such as Kubernetes), Dapr restricts its listening IP addresses to `localhost`. Use the [dapr-listen-addresses]({{}}) setting if you need to enable access from external addresses. ## Secure Dapr to application communication From 1abd41b428806b4cfa2083715041cb80067481ca Mon Sep 17 00:00:00 2001 From: Susheel Thapa Date: Fri, 20 Oct 2023 21:05:37 +0545 Subject: [PATCH 138/162] Fix typos in multiple files in dapr-docs (#3839) * Chore: Typo fixed in multiple files * Typo fixed * Typo fixed(Telementry) * Update pubsub-cloudevents.md Signed-off-by: Susheel Thapa --------- Signed-off-by: Susheel Thapa --- .../building-blocks/actors/actors-runtime-config.md | 2 +- .../building-blocks/actors/howto-actors-partitioning.md | 2 +- .../building-blocks/actors/howto-actors.md | 2 +- .../building-blocks/cryptography/cryptography-overview.md | 2 +- .../building-blocks/pubsub/pubsub-cloudevents.md | 2 +- .../building-blocks/pubsub/pubsub-overview.md | 2 +- .../ides/vscode/vscode-remote-dev-containers.md | 2 +- .../local-development/multi-app-dapr-run/multi-app-template.md | 2 +- .../operations/components/pluggable-components-registration.md | 2 +- .../content/en/operations/configuration/control-concurrency.md | 2 +- daprdocs/content/en/operations/configuration/secret-scope.md | 2 +- .../performance-and-scalability/perf-service-invocation.md | 2 +- daprdocs/content/en/operations/security/api-token.md | 2 +- daprdocs/content/en/operations/security/app-api-token.md | 2 +- daprdocs/content/en/operations/security/mtls.md | 2 +- .../content/en/operations/support/support-security-issues.md | 2 +- .../en/reference/cli/dapr-mtls/dapr-mtls-renew-certificate.md | 2 +- daprdocs/content/en/reference/cli/dapr-stop.md | 2 +- .../reference/components-reference/supported-bindings/mysql.md | 2 +- .../components-reference/supported-middleware/middleware-opa.md | 2 +- .../supported-middleware/middleware-wasm.md | 2 +- .../components-reference/supported-pubsub/setup-aws-snssqs.md | 2 +- .../supported-secret-stores/envvar-secret-store.md | 2 +- 23 files changed, 23 insertions(+), 23 deletions(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/actors/actors-runtime-config.md b/daprdocs/content/en/developing-applications/building-blocks/actors/actors-runtime-config.md index ce7008c1962..99b08040217 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/actors/actors-runtime-config.md +++ b/daprdocs/content/en/developing-applications/building-blocks/actors/actors-runtime-config.md @@ -59,7 +59,7 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(); } ``` -[See the .NET SDK documentation on registring actors]({{< ref "dotnet-actors-usage.md#registring-actors" >}}). +[See the .NET SDK documentation on registering actors]({{< ref "dotnet-actors-usage.md#registring-actors" >}}). {{% /codetab %}} diff --git a/daprdocs/content/en/developing-applications/building-blocks/actors/howto-actors-partitioning.md b/daprdocs/content/en/developing-applications/building-blocks/actors/howto-actors-partitioning.md index ad3473d9093..0d4017096e9 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/actors/howto-actors-partitioning.md +++ b/daprdocs/content/en/developing-applications/building-blocks/actors/howto-actors-partitioning.md @@ -57,7 +57,7 @@ public void ConfigureServices(IServiceCollection services) } ``` -[See the .NET SDK documentation on registring actors]({{< ref "dotnet-actors-usage.md#registring-actors" >}}). +[See the .NET SDK documentation on registering actors]({{< ref "dotnet-actors-usage.md#registring-actors" >}}). {{% /codetab %}} diff --git a/daprdocs/content/en/developing-applications/building-blocks/actors/howto-actors.md b/daprdocs/content/en/developing-applications/building-blocks/actors/howto-actors.md index 9a309f26604..16c7bbf4383 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/actors/howto-actors.md +++ b/daprdocs/content/en/developing-applications/building-blocks/actors/howto-actors.md @@ -26,7 +26,7 @@ Alternatively, you can use [Dapr SDKs to use actors]({{< ref "developing-applica ## Save state with actors -You can interact with Dapr via HTTP/gRPC endpoints to save state reliably using the Dapr actor state mangement capabaility. +You can interact with Dapr via HTTP/gRPC endpoints to save state reliably using the Dapr actor state management capabaility. To use actors, your state store must support multi-item transactions. This means your state store component must implement the `TransactionalStore` interface. diff --git a/daprdocs/content/en/developing-applications/building-blocks/cryptography/cryptography-overview.md b/daprdocs/content/en/developing-applications/building-blocks/cryptography/cryptography-overview.md index 79792f588cc..48e582a3c37 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/cryptography/cryptography-overview.md +++ b/daprdocs/content/en/developing-applications/building-blocks/cryptography/cryptography-overview.md @@ -45,7 +45,7 @@ While both HTTP and gRPC are supported in the alpha release, using the gRPC APIs ### Cryptographic components -The Dapr cryptography building block incldues two kinds of components: +The Dapr cryptography building block includes two kinds of components: - **Components that allow interacting with management services or vaults ("key vaults").** Similar to how Dapr offers an "abstraction layer" on top of various secret stores or state stores, these components allow interacting with various key vaults such as Azure Key Vault (with more coming in future Dapr releases). With these components, cryptographic operations on the private keys are performed within the vaults and Dapr never sees your private keys. diff --git a/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-cloudevents.md b/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-cloudevents.md index 47c115ef6c2..ca14d145eae 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-cloudevents.md +++ b/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-cloudevents.md @@ -160,7 +160,7 @@ The JSON payload then reflects the new `source` and `id` values: ``` {{% alert title="Important" color="warning" %}} -While you can replace `traceid`/`traceparent` and `tracestate`, doing this may interfere with tracing events and report inconsistent results in tracing tools. It's recommended to use Open Telementry for distributed traces. [Learn more about distributed tracing.]({{< ref tracing-overview.md >}}) +While you can replace `traceid`/`traceparent` and `tracestate`, doing this may interfere with tracing events and report inconsistent results in tracing tools. It's recommended to use Open Telemetry for distributed traces. [Learn more about distributed tracing.]({{< ref tracing-overview.md >}}) {{% /alert %}} diff --git a/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-overview.md b/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-overview.md index ed6b72cc38d..041dcec8b82 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-overview.md +++ b/daprdocs/content/en/developing-applications/building-blocks/pubsub/pubsub-overview.md @@ -114,7 +114,7 @@ All Dapr pub/sub components support the at-least-once guarantee. ### Consumer groups and competing consumers pattern -Dapr handles the burden of dealing with consumer groups and the competing consumers pattern. In the competing consumers pattern, multiple application instances using a single consumer group compete for the message. Dapr enforces the competing consumer pattern when replicas use the same `app-id` without explict consumer group overrides. +Dapr handles the burden of dealing with consumer groups and the competing consumers pattern. In the competing consumers pattern, multiple application instances using a single consumer group compete for the message. Dapr enforces the competing consumer pattern when replicas use the same `app-id` without explicit consumer group overrides. When multiple instances of the same application (with same `app-id`) subscribe to a topic, Dapr delivers each message to *only one instance of **that** application*. This concept is illustrated in the diagram below. diff --git a/daprdocs/content/en/developing-applications/local-development/ides/vscode/vscode-remote-dev-containers.md b/daprdocs/content/en/developing-applications/local-development/ides/vscode/vscode-remote-dev-containers.md index 952fdb9ed38..d39a45aeb16 100644 --- a/daprdocs/content/en/developing-applications/local-development/ides/vscode/vscode-remote-dev-containers.md +++ b/daprdocs/content/en/developing-applications/local-development/ides/vscode/vscode-remote-dev-containers.md @@ -45,7 +45,7 @@ dapr init #### Example: create a Java Dev Container for Dapr -This is an exmaple of creating a Dev Container for creating Java apps that use Dapr, based on the [official Java 17 Dev Container image](https://github.com/devcontainers/images/tree/main/src/java). +This is an example of creating a Dev Container for creating Java apps that use Dapr, based on the [official Java 17 Dev Container image](https://github.com/devcontainers/images/tree/main/src/java). Place this in a file called `.devcontainer/devcontainer.json` in your project: diff --git a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md index 350ef0f4219..67183f7af6b 100644 --- a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md +++ b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md @@ -14,7 +14,7 @@ The Multi-App Run template file is a YAML file that you can use to run multiple - Use the multi-app template - View started applications - Stop the multi-app template -- Stucture the multi-app template file +- Structure the multi-app template file ## Use the multi-app template diff --git a/daprdocs/content/en/operations/components/pluggable-components-registration.md b/daprdocs/content/en/operations/components/pluggable-components-registration.md index dda3e302005..10f6a057715 100644 --- a/daprdocs/content/en/operations/components/pluggable-components-registration.md +++ b/daprdocs/content/en/operations/components/pluggable-components-registration.md @@ -25,7 +25,7 @@ While Dapr's built-in components come [included with the runtime](https://github 1. Pluggable components need to be started and ready to take requests _before_ Dapr itself is started. 2. The [Unix Domain Socket][uds] file used for the pluggable component communication need to be made accessible to both Dapr and pluggable component. -In standalone mode, pluggable components run as processes or containers. On Kubernetes, pluggable components run as containers and are automatically injected to the application's pod by Dapr's sidecar injector, allowing customization via the standard [Kubernets Container spec](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#container-v1-core). +In standalone mode, pluggable components run as processes or containers. On Kubernetes, pluggable components run as containers and are automatically injected to the application's pod by Dapr's sidecar injector, allowing customization via the standard [Kubernetes Container spec](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#container-v1-core). This also changes the approach to share [Unix Domain Socket][uds] files between Dapr and pluggable components. diff --git a/daprdocs/content/en/operations/configuration/control-concurrency.md b/daprdocs/content/en/operations/configuration/control-concurrency.md index 7633820d6c9..85b240c19b5 100644 --- a/daprdocs/content/en/operations/configuration/control-concurrency.md +++ b/daprdocs/content/en/operations/configuration/control-concurrency.md @@ -11,7 +11,7 @@ Using Dapr, you can control how many requests and events will invoke your applic *Note that this rate limiting is guaranteed for every event that's coming from Dapr, meaning Pub/Sub events, direct invocation from other services, bindings events etc. Dapr can't enforce the concurrency policy on requests that are coming to your app externally.* -*Note that rate limiting per second can be achieved by using the **middleware.http.ratelimit** middleware. However, there is an imporant difference between the two approaches. The rate limit middlware is time bound and limits the number of requests per second, while the `app-max-concurrency` flag specifies the number of concurrent requests (and events) at any point of time. See [Rate limit middleware]({{< ref middleware-rate-limit.md >}}). * +*Note that rate limiting per second can be achieved by using the **middleware.http.ratelimit** middleware. However, there is an important difference between the two approaches. The rate limit middleware is time bound and limits the number of requests per second, while the `app-max-concurrency` flag specifies the number of concurrent requests (and events) at any point of time. See [Rate limit middleware]({{< ref middleware-rate-limit.md >}}). * Watch this [video](https://youtu.be/yRI5g6o_jp8?t=1710) on how to control concurrency and rate limiting ". diff --git a/daprdocs/content/en/operations/configuration/secret-scope.md b/daprdocs/content/en/operations/configuration/secret-scope.md index b31d5e9fd3c..39796447268 100644 --- a/daprdocs/content/en/operations/configuration/secret-scope.md +++ b/daprdocs/content/en/operations/configuration/secret-scope.md @@ -69,7 +69,7 @@ spec: defaultAccess: deny ``` -For applications that need to be deined access to the Kubernetes secret store, follow [these instructions]({{< ref kubernetes-overview >}}), and add the following annotation to the application pod. +For applications that need to be denied access to the Kubernetes secret store, follow [these instructions]({{< ref kubernetes-overview >}}), and add the following annotation to the application pod. ```yaml dapr.io/config: appconfig diff --git a/daprdocs/content/en/operations/performance-and-scalability/perf-service-invocation.md b/daprdocs/content/en/operations/performance-and-scalability/perf-service-invocation.md index 6246f346037..3b201e56ecd 100644 --- a/daprdocs/content/en/operations/performance-and-scalability/perf-service-invocation.md +++ b/daprdocs/content/en/operations/performance-and-scalability/perf-service-invocation.md @@ -54,7 +54,7 @@ The baseline test included direct, non-encrypted traffic, without telemetry, dir ### Control plane performance -The Dapr control plane uses a total of 0.009 vCPU and 61.6 Mb when running in non-HA mode, meaning a single replica per system compoment. +The Dapr control plane uses a total of 0.009 vCPU and 61.6 Mb when running in non-HA mode, meaning a single replica per system component. When running in a highly available production setup, the Dapr control plane consumes ~0.02 vCPU and 185 Mb. | Component | vCPU | Memory diff --git a/daprdocs/content/en/operations/security/api-token.md b/daprdocs/content/en/operations/security/api-token.md index 4435dcff463..aa30b39750e 100644 --- a/daprdocs/content/en/operations/security/api-token.md +++ b/daprdocs/content/en/operations/security/api-token.md @@ -60,7 +60,7 @@ To rotate the configured token in self-hosted, update the `DAPR_API_TOKEN` envir ### Kubernetes -To rotate the configured token in Kubernates, update the previously-created secret with the new token in each namespace. You can do that using `kubectl patch` command, but a simpler way to update these in each namespace is by using a manifest: +To rotate the configured token in Kubernetes, update the previously-created secret with the new token in each namespace. You can do that using `kubectl patch` command, but a simpler way to update these in each namespace is by using a manifest: ```yaml apiVersion: v1 diff --git a/daprdocs/content/en/operations/security/app-api-token.md b/daprdocs/content/en/operations/security/app-api-token.md index 3ab926a96af..d94e325139f 100644 --- a/daprdocs/content/en/operations/security/app-api-token.md +++ b/daprdocs/content/en/operations/security/app-api-token.md @@ -61,7 +61,7 @@ To rotate the configured token in self-hosted, update the `APP_API_TOKEN` enviro ### Kubernetes -To rotate the configured token in Kubernates, update the previously-created secret with the new token in each namespace. You can do that using `kubectl patch` command, but a simpler way to update these in each namespace is by using a manifest: +To rotate the configured token in Kubernetes, update the previously-created secret with the new token in each namespace. You can do that using `kubectl patch` command, but a simpler way to update these in each namespace is by using a manifest: ```yaml apiVersion: v1 diff --git a/daprdocs/content/en/operations/security/mtls.md b/daprdocs/content/en/operations/security/mtls.md index f0c3e1a6613..6868b622285 100644 --- a/daprdocs/content/en/operations/security/mtls.md +++ b/daprdocs/content/en/operations/security/mtls.md @@ -486,7 +486,7 @@ By default, system services will look for the credentials in `/var/run/dapr/cred *Note: If you signed the cert root with a different private key, restart the Dapr instances.* ## Community call video on certificate rotation -Watch this [video](https://www.youtube.com/watch?v=Hkcx9kBDrAc&feature=youtu.be&t=1400) on how to perform certificate rotation if your certicates are expiring. +Watch this [video](https://www.youtube.com/watch?v=Hkcx9kBDrAc&feature=youtu.be&t=1400) on how to perform certificate rotation if your certificates are expiring.
diff --git a/daprdocs/content/en/operations/support/support-security-issues.md b/daprdocs/content/en/operations/support/support-security-issues.md index f11b1e75679..c33ce0b167c 100644 --- a/daprdocs/content/en/operations/support/support-security-issues.md +++ b/daprdocs/content/en/operations/support/support-security-issues.md @@ -12,4 +12,4 @@ The Dapr organization and team makes security a central focus of how we operate To report a security issue, please privately email the [Dapr Maintainers (dapr@dapr.io)](mailto:dapr@dapr.io?subject=[Security%20Disclosure]:%20ISSUE%20TITLE) -The Dapr maintainers will triage and respond ASAP and then patch and send an annoucement within 30 days. +The Dapr maintainers will triage and respond ASAP and then patch and send an announcement within 30 days. diff --git a/daprdocs/content/en/reference/cli/dapr-mtls/dapr-mtls-renew-certificate.md b/daprdocs/content/en/reference/cli/dapr-mtls/dapr-mtls-renew-certificate.md index 71b154f7b45..8941c3ea3a7 100644 --- a/daprdocs/content/en/reference/cli/dapr-mtls/dapr-mtls-renew-certificate.md +++ b/daprdocs/content/en/reference/cli/dapr-mtls/dapr-mtls-renew-certificate.md @@ -24,7 +24,7 @@ dapr mtls renew-certificate [flags] | Name | Environment Variable | Default | Description | | -------------- | -------------------- | ----------------- | ------------------------------------------- | | `--help`, `-h` | | | help for renew-certificate -| `--kubernetes`, `-k` | | `false` | supprted platform| | +| `--kubernetes`, `-k` | | `false` | supported platform| | | `--valid-until` | | 365 days | Validity for newly created certificates | | `--restart` | | false | Restarts Dapr control plane services (Sentry service, Operator service and Placement server) | | `--timeout` | | 300 sec | The timeout for the certificate renewal process | diff --git a/daprdocs/content/en/reference/cli/dapr-stop.md b/daprdocs/content/en/reference/cli/dapr-stop.md index 0bb20213fa6..ef7acd50fb2 100644 --- a/daprdocs/content/en/reference/cli/dapr-stop.md +++ b/daprdocs/content/en/reference/cli/dapr-stop.md @@ -25,7 +25,7 @@ dapr stop [flags] | -------------------- | -------------------- | ------- | -------------------------------- | | `--app-id`, `-a` | `APP_ID` | | The application id to be stopped | | `--help`, `-h` | | | Print this help message | -| `--run-file`, `-f` | | | Stop running multiple applications at once using a Multi-App Run template file. Currently in [alpha]({{< ref "support-preview-features.md" >}}) and only availale in Linux/MacOS | +| `--run-file`, `-f` | | | Stop running multiple applications at once using a Multi-App Run template file. Currently in [alpha]({{< ref "support-preview-features.md" >}}) and only available in Linux/MacOS | ### Examples diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/mysql.md b/daprdocs/content/en/reference/components-reference/supported-bindings/mysql.md index f2c11899630..881e8eeb405 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/mysql.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/mysql.md @@ -52,7 +52,7 @@ Note that you can not use secret just for username/password. If you use secret, | `maxIdleConns` | N | Output | The max idle connections. Integer greater than 0 | `"10"` | | `maxOpenConns` | N | Output | The max open connections. Integer greater than 0 | `"10"` | | `connMaxLifetime` | N | Output | The max connection lifetime. Duration string | `"12s"` | -| `connMaxIdleTime` | N | Output | The max connection idel time. Duration string | `"12s"` | +| `connMaxIdleTime` | N | Output | The max connection idle time. Duration string | `"12s"` | ### SSL connection diff --git a/daprdocs/content/en/reference/components-reference/supported-middleware/middleware-opa.md b/daprdocs/content/en/reference/components-reference/supported-middleware/middleware-opa.md index 62bf7692277..a4e6a47bbde 100644 --- a/daprdocs/content/en/reference/components-reference/supported-middleware/middleware-opa.md +++ b/daprdocs/content/en/reference/components-reference/supported-middleware/middleware-opa.md @@ -31,7 +31,7 @@ spec: value: 403 # `readBody` controls whether the middleware reads the entire request body in-memory and make it - # availble for policy decisions. + # available for policy decisions. - name: readBody value: "false" diff --git a/daprdocs/content/en/reference/components-reference/supported-middleware/middleware-wasm.md b/daprdocs/content/en/reference/components-reference/supported-middleware/middleware-wasm.md index d83bda22fb2..e1167ad0299 100644 --- a/daprdocs/content/en/reference/components-reference/supported-middleware/middleware-wasm.md +++ b/daprdocs/content/en/reference/components-reference/supported-middleware/middleware-wasm.md @@ -51,7 +51,7 @@ How to compile this is described later. | Field | Details | Required | Example | |-------|----------------------------------------------------------------|----------|----------------| | url | The URL of the resource including the Wasm binary to instantiate. The supported schemes include `file://`, `http://`, and `https://`. The path of a `file://` URL is relative to the Dapr process unless it begins with `/`. | true | `file://hello.wasm`, `https://example.com/hello.wasm` | -| guestConfig | An optional configuration passed to Wasm guests. Users can pass an arbitrary string to be parsed by the guest code. | false | `enviroment=production`,`{"environment":"production"}` | +| guestConfig | An optional configuration passed to Wasm guests. Users can pass an arbitrary string to be parsed by the guest code. | false | `environment=production`,`{"environment":"production"}` | ## Dapr configuration diff --git a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-aws-snssqs.md b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-aws-snssqs.md index 61b68290196..d4f8618fce3 100644 --- a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-aws-snssqs.md +++ b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-aws-snssqs.md @@ -83,7 +83,7 @@ The above example uses secrets as plain strings. It is recommended to use [a sec | secretKey | Y | Secret for the AWS user/role. If using an `AssumeRole` access, you will also need to provide a `sessionToken` |`"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"` | region | Y | The AWS region where the SNS/SQS assets are located or be created in. See [this page](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/?p=ugi&l=na) for valid regions. Ensure that SNS and SQS are available in that region | `"us-east-1"` | consumerID | N | Consumer ID (consumer tag) organizes one or more consumers into a group. Consumers with the same consumer ID work as one virtual consumer; for example, a message is processed only once by one of the consumers in the group. If the `consumerID` is not provided, the Dapr runtime set it to the Dapr application ID (`appID`) value. See the [pub/sub broker component file]({{< ref setup-pubsub.md >}}) to learn how ConsumerID is automatically generated. | `"channel1"` -| endpoint | N | AWS endpoint for the component to use. Only used for local development with, for example, [localstack](https://github.com/localstack/localstack). The `endpoint` is unncessary when running against production AWS | `"http://localhost:4566"` +| endpoint | N | AWS endpoint for the component to use. Only used for local development with, for example, [localstack](https://github.com/localstack/localstack). The `endpoint` is unnecessary when running against production AWS | `"http://localhost:4566"` | sessionToken | N | AWS session token to use. A session token is only required if you are using temporary security credentials | `"TOKEN"` | messageReceiveLimit | N | Number of times a message is received, after processing of that message fails, that once reached, results in removing of that message from the queue. If `sqsDeadLettersQueueName` is specified, `messageReceiveLimit` is the number of times a message is received, after processing of that message fails, that once reached, results in moving of the message to the SQS dead-letters queue. Default: `10` | `10` | sqsDeadLettersQueueName | N | Name of the dead letters queue for this application | `"myapp-dlq"` diff --git a/daprdocs/content/en/reference/components-reference/supported-secret-stores/envvar-secret-store.md b/daprdocs/content/en/reference/components-reference/supported-secret-stores/envvar-secret-store.md index b4e67318946..9ef8198aac0 100644 --- a/daprdocs/content/en/reference/components-reference/supported-secret-stores/envvar-secret-store.md +++ b/daprdocs/content/en/reference/components-reference/supported-secret-stores/envvar-secret-store.md @@ -41,7 +41,7 @@ spec: For security reasons, this component cannot be used to access these environment variables: - `APP_API_TOKEN` -- Any variable whose name begines with the `DAPR_` prefix +- Any variable whose name begins with the `DAPR_` prefix ## Related Links - [Secrets building block]({{< ref secrets >}}) From c445eda8ebb54d3f256276edcdda87d87c70ac52 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Tue, 24 Oct 2023 17:52:53 -0400 Subject: [PATCH 139/162] update aad to entra Signed-off-by: Hannah Hunter --- .../Azure/azure-authentication/_index.md | 2 +- .../authenticating-azure.md | 30 +++++++++---------- .../Azure/azure-authentication/howto-aad.md | 14 ++++----- .../Azure/azure-authentication/howto-mi.md | 2 +- .../content/en/operations/security/oauth.md | 4 +-- .../supported-bindings/blobstorage.md | 6 ++-- .../supported-bindings/cosmosdb.md | 4 +-- .../supported-bindings/eventgrid.md | 8 ++--- .../supported-bindings/eventhubs.md | 16 +++++----- .../supported-bindings/openai.md | 6 ++-- .../supported-bindings/postgresql.md | 10 +++---- .../supported-bindings/servicebusqueues.md | 8 ++--- .../supported-bindings/signalr.md | 10 +++---- .../supported-bindings/storagequeues.md | 6 ++-- .../azure-appconfig-configuration-store.md | 6 ++-- .../postgresql-configuration-store.md | 10 +++---- .../supported-cryptography/azure-key-vault.md | 6 ++-- .../supported-middleware/middleware-bearer.md | 2 +- .../supported-pubsub/setup-azure-eventhubs.md | 18 +++++------ .../setup-azure-servicebus-queues.md | 10 +++---- .../setup-azure-servicebus-topics.md | 10 +++---- .../supported-secret-stores/azure-keyvault.md | 26 ++++++++-------- .../setup-azure-blobstorage.md | 10 +++---- .../setup-azure-cosmosdb.md | 14 ++++----- .../setup-azure-tablestorage.md | 10 +++---- .../setup-postgresql.md | 10 +++---- .../supported-state-stores/setup-sqlserver.md | 10 +++---- 27 files changed, 134 insertions(+), 134 deletions(-) diff --git a/daprdocs/content/en/developing-applications/integrations/Azure/azure-authentication/_index.md b/daprdocs/content/en/developing-applications/integrations/Azure/azure-authentication/_index.md index 59dce6d2305..d25f63f11c8 100644 --- a/daprdocs/content/en/developing-applications/integrations/Azure/azure-authentication/_index.md +++ b/daprdocs/content/en/developing-applications/integrations/Azure/azure-authentication/_index.md @@ -3,5 +3,5 @@ type: docs title: "Authenticate to Azure" linkTitle: "Authenticate to Azure" weight: 1600 -description: "Learn about authenticating Azure components using Azure Active Directory or Managed Identities" +description: "Learn about authenticating Azure components using Microsoft Entra ID or Managed Identities" --- \ No newline at end of file diff --git a/daprdocs/content/en/developing-applications/integrations/Azure/azure-authentication/authenticating-azure.md b/daprdocs/content/en/developing-applications/integrations/Azure/azure-authentication/authenticating-azure.md index b020548eeef..6e4ffbeee3d 100644 --- a/daprdocs/content/en/developing-applications/integrations/Azure/azure-authentication/authenticating-azure.md +++ b/daprdocs/content/en/developing-applications/integrations/Azure/azure-authentication/authenticating-azure.md @@ -2,27 +2,27 @@ type: docs title: "Authenticating to Azure" linkTitle: "Overview" -description: "How to authenticate Azure components using Azure AD and/or Managed Identities" +description: "How to authenticate Azure components using Microsoft Entra ID and/or Managed Identities" aliases: - "/operations/components/setup-secret-store/supported-secret-stores/azure-keyvault-managed-identity/" - "/reference/components-reference/supported-secret-stores/azure-keyvault-managed-identity/" weight: 10000 --- -Most Azure components for Dapr support authenticating with Azure AD (Azure Active Directory). Thanks to this: +Most Azure components for Dapr support authenticating with Microsoft Entra ID. Thanks to this: - Administrators can leverage all the benefits of fine-tuned permissions with Azure Role-Based Access Control (RBAC). - Applications running on Azure services such as Azure Container Apps, Azure Kubernetes Service, Azure VMs, or any other Azure platform services can leverage [Managed Identities (MI)](https://learn.microsoft.com/azure/active-directory/managed-identities-azure-resources/overview) and [Workload Identity](https://learn.microsoft.com/azure/aks/workload-identity-overview). These offer the ability to authenticate your applications without having to manage sensitive credentials. -## About authentication with Azure AD +## About authentication with Microsoft Entra ID -Azure AD is Azure's identity and access management (IAM) solution, which is used to authenticate and authorize users and services. +Microsoft Entra ID is Azure's identity and access management (IAM) solution, which is used to authenticate and authorize users and services. -Azure AD is built on top of open standards such OAuth 2.0, which allows services (applications) to obtain access tokens to make requests to Azure services, including Azure Storage, Azure Service Bus, Azure Key Vault, Azure Cosmos DB, Azure Database for Postgres, Azure SQL, etc. +Microsoft Entra ID is built on top of open standards such OAuth 2.0, which allows services (applications) to obtain access tokens to make requests to Azure services, including Azure Storage, Azure Service Bus, Azure Key Vault, Azure Cosmos DB, Azure Database for Postgres, Azure SQL, etc. > In Azure terminology, an application is also called a "Service Principal". -Some Azure components offer alternative authentication methods, such as systems based on "shared keys" or "access tokens". Although these are valid and supported by Dapr, you should authenticate your Dapr components using Azure AD whenever possible to take advantage of many benefits, including: +Some Azure components offer alternative authentication methods, such as systems based on "shared keys" or "access tokens". Although these are valid and supported by Dapr, you should authenticate your Dapr components using Microsoft Entra ID whenever possible to take advantage of many benefits, including: - [Managed Identities and Workload Identity](#managed-identities-and-workload-identity) - [Role-Based Access Control](#role-based-access-control) @@ -31,7 +31,7 @@ Some Azure components offer alternative authentication methods, such as systems ### Managed Identities and Workload Identity -With Managed Identities (MI), your application can authenticate with Azure AD and obtain an access token to make requests to Azure services. When your application is running on a supported Azure service (such as Azure VMs, Azure Container Apps, Azure Web Apps, etc), an identity for your application can be assigned at the infrastructure level. +With Managed Identities (MI), your application can authenticate with Microsoft Entra ID and obtain an access token to make requests to Azure services. When your application is running on a supported Azure service (such as Azure VMs, Azure Container Apps, Azure Web Apps, etc), an identity for your application can be assigned at the infrastructure level. Once using MI, your code doesn't have to deal with credentials, which: @@ -48,11 +48,11 @@ When using Azure Role-Based Access Control (RBAC) with supported services, permi ### Auditing -Using Azure AD provides an improved auditing experience for access. Tenant administrators can consult audit logs to track authentication requests. +Using Microsoft Entra ID provides an improved auditing experience for access. Tenant administrators can consult audit logs to track authentication requests. ### (Optional) Authentication using certificates -While Azure AD allows you to use MI, you still have the option to authenticate using certificates. +While Microsoft Entra ID allows you to use MI, you still have the option to authenticate using certificates. ## Support for other Azure environments @@ -66,7 +66,7 @@ By default, Dapr components are configured to interact with Azure resources in t ## Credentials metadata fields -To authenticate with Azure AD, you will need to add the following credentials as values in the metadata for your [Dapr component](#example-usage-in-a-dapr-component). +To authenticate with Microsoft Entra ID, you will need to add the following credentials as values in the metadata for your [Dapr component](#example-usage-in-a-dapr-component). ### Metadata options @@ -82,7 +82,7 @@ Depending on how you've passed credentials to your Dapr services, you have multi | Field | Required | Details | Example | |---------------------|----------|--------------------------------------|----------------------------------------------| -| `azureTenantId` | Y | ID of the Azure AD tenant | `"cd4b2887-304c-47e1-b4d5-65447fdd542b"` | +| `azureTenantId` | Y | ID of the Microsoft Entra ID tenant | `"cd4b2887-304c-47e1-b4d5-65447fdd542b"` | | `azureClientId` | Y | Client ID (application ID) | `"c7dd251f-811f-4ba2-a905-acd4d3f8f08b"` | | `azureClientSecret` | Y | Client secret (application password) | `"Ecy3XG7zVZK3/vl/a2NSB+a1zXLa8RnMum/IgD0E"` | @@ -92,7 +92,7 @@ When running on Kubernetes, you can also use references to Kubernetes secrets fo | Field | Required | Details | Example | |--------|--------|--------|--------| -| `azureTenantId` | Y | ID of the Azure AD tenant | `"cd4b2887-304c-47e1-b4d5-65447fdd542b"` | +| `azureTenantId` | Y | ID of the Microsoft Entra ID tenant | `"cd4b2887-304c-47e1-b4d5-65447fdd542b"` | | `azureClientId` | Y | Client ID (application ID) | `"c7dd251f-811f-4ba2-a905-acd4d3f8f08b"` | | `azureCertificate` | One of `azureCertificate` and `azureCertificateFile` | Certificate and private key (in PFX/PKCS#12 format) | `"-----BEGIN PRIVATE KEY-----\n MIIEvgI... \n -----END PRIVATE KEY----- \n -----BEGIN CERTIFICATE----- \n MIICoTC... \n -----END CERTIFICATE-----` | | `azureCertificateFile` | One of `azureCertificate` and `azureCertificateFile` | Path to the PFX/PKCS#12 file containing the certificate and private key | `"/path/to/file.pem"` | @@ -127,7 +127,7 @@ Using this authentication method does not require setting any metadata option. ### Example usage in a Dapr component -In this example, you will set up an Azure Key Vault secret store component that uses Azure AD to authenticate. +In this example, you will set up an Azure Key Vault secret store component that uses Microsoft Entra ID to authenticate. {{< tabs "Self-Hosted" "Kubernetes">}} @@ -279,11 +279,11 @@ To use a **certificate**: ## Next steps -{{< button text="Generate a new Azure AD application and Service Principal >>" page="howto-aad.md" >}} +{{< button text="Generate a new Microsoft Entra ID application and Service Principal >>" page="howto-aad.md" >}} ## References -- [Azure AD app credential: Azure CLI reference](https://docs.microsoft.com/cli/azure/ad/app/credential) +- [Microsoft Entra ID app credential: Azure CLI reference](https://docs.microsoft.com/cli/azure/ad/app/credential) - [Azure Managed Service Identity (MSI) overview](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/overview) - [Secrets building block]({{< ref secrets >}}) - [How-To: Retrieve a secret]({{< ref "howto-secrets.md" >}}) diff --git a/daprdocs/content/en/developing-applications/integrations/Azure/azure-authentication/howto-aad.md b/daprdocs/content/en/developing-applications/integrations/Azure/azure-authentication/howto-aad.md index d1be027ca98..abb67782420 100644 --- a/daprdocs/content/en/developing-applications/integrations/Azure/azure-authentication/howto-aad.md +++ b/daprdocs/content/en/developing-applications/integrations/Azure/azure-authentication/howto-aad.md @@ -1,9 +1,9 @@ --- type: docs -title: "How to: Generate a new Azure AD application and Service Principal" -linkTitle: "How to: Generate Azure AD and Service Principal" +title: "How to: Generate a new Microsoft Entra ID application and Service Principal" +linkTitle: "How to: Generate Microsoft Entra ID and Service Principal" weight: 30000 -description: "Learn how to generate an Azure Active Directory and use it as a Service Principal" +description: "Learn how to generate an Microsoft Entra ID and use it as a Service Principal" --- ## Prerequisites @@ -23,9 +23,9 @@ az login az account set -s [your subscription id] ``` -### Create an Azure AD application +### Create an Microsoft Entra ID application -Create the Azure AD application with: +Create the Microsoft Entra ID application with: ```sh # Friendly name for the application / Service Principal @@ -107,7 +107,7 @@ When adding the returned values to your Dapr component's metadata: ### Create a Service Principal -Once you have created an Azure AD application, create a Service Principal for that application. With this Service Principal, you can grant it access to Azure resources. +Once you have created an Microsoft Entra ID application, create a Service Principal for that application. With this Service Principal, you can grant it access to Azure resources. To create the Service Principal, run the following command: @@ -124,7 +124,7 @@ Expected output: Service Principal ID: 1d0ccf05-5427-4b5e-8eb4-005ac5f9f163 ``` -The returned value above is the **Service Principal ID**, which is different from the Azure AD application ID (client ID). The Service Principal ID is defined within an Azure tenant and used to grant access to Azure resources to an application +The returned value above is the **Service Principal ID**, which is different from the Microsoft Entra ID application ID (client ID). The Service Principal ID is defined within an Azure tenant and used to grant access to Azure resources to an application You'll use the Service Principal ID to grant permissions to an application to access Azure resources. Meanwhile, **the client ID** is used by your application to authenticate. You'll use the client ID in Dapr manifests to configure authentication with Azure services. diff --git a/daprdocs/content/en/developing-applications/integrations/Azure/azure-authentication/howto-mi.md b/daprdocs/content/en/developing-applications/integrations/Azure/azure-authentication/howto-mi.md index 5eb6a8f8683..28aa976dc6a 100644 --- a/daprdocs/content/en/developing-applications/integrations/Azure/azure-authentication/howto-mi.md +++ b/daprdocs/content/en/developing-applications/integrations/Azure/azure-authentication/howto-mi.md @@ -10,7 +10,7 @@ description: "Learn how to use Managed Identities" Using Managed Identities (MI), authentication happens automatically by virtue of your application running on top of an Azure service that has an assigned identity. -For example, let's say you enable a managed service identity for an Azure VM, Azure Container App, or an Azure Kubernetes Service cluster. When you do, an Azure AD application is created for you and automatically assigned to the service. Your Dapr services can then leverage that identity to authenticate with Azure AD, transparently and without you having to specify any credentials. +For example, let's say you enable a managed service identity for an Azure VM, Azure Container App, or an Azure Kubernetes Service cluster. When you do, an Microsoft Entra ID application is created for you and automatically assigned to the service. Your Dapr services can then leverage that identity to authenticate with Microsoft Entra ID, transparently and without you having to specify any credentials. To get started with managed identities, you need to assign an identity to a new or existing Azure resource. The instructions depend on the service use. Check the following official documentation for the most appropriate instructions: diff --git a/daprdocs/content/en/operations/security/oauth.md b/daprdocs/content/en/operations/security/oauth.md index 0e1213dbfcd..ab29634ceaf 100644 --- a/daprdocs/content/en/operations/security/oauth.md +++ b/daprdocs/content/en/operations/security/oauth.md @@ -16,7 +16,7 @@ The main difference between the two flows is that the `Authorization Code Grant Different authorization servers provide different application registration experiences. Here are some samples: -* [Azure AAD](https://docs.microsoft.com/azure/active-directory/develop/v1-protocols-oauth-code) +* [Microsoft Entra ID](https://docs.microsoft.com/azure/active-directory/develop/v1-protocols-oauth-code) * [Facebook](https://developers.facebook.com/apps) * [Fitbit](https://dev.fitbit.com/build/reference/web-api/oauth2/) * [GitHub](https://developer.github.com/apps/building-oauth-apps/creating-an-oauth-app/) @@ -37,7 +37,7 @@ Authorization/Token URLs of some of the popular authorization servers: | Server | Authorization URL | Token URL | |---------|-------------------|-----------| -|Azure AAD||| +|Microsoft Entra ID||| |GitHub||| |Google|| | |Twitter||| diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/blobstorage.md b/daprdocs/content/en/reference/components-reference/supported-bindings/blobstorage.md index 3df3e28048b..4baea225cf1 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/blobstorage.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/blobstorage.md @@ -43,16 +43,16 @@ The above example uses secrets as plain strings. It is recommended to use a secr | Field | Required | Binding support | Details | Example | |--------------------|:--------:|--------|---------|---------| | `accountName` | Y | Input/Output | The name of the Azure Storage account | `"myexmapleaccount"` | -| `accountKey` | Y* | Input/Output | The access key of the Azure Storage account. Only required when not using Azure AD authentication. | `"access-key"` | +| `accountKey` | Y* | Input/Output | The access key of the Azure Storage account. Only required when not using Microsoft Entra ID authentication. | `"access-key"` | | `containerName` | Y | Output | The name of the Blob Storage container to write to | `myexamplecontainer` | | `endpoint` | N | Input/Output | Optional custom endpoint URL. This is useful when using the [Azurite emulator](https://github.com/Azure/azurite) or when using custom domains for Azure Storage (although this is not officially supported). The endpoint must be the full base URL, including the protocol (`http://` or `https://`), the IP or FQDN, and optional port. | `"http://127.0.0.1:10000"` | `decodeBase64` | N | Output | Configuration to decode base64 file content before saving to Blob Storage. (In case of saving a file with binary content). Defaults to `false` | `true`, `false` | | `getBlobRetryCount` | N | Output | Specifies the maximum number of HTTP GET requests that will be made while reading from a RetryReader Defaults to `10` | `1`, `2` | `publicAccessLevel` | N | Output | Specifies whether data in the container may be accessed publicly and the level of access (only used if the container is created by Dapr). Defaults to `none` | `blob`, `container`, `none` -### Azure Active Directory (AAD) authentication +### Microsoft Entra ID authentication -The Azure Blob Storage binding component supports authentication using all Azure Active Directory mechanisms. For further information and the relevant component metadata fields to provide depending on the choice of AAD authentication mechanism, see the [docs for authenticating to Azure]({{< ref authenticating-azure.md >}}). +The Azure Blob Storage binding component supports authentication using all Microsoft Entra ID mechanisms. For further information and the relevant component metadata fields to provide depending on the choice of Microsoft Entra ID authentication mechanism, see the [docs for authenticating to Azure]({{< ref authenticating-azure.md >}}). ## Binding support diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/cosmosdb.md b/daprdocs/content/en/reference/components-reference/supported-bindings/cosmosdb.md index 661c75e8e3f..813166f0265 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/cosmosdb.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/cosmosdb.md @@ -48,9 +48,9 @@ The above example uses secrets as plain strings. It is recommended to use a secr For more information see [Azure Cosmos DB resource model](https://docs.microsoft.com/azure/cosmos-db/account-databases-containers-items). -### Azure Active Directory (Azure AD) authentication +### Microsoft Entra ID authentication -The Azure Cosmos DB binding component supports authentication using all Azure Active Directory mechanisms. For further information and the relevant component metadata fields to provide depending on the choice of AAD authentication mechanism, see the [docs for authenticating to Azure]({{< ref authenticating-azure.md >}}). +The Azure Cosmos DB binding component supports authentication using all Microsoft Entra ID mechanisms. For further information and the relevant component metadata fields to provide depending on the choice of Microsoft Entra ID authentication mechanism, see the [docs for authenticating to Azure]({{< ref authenticating-azure.md >}}). You can read additional information for setting up Cosmos DB with Azure AD authentication in the [section below](#setting-up-cosmos-db-for-authenticating-with-azure-ad). diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/eventgrid.md b/daprdocs/content/en/reference/components-reference/supported-bindings/eventgrid.md index 6288baee4b7..9e66107b591 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/eventgrid.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/eventgrid.md @@ -90,9 +90,9 @@ This component supports **output binding** with the following operations: - `create`: publishes a message on the Event Grid topic -## Azure AD credentials +## Microsoft Entra ID credentials -The Azure Event Grid binding requires an Azure AD application and service principal for two reasons: +The Azure Event Grid binding requires an Microsoft Entra ID application and service principal for two reasons: - Creating an [event subscription](https://docs.microsoft.com/azure/event-grid/concepts#event-subscriptions) when Dapr is started (and updating it if the Dapr configuration changes) - Authenticating messages delivered by Event Hubs to your application. @@ -106,7 +106,7 @@ Requirements: - [Microsoft.Graph module for PowerShell](https://learn.microsoft.com/powershell/microsoftgraph/installation) for PowerShell installed: `Install-Module Microsoft.Graph -Scope CurrentUser -Repository PSGallery -Force` -For the first purpose, you will need to [create an Azure Service Principal](https://learn.microsoft.com/azure/active-directory/develop/howto-create-service-principal-portal). After creating it, take note of the Azure AD application's **clientID** (a UUID), and run the following script with the Azure CLI: +For the first purpose, you will need to [create an Azure Service Principal](https://learn.microsoft.com/azure/active-directory/develop/howto-create-service-principal-portal). After creating it, take note of the Microsoft Entra ID application's **clientID** (a UUID), and run the following script with the Azure CLI: ```bash # Set the client ID of the app you created @@ -140,7 +140,7 @@ Connect-MgGraph -Scopes "Application.Read.All","Application.ReadWrite.All" ./setup-eventgrid-sp.ps1 $clientId ``` -> Note: if your directory does not have a Service Principal for the application "Microsoft.EventGrid", you may need to run the command `Connect-MgGraph` and sign in as an admin for the Azure AD tenant (this is related to permissions on the Azure AD directory, and not the Azure subscription). Otherwise, please ask your tenant's admin to sign in and run this PowerShell command: `New-MgServicePrincipal -AppId "4962773b-9cdb-44cf-a8bf-237846a00ab7"` (the UUID is a constant) +> Note: if your directory does not have a Service Principal for the application "Microsoft.EventGrid", you may need to run the command `Connect-MgGraph` and sign in as an admin for the Microsoft Entra ID tenant (this is related to permissions on the Microsoft Entra ID directory, and not the Azure subscription). Otherwise, please ask your tenant's admin to sign in and run this PowerShell command: `New-MgServicePrincipal -AppId "4962773b-9cdb-44cf-a8bf-237846a00ab7"` (the UUID is a constant) ### Testing locally diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/eventhubs.md b/daprdocs/content/en/reference/components-reference/supported-bindings/eventhubs.md index a4dc7701369..ee005b4dda4 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/eventhubs.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/eventhubs.md @@ -28,10 +28,10 @@ spec: - name: consumerGroup value: "myapp" # Either connectionString or eventHubNamespace is required - # Use connectionString when *not* using Azure AD + # Use connectionString when *not* using Microsoft Entra ID - name: connectionString value: "Endpoint=sb://{EventHubNamespace}.servicebus.windows.net/;SharedAccessKeyName={PolicyName};SharedAccessKey={Key};EntityPath={EventHub}" - # Use eventHubNamespace when using Azure AD + # Use eventHubNamespace when using Microsoft Entra ID - name: eventHubNamespace value: "namespace" - name: enableEntityManagement @@ -68,9 +68,9 @@ The above example uses secrets as plain strings. It is recommended to use a secr | Field | Required | Binding support | Details | Example | |--------------------|:--------:|------------|-----|---------| -| `eventHub` | Y* | Input/Output | The name of the Event Hubs hub ("topic"). Required if using Azure AD authentication or if the connection string doesn't contain an `EntityPath` value | `mytopic` | -| `connectionString` | Y* | Input/Output | Connection string for the Event Hub or the Event Hub namespace.
* Mutally exclusive with `eventHubNamespace` field.
* Required when not using [Azure AD Authentication]({{< ref "authenticating-azure.md" >}}) | `"Endpoint=sb://{EventHubNamespace}.servicebus.windows.net/;SharedAccessKeyName={PolicyName};SharedAccessKey={Key};EntityPath={EventHub}"` or `"Endpoint=sb://{EventHubNamespace}.servicebus.windows.net/;SharedAccessKeyName={PolicyName};SharedAccessKey={Key}"` -| `eventHubNamespace` | Y* | Input/Output | The Event Hub Namespace name.
* Mutally exclusive with `connectionString` field.
* Required when using [Azure AD Authentication]({{< ref "authenticating-azure.md" >}}) | `"namespace"` +| `eventHub` | Y* | Input/Output | The name of the Event Hubs hub ("topic"). Required if using Microsoft Entra ID authentication or if the connection string doesn't contain an `EntityPath` value | `mytopic` | +| `connectionString` | Y* | Input/Output | Connection string for the Event Hub or the Event Hub namespace.
* Mutally exclusive with `eventHubNamespace` field.
* Required when not using [Microsoft Entra ID Authentication]({{< ref "authenticating-azure.md" >}}) | `"Endpoint=sb://{EventHubNamespace}.servicebus.windows.net/;SharedAccessKeyName={PolicyName};SharedAccessKey={Key};EntityPath={EventHub}"` or `"Endpoint=sb://{EventHubNamespace}.servicebus.windows.net/;SharedAccessKeyName={PolicyName};SharedAccessKey={Key}"` +| `eventHubNamespace` | Y* | Input/Output | The Event Hub Namespace name.
* Mutally exclusive with `connectionString` field.
* Required when using [Microsoft Entra ID Authentication]({{< ref "authenticating-azure.md" >}}) | `"namespace"` | `enableEntityManagement` | N | Input/Output | Boolean value to allow management of the EventHub namespace and storage account. Default: `false` | `"true", "false"` | `resourceGroupName` | N | Input/Output | Name of the resource group the Event Hub namespace is part of. Required when entity management is enabled | `"test-rg"` | `subscriptionID` | N | Input/Output | Azure subscription ID value. Required when entity management is enabled | `"azure subscription id"` @@ -78,14 +78,14 @@ The above example uses secrets as plain strings. It is recommended to use a secr | `messageRetentionInDays` | N | Input/Output | Number of days to retain messages for in the newly created Event Hub namespace. Used only when entity management is enabled. Default: `"1"` | `"90"` | `consumerGroup` | Y | Input | The name of the [Event Hubs Consumer Group](https://docs.microsoft.com/azure/event-hubs/event-hubs-features#consumer-groups) to listen on | `"group1"` | | `storageAccountName` | Y | Input | Storage account name to use for the checkpoint store. |`"myeventhubstorage"` -| `storageAccountKey` | Y* | Input | Storage account key for the checkpoint store account.
* When using Azure AD, it's possible to omit this if the service principal has access to the storage account too. | `"112233445566778899"` +| `storageAccountKey` | Y* | Input | Storage account key for the checkpoint store account.
* When using Microsoft Entra ID, it's possible to omit this if the service principal has access to the storage account too. | `"112233445566778899"` | `storageConnectionString` | Y* | Input | Connection string for the checkpoint store, alternative to specifying `storageAccountKey` | `"DefaultEndpointsProtocol=https;AccountName=myeventhubstorage;AccountKey="` | `storageContainerName` | Y | Input | Storage container name for the storage account name. | `"myeventhubstoragecontainer"` | `direction` | N | Input/Output | The direction of the binding. | `"input"`, `"output"`, `"input, output"` -### Azure Active Directory (AAD) authentication +### Microsoft Entra ID authentication -The Azure Event Hubs pub/sub component supports authentication using all Azure Active Directory mechanisms. For further information and the relevant component metadata fields to provide depending on the choice of AAD authentication mechanism, see the [docs for authenticating to Azure]({{< ref authenticating-azure.md >}}). +The Azure Event Hubs pub/sub component supports authentication using all Microsoft Entra ID mechanisms. For further information and the relevant component metadata fields to provide depending on the choice of Microsoft Entra ID authentication mechanism, see the [docs for authenticating to Azure]({{< ref authenticating-azure.md >}}). ## Binding support diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/openai.md b/daprdocs/content/en/reference/components-reference/supported-bindings/openai.md index f62950c04b6..34bbeb151fc 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/openai.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/openai.md @@ -36,14 +36,14 @@ The above example uses `apiKey` as a plain string. It is recommended to use a s | Field | Required | Binding support | Details | Example | |--------------------|:--------:|--------|---------|---------| | `endpoint` | Y | Output | Azure OpenAI service endpoint URL. | `"https://myopenai.openai.azure.com"` | -| `apiKey` | Y* | Output | The access key of the Azure OpenAI service. Only required when not using Azure AD authentication. | `"1234567890abcdef"` | +| `apiKey` | Y* | Output | The access key of the Azure OpenAI service. Only required when not using Microsoft Entra ID authentication. | `"1234567890abcdef"` | | `azureTenantId` | Y* | Input | The tenant ID of the Azure OpenAI resource. Only required when `apiKey` is not provided. | `"tenentID"` | | `azureClientId` | Y* | Input | The client ID that should be used by the binding to create or update the Azure OpenAI Subscription and to authenticate incoming messages. Only required when `apiKey` is not provided.| `"clientId"` | | `azureClientSecret` | Y* | Input | The client secret that should be used by the binding to create or update the Azure OpenAI Subscription and to authenticate incoming messages. Only required when `apiKey` is not provided. | `"clientSecret"` | -### Azure Active Directory (AAD) authentication +### Microsoft Entra ID authentication -The Azure OpenAI binding component supports authentication using all Azure Active Directory mechanisms. For further information and the relevant component metadata fields to provide depending on the choice of AAD authentication mechanism, see the [docs for authenticating to Azure]({{< ref authenticating-azure.md >}}). +The Azure OpenAI binding component supports authentication using all Microsoft Entra ID mechanisms. For further information and the relevant component metadata fields to provide depending on the choice of Microsoft Entra ID authentication mechanism, see the [docs for authenticating to Azure]({{< ref authenticating-azure.md >}}). #### Example Configuration diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/postgresql.md b/daprdocs/content/en/reference/components-reference/supported-bindings/postgresql.md index 0a21d93b663..235cebabaa2 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/postgresql.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/postgresql.md @@ -41,15 +41,15 @@ The following metadata options are **required** to authenticate using a PostgreS |--------|:--------:|---------|---------| | `connectionString` | Y | The connection string for the PostgreSQL database. See the PostgreSQL [documentation on database connections](https://www.postgresql.org/docs/current/libpq-connect.html) for information on how to define a connection string. | `"host=localhost user=postgres password=example port=5432 connect_timeout=10 database=my_db"` -### Authenticate using Azure AD +### Authenticate using Microsoft Entra ID -Authenticating with Azure AD is supported with Azure Database for PostgreSQL. All authentication methods supported by Dapr can be used, including client credentials ("service principal") and Managed Identity. +Authenticating with Microsoft Entra ID is supported with Azure Database for PostgreSQL. All authentication methods supported by Dapr can be used, including client credentials ("service principal") and Managed Identity. | Field | Required | Details | Example | |--------|:--------:|---------|---------| -| `useAzureAD` | Y | Must be set to `true` to enable the component to retrieve access tokens from Azure AD. | `"true"` | -| `connectionString` | Y | The connection string for the PostgreSQL database.
This must contain the user, which corresponds to the name of the user created inside PostgreSQL that maps to the Azure AD identity; this is often the name of the corresponding principal (e.g. the name of the Azure AD application). This connection string should not contain any password. | `"host=mydb.postgres.database.azure.com user=myapplication port=5432 database=my_db sslmode=require"` | -| `azureTenantId` | N | ID of the Azure AD tenant | `"cd4b2887-304c-…"` | +| `useAzureAD` | Y | Must be set to `true` to enable the component to retrieve access tokens from Microsoft Entra ID. | `"true"` | +| `connectionString` | Y | The connection string for the PostgreSQL database.
This must contain the user, which corresponds to the name of the user created inside PostgreSQL that maps to the Microsoft Entra ID identity; this is often the name of the corresponding principal (e.g. the name of the Microsoft Entra ID application). This connection string should not contain any password. | `"host=mydb.postgres.database.azure.com user=myapplication port=5432 database=my_db sslmode=require"` | +| `azureTenantId` | N | ID of the Microsoft Entra ID tenant | `"cd4b2887-304c-…"` | | `azureClientId` | N | Client ID (application ID) | `"c7dd251f-811f-…"` | | `azureClientSecret` | N | Client secret (application password) | `"Ecy3X…"` | diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/servicebusqueues.md b/daprdocs/content/en/reference/components-reference/supported-bindings/servicebusqueues.md index e2c74a4ba4b..c836626edd6 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/servicebusqueues.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/servicebusqueues.md @@ -67,10 +67,10 @@ The above example uses secrets as plain strings. It is recommended to use a secr | Field | Required | Binding support | Details | Example | |--------------------|:--------:|-----------------|----------|---------| -| `connectionString` | Y | Input/Output | The Service Bus connection string. Required unless using Azure AD authentication. | `"Endpoint=sb://************"` | +| `connectionString` | Y | Input/Output | The Service Bus connection string. Required unless using Microsoft Entra ID authentication. | `"Endpoint=sb://************"` | | `queueName` | Y | Input/Output | The Service Bus queue name. Queue names are case-insensitive and will always be forced to lowercase. | `"queuename"` | | `timeoutInSec` | N | Input/Output | Timeout for all invocations to the Azure Service Bus endpoint, in seconds. *Note that this option impacts network calls and it's unrelated to the TTL applies to messages*. Default: `"60"` | `"60"` | -| `namespaceName`| N | Input/Output | Parameter to set the address of the Service Bus namespace, as a fully-qualified domain name. Required if using Azure AD authentication. | `"namespace.servicebus.windows.net"` | +| `namespaceName`| N | Input/Output | Parameter to set the address of the Service Bus namespace, as a fully-qualified domain name. Required if using Microsoft Entra ID authentication. | `"namespace.servicebus.windows.net"` | | `disableEntityManagement` | N | Input/Output | When set to true, queues and subscriptions do not get created automatically. Default: `"false"` | `"true"`, `"false"` | `lockDurationInSec` | N | Input/Output | Defines the length in seconds that a message will be locked for before expiring. Used during subscription creation only. Default set by server. | `"30"` | `autoDeleteOnIdleInSec` | N | Input/Output | Time in seconds to wait before auto deleting idle subscriptions. Used during subscription creation only. Default: `"0"` (disabled) | `"3600"` @@ -90,9 +90,9 @@ The above example uses secrets as plain strings. It is recommended to use a secr | `publishInitialRetryIntervalInMs` | N | Output | Time in milliseconds for the initial exponential backoff when Azure Service Bus throttle messages. Defaults: `"500"` | `"500"` | `direction` | N | Input/Output | The direction of the binding | `"input"`, `"output"`, `"input, output"` -### Azure Active Directory (AAD) authentication +### Microsoft Entra ID authentication -The Azure Service Bus Queues binding component supports authentication using all Azure Active Directory mechanisms, including Managed Identities. For further information and the relevant component metadata fields to provide depending on the choice of AAD authentication mechanism, see the [docs for authenticating to Azure]({{< ref authenticating-azure.md >}}). +The Azure Service Bus Queues binding component supports authentication using all Microsoft Entra ID mechanisms, including Managed Identities. For further information and the relevant component metadata fields to provide depending on the choice of Microsoft Entra ID authentication mechanism, see the [docs for authenticating to Azure]({{< ref authenticating-azure.md >}}). #### Example Configuration diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/signalr.md b/daprdocs/content/en/reference/components-reference/supported-bindings/signalr.md index 560ed30fcc9..ea29d744a0d 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/signalr.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/signalr.md @@ -37,14 +37,14 @@ The above example uses secrets as plain strings. It is recommended to use a secr |--------------------|:--------:|------------|-----|---------| | `connectionString` | Y | Output | The Azure SignalR connection string | `"Endpoint=https://.service.signalr.net;AccessKey=;Version=1.0;"` | | `hub` | N | Output | Defines the hub in which the message will be send. The hub can be dynamically defined as a metadata value when publishing to an output binding (key is "hub") | `"myhub"` | -| `endpoint` | N | Output | Endpoint of Azure SignalR; required if not included in the `connectionString` or if using Azure AD | `"https://.service.signalr.net"` +| `endpoint` | N | Output | Endpoint of Azure SignalR; required if not included in the `connectionString` or if using Microsoft Entra ID | `"https://.service.signalr.net"` | `accessKey` | N | Output | Access key | `"your-access-key"` -### Azure Active Directory (Azure AD) authentication +### Microsoft Entra ID authentication -The Azure SignalR binding component supports authentication using all Azure Active Directory mechanisms. See the [docs for authenticating to Azure]({{< ref authenticating-azure.md >}}) to learn more about the relevant component metadata fields based on your choice of Azure AD authentication mechanism. +The Azure SignalR binding component supports authentication using all Microsoft Entra ID mechanisms. See the [docs for authenticating to Azure]({{< ref authenticating-azure.md >}}) to learn more about the relevant component metadata fields based on your choice of Microsoft Entra ID authentication mechanism. -You have two options to authenticate this component with Azure AD: +You have two options to authenticate this component with Microsoft Entra ID: - Pass individual metadata keys: - `endpoint` for the endpoint @@ -52,7 +52,7 @@ You have two options to authenticate this component with Azure AD: - Pass a connection string with `AuthType=aad` specified: - System-assigned managed identity: `Endpoint=https://.service.signalr.net;AuthType=aad;Version=1.0;` - User-assigned managed identity: `Endpoint=https://.service.signalr.net;AuthType=aad;ClientId=;Version=1.0;` - - Azure AD application: `Endpoint=https://.service.signalr.net;AuthType=aad;ClientId=;ClientSecret=;TenantId=;Version=1.0;` + - Microsoft Entra ID application: `Endpoint=https://.service.signalr.net;AuthType=aad;ClientId=;ClientSecret=;TenantId=;Version=1.0;` Note that you cannot use a connection string if your application's ClientSecret contains a `;` character. ## Binding support diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/storagequeues.md b/daprdocs/content/en/reference/components-reference/supported-bindings/storagequeues.md index e29e29932b7..6562364a38b 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/storagequeues.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/storagequeues.md @@ -52,7 +52,7 @@ The above example uses secrets as plain strings. It is recommended to use a secr | Field | Required | Binding support | Details | Example | |--------------------|:--------:|------------|-----|---------| | `accountName` | Y | Input/Output | The name of the Azure Storage account | `"account1"` | -| `accountKey` | Y* | Input/Output | The access key of the Azure Storage account. Only required when not using Azure AD authentication. | `"access-key"` | +| `accountKey` | Y* | Input/Output | The access key of the Azure Storage account. Only required when not using Microsoft Entra ID authentication. | `"access-key"` | | `queueName` | Y | Input/Output | The name of the Azure Storage queue | `"myqueue"` | | `pollingInterval` | N | Output | Set the interval to poll Azure Storage Queues for new messages, as a Go duration value. Default: `"10s"` | `"30s"` | | `ttlInSeconds` | N | Output | Parameter to set the default message time to live. If this parameter is omitted, messages will expire after 10 minutes. See [also](#specifying-a-ttl-per-message) | `"60"` | @@ -62,9 +62,9 @@ The above example uses secrets as plain strings. It is recommended to use a secr | `visibilityTimeout` | N | Input | Allows setting a custom queue visibility timeout to avoid immediate retrying of recently failed messages. Defaults to 30 seconds. | `"100s"` | | `direction` | N | Input/Output | Direction of the binding. | `"input"`, `"output"`, `"input, output"` | -### Azure Active Directory (Azure AD) authentication +### Microsoft Entra ID authentication -The Azure Storage Queue binding component supports authentication using all Azure Active Directory mechanisms. See the [docs for authenticating to Azure]({{< ref authenticating-azure.md >}}) to learn more about the relevant component metadata fields based on your choice of Azure AD authentication mechanism. +The Azure Storage Queue binding component supports authentication using all Microsoft Entra ID mechanisms. See the [docs for authenticating to Azure]({{< ref authenticating-azure.md >}}) to learn more about the relevant component metadata fields based on your choice of Microsoft Entra ID authentication mechanism. ## Binding support diff --git a/daprdocs/content/en/reference/components-reference/supported-configuration-stores/azure-appconfig-configuration-store.md b/daprdocs/content/en/reference/components-reference/supported-configuration-stores/azure-appconfig-configuration-store.md index 11c1848cc13..c9f26f2a26c 100644 --- a/daprdocs/content/en/reference/components-reference/supported-configuration-stores/azure-appconfig-configuration-store.md +++ b/daprdocs/content/en/reference/components-reference/supported-configuration-stores/azure-appconfig-configuration-store.md @@ -65,11 +65,11 @@ The above example uses secrets as plain strings. It is recommended to use a secr Access an App Configuration instance using its connection string, which is available in the Azure portal. Since connection strings contain credential information, you should treat them as secrets and [use a secret store]({{< ref component-secrets.md >}}). -## Authenticating with Azure AD +## Authenticating with Microsoft Entra ID -The Azure App Configuration configuration store component also supports authentication with Azure AD. Before you enable this component: +The Azure App Configuration configuration store component also supports authentication with Microsoft Entra ID. Before you enable this component: - Read the [Authenticating to Azure]({{< ref authenticating-azure.md >}}) document. -- Create an Azure AD application (also called Service Principal). +- Create an Microsoft Entra ID application (also called Service Principal). - Alternatively, create a managed identity for your application platform. ## Set up Azure App Configuration diff --git a/daprdocs/content/en/reference/components-reference/supported-configuration-stores/postgresql-configuration-store.md b/daprdocs/content/en/reference/components-reference/supported-configuration-stores/postgresql-configuration-store.md index b9bc3de8328..a846b6a2344 100644 --- a/daprdocs/content/en/reference/components-reference/supported-configuration-stores/postgresql-configuration-store.md +++ b/daprdocs/content/en/reference/components-reference/supported-configuration-stores/postgresql-configuration-store.md @@ -67,15 +67,15 @@ The following metadata options are **required** to authenticate using a PostgreS |--------|:--------:|---------|---------| | `connectionString` | Y | The connection string for the PostgreSQL database. See the PostgreSQL [documentation on database connections](https://www.postgresql.org/docs/current/libpq-connect.html) for information on how to define a connection string. | `"host=localhost user=postgres password=example port=5432 connect_timeout=10 database=my_db"` -### Authenticate using Azure AD +### Authenticate using Microsoft Entra ID -Authenticating with Azure AD is supported with Azure Database for PostgreSQL. All authentication methods supported by Dapr can be used, including client credentials ("service principal") and Managed Identity. +Authenticating with Microsoft Entra ID is supported with Azure Database for PostgreSQL. All authentication methods supported by Dapr can be used, including client credentials ("service principal") and Managed Identity. | Field | Required | Details | Example | |--------|:--------:|---------|---------| -| `useAzureAD` | Y | Must be set to `true` to enable the component to retrieve access tokens from Azure AD. | `"true"` | -| `connectionString` | Y | The connection string for the PostgreSQL database.
This must contain the user, which corresponds to the name of the user created inside PostgreSQL that maps to the Azure AD identity; this is often the name of the corresponding principal (e.g. the name of the Azure AD application). This connection string should not contain any password. | `"host=mydb.postgres.database.azure.com user=myapplication port=5432 database=my_db sslmode=require"` | -| `azureTenantId` | N | ID of the Azure AD tenant | `"cd4b2887-304c-…"` | +| `useAzureAD` | Y | Must be set to `true` to enable the component to retrieve access tokens from Microsoft Entra ID. | `"true"` | +| `connectionString` | Y | The connection string for the PostgreSQL database.
This must contain the user, which corresponds to the name of the user created inside PostgreSQL that maps to the Microsoft Entra ID identity; this is often the name of the corresponding principal (e.g. the name of the Microsoft Entra ID application). This connection string should not contain any password. | `"host=mydb.postgres.database.azure.com user=myapplication port=5432 database=my_db sslmode=require"` | +| `azureTenantId` | N | ID of the Microsoft Entra ID tenant | `"cd4b2887-304c-…"` | | `azureClientId` | N | Client ID (application ID) | `"c7dd251f-811f-…"` | | `azureClientSecret` | N | Client secret (application password) | `"Ecy3X…"` | diff --git a/daprdocs/content/en/reference/components-reference/supported-cryptography/azure-key-vault.md b/daprdocs/content/en/reference/components-reference/supported-cryptography/azure-key-vault.md index 6ec9ba6a456..18f650a07b5 100644 --- a/daprdocs/content/en/reference/components-reference/supported-cryptography/azure-key-vault.md +++ b/daprdocs/content/en/reference/components-reference/supported-cryptography/azure-key-vault.md @@ -32,12 +32,12 @@ spec: The above example uses secrets as plain strings. It is recommended to use a secret store for the secrets, as described [here]({{< ref component-secrets.md >}}). {{% /alert %}} -## Authenticating with Azure AD +## Authenticating with Microsoft Entra ID -The Azure Key Vault cryptography component supports authentication with Azure AD only. Before you enable this component: +The Azure Key Vault cryptography component supports authentication with Microsoft Entra ID only. Before you enable this component: 1. Read the [Authenticating to Azure]({{< ref "authenticating-azure.md" >}}) document. -1. Create an [Azure AD application]({{< ref "howto-aad.md" >}}) (also called a Service Principal). +1. Create an [Microsoft Entra ID application]({{< ref "howto-aad.md" >}}) (also called a Service Principal). 1. Alternatively, create a [managed identity]({{< ref "howto-mi.md" >}}) for your application platform. ## Spec metadata fields diff --git a/daprdocs/content/en/reference/components-reference/supported-middleware/middleware-bearer.md b/daprdocs/content/en/reference/components-reference/supported-middleware/middleware-bearer.md index a075548854f..d47c769a93b 100644 --- a/daprdocs/content/en/reference/components-reference/supported-middleware/middleware-bearer.md +++ b/daprdocs/content/en/reference/components-reference/supported-middleware/middleware-bearer.md @@ -42,7 +42,7 @@ spec: Common values for `issuer` include: - Auth0: `https://{domain}`, where `{domain}` is the domain of your Auth0 application -- Azure AD: `https://login.microsoftonline.com/{tenant}/v2.0`, where `{tenant}` should be replaced with the tenant ID of your application, as a UUID +- Microsoft Entra ID: `https://login.microsoftonline.com/{tenant}/v2.0`, where `{tenant}` should be replaced with the tenant ID of your application, as a UUID - Google: `https://accounts.google.com` - Salesforce (Force.com): `https://login.salesforce.com` diff --git a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-azure-eventhubs.md b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-azure-eventhubs.md index 40d63bdfe75..215d93bf44e 100644 --- a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-azure-eventhubs.md +++ b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-azure-eventhubs.md @@ -23,10 +23,10 @@ spec: version: v1 metadata: # Either connectionString or eventHubNamespace is required - # Use connectionString when *not* using Azure AD + # Use connectionString when *not* using Microsoft Entra ID - name: connectionString value: "Endpoint=sb://{EventHubNamespace}.servicebus.windows.net/;SharedAccessKeyName={PolicyName};SharedAccessKey={Key};EntityPath={EventHub}" - # Use eventHubNamespace when using Azure AD + # Use eventHubNamespace when using Microsoft Entra ID - name: eventHubNamespace value: "namespace" - name: consumerID # Optional. If not supplied, the runtime will create one. @@ -62,11 +62,11 @@ The above example uses secrets as plain strings. It is recommended to use a secr | Field | Required | Details | Example | |--------------------|:--------:|---------|---------| -| `connectionString` | Y* | Connection string for the Event Hub or the Event Hub namespace.
* Mutally exclusive with `eventHubNamespace` field.
* Required when not using [Azure AD Authentication]({{< ref "authenticating-azure.md" >}}) | `"Endpoint=sb://{EventHubNamespace}.servicebus.windows.net/;SharedAccessKeyName={PolicyName};SharedAccessKey={Key};EntityPath={EventHub}"` or `"Endpoint=sb://{EventHubNamespace}.servicebus.windows.net/;SharedAccessKeyName={PolicyName};SharedAccessKey={Key}"` -| `eventHubNamespace` | Y* | The Event Hub Namespace name.
* Mutally exclusive with `connectionString` field.
* Required when using [Azure AD Authentication]({{< ref "authenticating-azure.md" >}}) | `"namespace"` +| `connectionString` | Y* | Connection string for the Event Hub or the Event Hub namespace.
* Mutally exclusive with `eventHubNamespace` field.
* Required when not using [Microsoft Entra ID Authentication]({{< ref "authenticating-azure.md" >}}) | `"Endpoint=sb://{EventHubNamespace}.servicebus.windows.net/;SharedAccessKeyName={PolicyName};SharedAccessKey={Key};EntityPath={EventHub}"` or `"Endpoint=sb://{EventHubNamespace}.servicebus.windows.net/;SharedAccessKeyName={PolicyName};SharedAccessKey={Key}"` +| `eventHubNamespace` | Y* | The Event Hub Namespace name.
* Mutally exclusive with `connectionString` field.
* Required when using [Microsoft Entra ID Authentication]({{< ref "authenticating-azure.md" >}}) | `"namespace"` | `consumerID` | N | Consumer ID (consumer tag) organizes one or more consumers into a group. Consumers with the same consumer ID work as one virtual consumer; for example, a message is processed only once by one of the consumers in the group. If the `consumerID` is not provided, the Dapr runtime set it to the Dapr application ID (`appID`) value. | `"channel1"` | `storageAccountName` | Y | Storage account name to use for the checkpoint store. |`"myeventhubstorage"` -| `storageAccountKey` | Y* | Storage account key for the checkpoint store account.
* When using Azure AD, it's possible to omit this if the service principal has access to the storage account too. | `"112233445566778899"` +| `storageAccountKey` | Y* | Storage account key for the checkpoint store account.
* When using Microsoft Entra ID, it's possible to omit this if the service principal has access to the storage account too. | `"112233445566778899"` | `storageConnectionString` | Y* | Connection string for the checkpoint store, alternative to specifying `storageAccountKey` | `"DefaultEndpointsProtocol=https;AccountName=myeventhubstorage;AccountKey="` | `storageContainerName` | Y | Storage container name for the storage account name. | `"myeventhubstoragecontainer"` | `enableEntityManagement` | N | Boolean value to allow management of the EventHub namespace and storage account. Default: `false` | `"true", "false"` @@ -75,9 +75,9 @@ The above example uses secrets as plain strings. It is recommended to use a secr | `partitionCount` | N | Number of partitions for the new Event Hub namespace. Used only when entity management is enabled. Default: `"1"` | `"2"` | `messageRetentionInDays` | N | Number of days to retain messages for in the newly created Event Hub namespace. Used only when entity management is enabled. Default: `"1"` | `"90"` -### Azure Active Directory (AAD) authentication +### Microsoft Entra ID authentication -The Azure Event Hubs pub/sub component supports authentication using all Azure Active Directory mechanisms. For further information and the relevant component metadata fields to provide depending on the choice of AAD authentication mechanism, see the [docs for authenticating to Azure]({{< ref authenticating-azure.md >}}). +The Azure Event Hubs pub/sub component supports authentication using all Microsoft Entra ID mechanisms. For further information and the relevant component metadata fields to provide depending on the choice of Microsoft Entra ID authentication mechanism, see the [docs for authenticating to Azure]({{< ref authenticating-azure.md >}}). #### Example Configuration @@ -110,7 +110,7 @@ spec: value: "1" - name: messageRetentionInDays # Checkpoint store attributes - # In this case, we're using Azure AD to access the storage account too + # In this case, we're using Microsoft Entra ID to access the storage account too - name: storageAccountName value: "myeventhubstorage" - name: storageContainerName @@ -191,7 +191,7 @@ When entity management is enabled in the metadata, as long as the application ha The Evet Hub name is the `topic` field in the incoming request to publish or subscribe to, while the consumer group name is the name of the Dapr app which subscribes to a given Event Hub. For example, a Dapr app running on Kubernetes with name `dapr.io/app-id: "myapp"` requires an Event Hubs consumer group named `myapp`. -Entity management is only possible when using [Azure AD Authentication]({{< ref "authenticating-azure.md" >}}) and not using a connection string. +Entity management is only possible when using [Microsoft Entra ID Authentication]({{< ref "authenticating-azure.md" >}}) and not using a connection string. > Dapr passes the name of the consumer group to the Event Hub, so this is not supplied in the metadata. diff --git a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-azure-servicebus-queues.md b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-azure-servicebus-queues.md index e98df4814f3..57e3b92868d 100644 --- a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-azure-servicebus-queues.md +++ b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-azure-servicebus-queues.md @@ -25,7 +25,7 @@ spec: type: pubsub.azure.servicebus.queues version: v1 metadata: - # Required when not using Azure AD Authentication + # Required when not using Microsoft Entra ID Authentication - name: connectionString value: "Endpoint=sb://{ServiceBusNamespace}.servicebus.windows.net/;SharedAccessKeyName={PolicyName};SharedAccessKey={Key};EntityPath={ServiceBus}" # - name: consumerID # Optional @@ -70,9 +70,9 @@ The above example uses secrets as plain strings. It is recommended to use a secr | Field | Required | Details | Example | |--------------------|:--------:|---------|---------| -| `connectionString` | Y | Shared access policy connection string for the Service Bus. Required unless using Azure AD authentication. | See example above +| `connectionString` | Y | Shared access policy connection string for the Service Bus. Required unless using Microsoft Entra ID authentication. | See example above | `consumerID` | N | Consumer ID (consumer tag) organizes one or more consumers into a group. Consumers with the same consumer ID work as one virtual consumer; for example, a message is processed only once by one of the consumers in the group. If the `consumerID` is not provided, the Dapr runtime set it to the Dapr application ID (`appID`) value. | `"channel1"` -| `namespaceName`| N | Parameter to set the address of the Service Bus namespace, as a fully-qualified domain name. Required if using Azure AD authentication. | `"namespace.servicebus.windows.net"` | +| `namespaceName`| N | Parameter to set the address of the Service Bus namespace, as a fully-qualified domain name. Required if using Microsoft Entra ID authentication. | `"namespace.servicebus.windows.net"` | | `timeoutInSec` | N | Timeout for sending messages and for management operations. Default: `60` |`30` | `handlerTimeoutInSec`| N | Timeout for invoking the app's handler. Default: `60` | `30` | `lockRenewalInSec` | N | Defines the frequency at which buffered message locks will be renewed. Default: `20`. | `20` @@ -89,9 +89,9 @@ The above example uses secrets as plain strings. It is recommended to use a secr | `publishMaxRetries` | N | The max number of retries for when Azure Service Bus responds with "too busy" in order to throttle messages. Defaults: `5` | `5` | `publishInitialRetryIntervalInMs` | N | Time in milliseconds for the initial exponential backoff when Azure Service Bus throttle messages. Defaults: `500` | `500` -### Azure Active Directory (AAD) authentication +### Microsoft Entra ID authentication -The Azure Service Bus Queues pubsub component supports authentication using all Azure Active Directory mechanisms, including Managed Identities. For further information and the relevant component metadata fields to provide depending on the choice of AAD authentication mechanism, see the [docs for authenticating to Azure]({{< ref authenticating-azure.md >}}). +The Azure Service Bus Queues pubsub component supports authentication using all Microsoft Entra ID mechanisms, including Managed Identities. For further information and the relevant component metadata fields to provide depending on the choice of Microsoft Entra ID authentication mechanism, see the [docs for authenticating to Azure]({{< ref authenticating-azure.md >}}). #### Example Configuration diff --git a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-azure-servicebus-topics.md b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-azure-servicebus-topics.md index 7d9ab5b1672..157f960da3e 100644 --- a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-azure-servicebus-topics.md +++ b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-azure-servicebus-topics.md @@ -26,7 +26,7 @@ spec: type: pubsub.azure.servicebus.topics version: v1 metadata: - # Required when not using Azure AD Authentication + # Required when not using Microsoft Entra ID Authentication - name: connectionString value: "Endpoint=sb://{ServiceBusNamespace}.servicebus.windows.net/;SharedAccessKeyName={PolicyName};SharedAccessKey={Key};EntityPath={ServiceBus}" # - name: consumerID # Optional: defaults to the app's own ID @@ -73,8 +73,8 @@ The above example uses secrets as plain strings. It is recommended to use a secr | Field | Required | Details | Example | |--------------------|:--------:|---------|---------| -| `connectionString` | Y | Shared access policy connection string for the Service Bus. Required unless using Azure AD authentication. | See example above -| `namespaceName`| N | Parameter to set the address of the Service Bus namespace, as a fully-qualified domain name. Required if using Azure AD authentication. | `"namespace.servicebus.windows.net"` | +| `connectionString` | Y | Shared access policy connection string for the Service Bus. Required unless using Microsoft Entra ID authentication. | See example above +| `namespaceName`| N | Parameter to set the address of the Service Bus namespace, as a fully-qualified domain name. Required if using Microsoft Entra ID authentication. | `"namespace.servicebus.windows.net"` | | `consumerID` | N | Consumer ID (consumer tag) organizes one or more consumers into a group. Consumers with the same consumer ID work as one virtual consumer; for example, a message is processed only once by one of the consumers in the group. If the `consumerID` is not provided, the Dapr runtime set it to the Dapr application ID (`appID`) value. (`appID`) value. | | `timeoutInSec` | N | Timeout for sending messages and for management operations. Default: `60` |`30` | `handlerTimeoutInSec`| N | Timeout for invoking the app's handler. Default: `60` | `30` @@ -92,9 +92,9 @@ The above example uses secrets as plain strings. It is recommended to use a secr | `publishMaxRetries` | N | The max number of retries for when Azure Service Bus responds with "too busy" in order to throttle messages. Defaults: `5` | `5` | `publishInitialRetryIntervalInMs` | N | Time in milliseconds for the initial exponential backoff when Azure Service Bus throttle messages. Defaults: `500` | `500` -### Azure Active Directory (AAD) authentication +### Microsoft Entra ID authentication -The Azure Service Bus Topics pubsub component supports authentication using all Azure Active Directory mechanisms, including Managed Identities. For further information and the relevant component metadata fields to provide depending on the choice of AAD authentication mechanism, see the [docs for authenticating to Azure]({{< ref authenticating-azure.md >}}). +The Azure Service Bus Topics pubsub component supports authentication using all Microsoft Entra ID mechanisms, including Managed Identities. For further information and the relevant component metadata fields to provide depending on the choice of Microsoft Entra ID authentication mechanism, see the [docs for authenticating to Azure]({{< ref authenticating-azure.md >}}). #### Example Configuration diff --git a/daprdocs/content/en/reference/components-reference/supported-secret-stores/azure-keyvault.md b/daprdocs/content/en/reference/components-reference/supported-secret-stores/azure-keyvault.md index 57286c1b3bd..b5860fe9941 100644 --- a/daprdocs/content/en/reference/components-reference/supported-secret-stores/azure-keyvault.md +++ b/daprdocs/content/en/reference/components-reference/supported-secret-stores/azure-keyvault.md @@ -36,11 +36,11 @@ spec: value : "[pfx_certificate_file_fully_qualified_local_path]" ``` -## Authenticating with Azure AD +## Authenticating with Microsoft Entra ID -The Azure Key Vault secret store component supports authentication with Azure AD only. Before you enable this component: +The Azure Key Vault secret store component supports authentication with Microsoft Entra ID only. Before you enable this component: 1. Read the [Authenticating to Azure]({{< ref authenticating-azure.md >}}) document. -1. Create an Azure AD application (also called Service Principal). +1. Create an Microsoft Entra ID application (also called Service Principal). 1. Alternatively, create a managed identity for your application platform. ## Spec metadata fields @@ -70,7 +70,7 @@ Query Parameter | Description - [Azure CLI](https://docs.microsoft.com/cli/azure/install-azure-cli) - [jq](https://stedolan.github.io/jq/download/) - You are using bash or zsh shell -- You've created an Azure AD application (Service Principal) per the instructions in [Authenticating to Azure]({{< ref authenticating-azure.md >}}). You will need the following values: +- You've created an Microsoft Entra ID application (Service Principal) per the instructions in [Authenticating to Azure]({{< ref authenticating-azure.md >}}). You will need the following values: | Value | Description | | ----- | ----------- | @@ -113,7 +113,7 @@ Query Parameter | Description --location "${LOCATION}" ``` -1. Using RBAC, assign a role to the Azure AD application so it can access the Key Vault. +1. Using RBAC, assign a role to the Microsoft Entra ID application so it can access the Key Vault. In this case, assign the "Key Vault Secrets User" role, which has the "Get secrets" permission over Azure Key Vault. ```sh @@ -133,7 +133,7 @@ Other less restrictive roles, like "Key Vault Secrets Officer" and "Key Vault Ad #### Using a client secret -To use a **client secret**, create a file called `azurekeyvault.yaml` in the components directory. Use the following template, filling in [the Azure AD application you created]({{< ref authenticating-azure.md >}}): +To use a **client secret**, create a file called `azurekeyvault.yaml` in the components directory. Use the following template, filling in [the Microsoft Entra ID application you created]({{< ref authenticating-azure.md >}}): ```yaml apiVersion: dapr.io/v1alpha1 @@ -156,7 +156,7 @@ spec: #### Using a certificate -If you want to use a **certificate** saved on the local disk instead, use the following template. Fill in the details of [the Azure AD application you created]({{< ref authenticating-azure.md >}}): +If you want to use a **certificate** saved on the local disk instead, use the following template. Fill in the details of [the Microsoft Entra ID application you created]({{< ref authenticating-azure.md >}}): ```yaml apiVersion: dapr.io/v1alpha1 @@ -179,7 +179,7 @@ spec: {{% /codetab %}} {{% codetab %}} -In Kubernetes, you store the client secret or the certificate into the Kubernetes Secret Store and then refer to those in the YAML file. Before you start, you need the details of [the Azure AD application you created]({{< ref authenticating-azure.md >}}). +In Kubernetes, you store the client secret or the certificate into the Kubernetes Secret Store and then refer to those in the YAML file. Before you start, you need the details of [the Microsoft Entra ID application you created]({{< ref authenticating-azure.md >}}). #### Using a client secret @@ -298,11 +298,11 @@ In Kubernetes, you store the client secret or the certificate into the Kubernete kubectl apply -f azurekeyvault.yaml ``` 1. Create and assign a managed identity at the pod-level via either: - - [Azure AD workload identity](https://learn.microsoft.com/azure/aks/workload-identity-overview) (preferred method) - - [Azure AD pod identity](https://docs.microsoft.com/azure/aks/use-azure-ad-pod-identity#create-a-pod-identity) + - [Microsoft Entra ID workload identity](https://learn.microsoft.com/azure/aks/workload-identity-overview) (preferred method) + - [Microsoft Entra ID pod identity](https://docs.microsoft.com/azure/aks/use-azure-ad-pod-identity#create-a-pod-identity) - **Important**: While both Azure AD pod identity and workload identity are in preview, currently Azure AD Workload Identity is planned for general availability (stable state). + **Important**: While both Microsoft Entra ID pod identity and workload identity are in preview, currently Microsoft Entra ID Workload Identity is planned for general availability (stable state). 1. After creating a workload identity, give it `read` permissions: - [On your desired KeyVault instance](https://docs.microsoft.com/azure/key-vault/general/assign-access-policy?tabs=azure-cli#assign-the-access-policy) @@ -319,11 +319,11 @@ In Kubernetes, you store the client secret or the certificate into the Kubernete aadpodidbinding: $POD_IDENTITY_NAME ``` -#### Using Azure managed identity directly vs. via Azure AD workload identity +#### Using Azure managed identity directly vs. via Microsoft Entra ID workload identity When using **managed identity directly**, you can have multiple identities associated with an app, requiring `azureClientId` to specify which identity should be used. -However, when using **managed identity via Azure AD workload identity**, `azureClientId` is not necessary and has no effect. The Azure identity to be used is inferred from the service account tied to an Azure identity via the Azure federated identity. +However, when using **managed identity via Microsoft Entra ID workload identity**, `azureClientId` is not necessary and has no effect. The Azure identity to be used is inferred from the service account tied to an Azure identity via the Azure federated identity. {{% /codetab %}} diff --git a/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-azure-blobstorage.md b/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-azure-blobstorage.md index f4922097cb4..61846c3beff 100644 --- a/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-azure-blobstorage.md +++ b/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-azure-blobstorage.md @@ -37,7 +37,7 @@ The above example uses secrets as plain strings. It is recommended to use a secr | Field | Required | Details | Example | |--------------------|:--------:|---------|---------| | `accountName` | Y | The storage account name | `"mystorageaccount"`. -| `accountKey` | Y (unless using Azure AD) | Primary or secondary storage key | `"key"` +| `accountKey` | Y (unless using Microsoft Entra ID) | Primary or secondary storage key | `"key"` | `containerName` | Y | The name of the container to be used for Dapr state. The container will be created for you if it doesn't exist | `"container"` | `azureEnvironment` | N | Optional name for the Azure environment if using a different Azure cloud | `"AZUREPUBLICCLOUD"` (default value), `"AZURECHINACLOUD"`, `"AZUREUSGOVERNMENTCLOUD"`, `"AZUREGERMANCLOUD"` | `endpoint` | N | Optional custom endpoint URL. This is useful when using the [Azurite emulator](https://github.com/Azure/azurite) or when using custom domains for Azure Storage (although this is not officially supported). The endpoint must be the full base URL, including the protocol (`http://` or `https://`), the IP or FQDN, and optional port. | `"http://127.0.0.1:10000"` @@ -60,9 +60,9 @@ In order to setup Azure Blob Storage as a state store, you will need the followi - **accountKey**: Primary or secondary storage account key. - **containerName**: The name of the container to be used for Dapr state. The container will be created for you if it doesn't exist. -### Authenticating with Azure AD +### Authenticating with Microsoft Entra ID -This component supports authentication with Azure AD as an alternative to use account keys. Whenever possible, it is recommended that you use Azure AD for authentication in production systems, to take advantage of better security, fine-tuned access control, and the ability to use managed identities for apps running on Azure. +This component supports authentication with Microsoft Entra ID as an alternative to use account keys. Whenever possible, it is recommended that you use Microsoft Entra ID for authentication in production systems, to take advantage of better security, fine-tuned access control, and the ability to use managed identities for apps running on Azure. > The following scripts are optimized for a bash or zsh shell and require the following apps installed: > @@ -71,7 +71,7 @@ This component supports authentication with Azure AD as an alternative to use ac > > You must also be authenticated with Azure in your Azure CLI. -1. To get started with using Azure AD for authenticating the Blob Storage state store component, make sure you've created an Azure AD application and a Service Principal as explained in the [Authenticating to Azure]({{< ref authenticating-azure.md >}}) document. +1. To get started with using Microsoft Entra ID for authenticating the Blob Storage state store component, make sure you've created an Microsoft Entra ID application and a Service Principal as explained in the [Authenticating to Azure]({{< ref authenticating-azure.md >}}) document. Once done, set a variable with the ID of the Service Principal that you created: ```sh @@ -96,7 +96,7 @@ This component supports authentication with Azure AD as an alternative to use ac --scope "${RG_ID}/providers/Microsoft.Storage/storageAccounts/${STORAGE_ACCOUNT_NAME}" ``` -When authenticating your component using Azure AD, the `accountKey` field is not required. Instead, please specify the required credentials in the component's metadata (if any) according to the [Authenticating to Azure]({{< ref authenticating-azure.md >}}) document. +When authenticating your component using Microsoft Entra ID, the `accountKey` field is not required. Instead, please specify the required credentials in the component's metadata (if any) according to the [Authenticating to Azure]({{< ref authenticating-azure.md >}}) document. For example: diff --git a/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-azure-cosmosdb.md b/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-azure-cosmosdb.md index a1f4f59b935..0d636a452d3 100644 --- a/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-azure-cosmosdb.md +++ b/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-azure-cosmosdb.md @@ -46,14 +46,14 @@ If you wish to use Cosmos DB as an actor store, append the following to the yam | Field | Required | Details | Example | |--------------------|:--------:|---------|---------| | url | Y | The Cosmos DB url | `"https://******.documents.azure.com:443/"`. -| masterKey | Y* | The key to authenticate to the Cosmos DB account. Only required when not using Azure AD authentication. | `"key"` +| masterKey | Y* | The key to authenticate to the Cosmos DB account. Only required when not using Microsoft Entra ID authentication. | `"key"` | database | Y | The name of the database | `"db"` | collection | Y | The name of the collection (container) | `"collection"` | actorStateStore | N | Consider this state store for actors. Defaults to `"false"` | `"true"`, `"false"` -### Azure Active Directory (Azure AD) authentication +### Microsoft Entra ID authentication -The Azure Cosmos DB state store component supports authentication using all Azure Active Directory mechanisms. For further information and the relevant component metadata fields to provide depending on the choice of Azure AD authentication mechanism, see the [docs for authenticating to Azure]({{< ref authenticating-azure.md >}}). +The Azure Cosmos DB state store component supports authentication using all Microsoft Entra ID mechanisms. For further information and the relevant component metadata fields to provide depending on the choice of Microsoft Entra ID authentication mechanism, see the [docs for authenticating to Azure]({{< ref authenticating-azure.md >}}). You can read additional information for setting up Cosmos DB with Azure AD authentication in the [section below](#setting-up-cosmos-db-for-authenticating-with-azure-ad). @@ -66,7 +66,7 @@ You can read additional information for setting up Cosmos DB with Azure AD aut In order to setup Cosmos DB as a state store, you need the following properties: - **URL**: the Cosmos DB url. for example: `https://******.documents.azure.com:443/` -- **Master Key**: The key to authenticate to the Cosmos DB account. Skip this if using Azure AD authentication. +- **Master Key**: The key to authenticate to the Cosmos DB account. Skip this if using Microsoft Entra ID authentication. - **Database**: The name of the database - **Collection**: The name of the collection (or container) @@ -136,9 +136,9 @@ curl -X POST http://localhost:3500/v1.0/state/ \ For **actor** state operations, the partition key is generated by Dapr using the `appId`, the actor type, and the actor id, such that data for the same actor always ends up under the same partition (you do not need to specify it). This is because actor state operations must use transactions, and in Cosmos DB the items in a transaction must be on the same partition. -## Setting up Cosmos DB for authenticating with Azure AD +## Setting up Cosmos DB for authenticating with Microsoft Entra ID -When using the Dapr Cosmos DB state store and authenticating with Azure AD, you need to perform a few additional steps to set up your environment. +When using the Dapr Cosmos DB state store and authenticating with Microsoft Entra ID, you need to perform a few additional steps to set up your environment. Prerequisites: @@ -147,7 +147,7 @@ Prerequisites: - [jq](https://stedolan.github.io/jq/download/) - The scripts below are optimized for a bash or zsh shell -### Granting your Azure AD application access to Cosmos DB +### Granting your Microsoft Entra ID application access to Cosmos DB > You can find more information on the [official documentation](https://docs.microsoft.com/azure/cosmos-db/how-to-setup-rbac), including instructions to assign more granular permissions. diff --git a/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-azure-tablestorage.md b/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-azure-tablestorage.md index 64fa12c828c..5d8e8cfe672 100644 --- a/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-azure-tablestorage.md +++ b/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-azure-tablestorage.md @@ -45,11 +45,11 @@ The above example uses secrets as plain strings. It is recommended to use a secr | `serviceURL` | N | The full storage service endpoint URL. Useful for Azure environments other than public cloud. | `"https://mystorageaccount.table.core.windows.net/"` | `skipCreateTable` | N | Skips the check for and, if necessary, creation of the specified storage table. This is useful when using active directory authentication with minimal privileges. Defaults to `false`. | `"true"` -### Azure Active Directory (Azure AD) authentication +### Microsoft Entra ID authentication -The Azure Cosmos DB state store component supports authentication using all Azure Active Directory mechanisms. For further information and the relevant component metadata fields to provide depending on the choice of Azure AD authentication mechanism, see the [docs for authenticating to Azure]({{< ref authenticating-azure.md >}}). +The Azure Cosmos DB state store component supports authentication using all Microsoft Entra ID mechanisms. For further information and the relevant component metadata fields to provide depending on the choice of Microsoft Entra ID authentication mechanism, see the [docs for authenticating to Azure]({{< ref authenticating-azure.md >}}). -You can read additional information for setting up Cosmos DB with Azure AD authentication in the [section below](#setting-up-cosmos-db-for-authenticating-with-azure-ad). +You can read additional information for setting up Cosmos DB with Microsoft Entra ID authentication in the [section below](#setting-up-cosmos-db-for-authenticating-with-azure-ad). ## Option 1: Setup Azure Table Storage @@ -59,7 +59,7 @@ If you wish to create a table for Dapr to use, you can do so beforehand. However In order to setup Azure Table Storage as a state store, you will need the following properties: - **AccountName**: The storage account name. For example: **mystorageaccount**. -- **AccountKey**: Primary or secondary storage key. Skip this if using Azure AD authentication. +- **AccountKey**: Primary or secondary storage key. Skip this if using Microsoft Entra ID authentication. - **TableName**: The name of the table to be used for Dapr state. The table will be created for you if it doesn't exist, unless the `skipCreateTable` option is enabled. - **cosmosDbMode**: Set this to `false` to connect to Azure Tables. @@ -71,7 +71,7 @@ If you wish to create a table for Dapr to use, you can do so beforehand. However In order to setup Azure Cosmos DB Table API as a state store, you will need the following properties: - **AccountName**: The Cosmos DB account name. For example: **mycosmosaccount**. -- **AccountKey**: The Cosmos DB master key. Skip this if using Azure AD authentication. +- **AccountKey**: The Cosmos DB master key. Skip this if using Microsoft Entra ID authentication. - **TableName**: The name of the table to be used for Dapr state. The table will be created for you if it doesn't exist, unless the `skipCreateTable` option is enabled. - **cosmosDbMode**: Set this to `true` to connect to Azure Tables. diff --git a/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-postgresql.md b/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-postgresql.md index 0d5c682422e..5035d8fae03 100644 --- a/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-postgresql.md +++ b/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-postgresql.md @@ -61,15 +61,15 @@ The following metadata options are **required** to authenticate using a PostgreS |--------|:--------:|---------|---------| | `connectionString` | Y | The connection string for the PostgreSQL database. See the PostgreSQL [documentation on database connections](https://www.postgresql.org/docs/current/libpq-connect.html) for information on how to define a connection string. | `"host=localhost user=postgres password=example port=5432 connect_timeout=10 database=my_db"` -### Authenticate using Azure AD +### Authenticate using Microsoft Entra ID -Authenticating with Azure AD is supported with Azure Database for PostgreSQL. All authentication methods supported by Dapr can be used, including client credentials ("service principal") and Managed Identity. +Authenticating with Microsoft Entra ID is supported with Azure Database for PostgreSQL. All authentication methods supported by Dapr can be used, including client credentials ("service principal") and Managed Identity. | Field | Required | Details | Example | |--------|:--------:|---------|---------| -| `useAzureAD` | Y | Must be set to `true` to enable the component to retrieve access tokens from Azure AD. | `"true"` | -| `connectionString` | Y | The connection string for the PostgreSQL database.
This must contain the user, which corresponds to the name of the user created inside PostgreSQL that maps to the Azure AD identity; this is often the name of the corresponding principal (e.g. the name of the Azure AD application). This connection string should not contain any password. | `"host=mydb.postgres.database.azure.com user=myapplication port=5432 database=my_db sslmode=require"` | -| `azureTenantId` | N | ID of the Azure AD tenant | `"cd4b2887-304c-…"` | +| `useAzureAD` | Y | Must be set to `true` to enable the component to retrieve access tokens from Microsoft Entra ID. | `"true"` | +| `connectionString` | Y | The connection string for the PostgreSQL database.
This must contain the user, which corresponds to the name of the user created inside PostgreSQL that maps to the Microsoft Entra ID identity; this is often the name of the corresponding principal (e.g. the name of the Microsoft Entra ID application). This connection string should not contain any password. | `"host=mydb.postgres.database.azure.com user=myapplication port=5432 database=my_db sslmode=require"` | +| `azureTenantId` | N | ID of the Microsoft Entra ID tenant | `"cd4b2887-304c-…"` | | `azureClientId` | N | Client ID (application ID) | `"c7dd251f-811f-…"` | | `azureClientSecret` | N | Client secret (application password) | `"Ecy3X…"` | diff --git a/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-sqlserver.md b/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-sqlserver.md index e4f48d547b6..96d79ac9d64 100644 --- a/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-sqlserver.md +++ b/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-sqlserver.md @@ -28,7 +28,7 @@ spec: value: | Server=myServerName\myInstanceName;Database=myDataBase;User Id=myUsername;Password=myPassword; - # Authenticate with Azure AD (Azure SQL only) + # Authenticate with Microsoft Entra ID (Azure SQL only) # "useAzureAD" be set to "true" - name: useAzureAD value: true @@ -75,15 +75,15 @@ The following metadata options are **required** to authenticate using SQL Server |--------|:--------:|---------|---------| | `connectionString` | Y | The connection string used to connect.
If the connection string contains the database, it must already exist. Otherwise, if the database is omitted, a default database named "Dapr" is created. | `"Server=myServerName\myInstanceName;Database=myDataBase;User Id=myUsername;Password=myPassword;"` | -### Authenticate using Azure AD +### Authenticate using Microsoft Entra ID -Authenticating with Azure AD is supported with Azure SQL only. All authentication methods supported by Dapr can be used, including client credentials ("service principal") and Managed Identity. +Authenticating with Microsoft Entra ID is supported with Azure SQL only. All authentication methods supported by Dapr can be used, including client credentials ("service principal") and Managed Identity. | Field | Required | Details | Example | |--------|:--------:|---------|---------| -| `useAzureAD` | Y | Must be set to `true` to enable the component to retrieve access tokens from Azure AD. | `"true"` | +| `useAzureAD` | Y | Must be set to `true` to enable the component to retrieve access tokens from Microsoft Entra ID. | `"true"` | | `connectionString` | Y | The connection string or URL of the Azure SQL database, **without credentials**.
If the connection string contains the database, it must already exist. Otherwise, if the database is omitted, a default database named "Dapr" is created. | `"sqlserver://myServerName.database.windows.net:1433?database=myDataBase"` | -| `azureTenantId` | N | ID of the Azure AD tenant | `"cd4b2887-304c-47e1-b4d5-65447fdd542b"` | +| `azureTenantId` | N | ID of the Microsoft Entra ID tenant | `"cd4b2887-304c-47e1-b4d5-65447fdd542b"` | | `azureClientId` | N | Client ID (application ID) | `"c7dd251f-811f-4ba2-a905-acd4d3f8f08b"` | | `azureClientSecret` | N | Client secret (application password) | `"Ecy3XG7zVZK3/vl/a2NSB+a1zXLa8RnMum/IgD0E"` | From 457416da684704230c524f534b1edb2934162abe Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Wed, 25 Oct 2023 10:45:39 -0400 Subject: [PATCH 140/162] add javascript quickstart for crypto Signed-off-by: Hannah Hunter --- .../quickstarts/cryptography-quickstart.md | 204 +++++++++++++++++- 1 file changed, 203 insertions(+), 1 deletion(-) diff --git a/daprdocs/content/en/getting-started/quickstarts/cryptography-quickstart.md b/daprdocs/content/en/getting-started/quickstarts/cryptography-quickstart.md index 7da6714cea1..8959672d09a 100644 --- a/daprdocs/content/en/getting-started/quickstarts/cryptography-quickstart.md +++ b/daprdocs/content/en/getting-started/quickstarts/cryptography-quickstart.md @@ -23,7 +23,209 @@ This example uses the Dapr SDK, which leverages gRPC and is **strongly** recomme Currently, you can experience the cryptography API using the Go SDK. -{{< tabs "Go" >}} +{{< tabs "JavaScript" "Go" >}} + + +{{% codetab %}} + +> This quickstart includes a JavaScript application called `crypto-quickstart`. + +### Pre-requisites + +For this example, you will need: + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started). +- [Latest Node.js installed](https://nodejs.org/download/). + +- [Docker Desktop](https://www.docker.com/products/docker-desktop) + +- [OpenSSL](https://www.openssl.org/source/) available on your system + +### Step 1: Set up the environment + +Clone the [sample provided in the Quickstarts repo](https://github.com/dapr/quickstarts/tree/master/cryptography) + +```bash +git clone https://github.com/dapr/quickstarts.git +``` + +In the terminal, from the root directory, navigate to the cryptography sample. + +```bash +cd cryptography/javascript/sdk +``` + +Navigate into the folder with the source code: + +```bash +cd ./crypto-quickstart +``` + +Install the dependencies: + +```bash +npm install +``` + +### Step 2: Run the application with Dapr + +The application code defines two required keys: + +- Private RSA key +- A 256-bit symmetric (AES) key + +Generate two keys, an RSA key and and AES key using OpenSSL and write these to two files: + +```bash +mkdir -p keys +# Generate a private RSA key, 4096-bit keys +openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out keys/rsa-private-key.pem +# Generate a 256-bit key for AES +openssl rand -out keys/symmetric-key-256 32 +``` + +Run the Go service app with Dapr: + +```bash +dapr run --app-id crypto-quickstart --resources-path ../../../components/ -- npm start +``` + +**Expected output** + +``` +== APP == 2023-10-25T14:30:50.435Z INFO [GRPCClient, GRPCClient] Opening connection to 127.0.0.1:58173 +== APP == == Encrypting message using buffers +== APP == Encrypted the message, got 856 bytes +== APP == == Decrypting message using buffers +== APP == Decrypted the message, got 24 bytes +== APP == The secret is "passw0rd" +== APP == == Encrypting message using streams +== APP == Encrypting federico-di-dio-photography-Q4g0Q-eVVEg-unsplash.jpg to encrypted.out +== APP == Encrypted the message to encrypted.out +== APP == == Decrypting message using streams +== APP == Decrypting encrypted.out to decrypted.out.jpg +== APP == Decrypted the message to decrypted.out.jpg +``` + +### What happened? + +#### `local-storage.yaml` + +Earlier, you created a directory inside `crypto-quickstarts` called `keys`. In [the `local-storage` component YAML](https://github.com/dapr/quickstarts/tree/master/cryptography/components/local-storage.yaml), the `path` metadata maps to the newly created `keys` directory. + +```yml +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: localstorage +spec: + type: crypto.dapr.localstorage + version: v1 + metadata: + - name: path + # Path is relative to the folder where the example is located + value: ./keys +``` + +#### `index.mjs` + +[The application file](https://github.com/dapr/quickstarts/blob/master/cryptography/javascript/sdk/crypto-quickstart/index.mjs) encrypts and decrypts messages and files using the RSA and AES keys that you generated. The application creates a new Dapr SDK client: + +```javascript +async function start() { + const client = new DaprClient({ + daprHost, + daprPort, + communicationProtocol: CommunicationProtocolEnum.GRPC, + }); + + // Encrypt and decrypt a message from a buffer + await encryptDecryptBuffer(client); + + // Encrypt and decrypt a message using streams + await encryptDecryptStream(client); +} +``` + +##### Encrypting and decrypting a string using the RSA key + +Once the client is created, the application encrypts a message: + +```javascript +async function encryptDecryptBuffer(client) { + // Message to encrypt + const plaintext = `The secret is "passw0rd"` + + // First, encrypt the message + console.log("== Encrypting message using buffers"); + + const encrypted = await client.crypto.encrypt(plaintext, { + componentName: "localstorage", + keyName: "rsa-private-key.pem", + keyWrapAlgorithm: "RSA", + }); + + console.log("Encrypted the message, got", encrypted.length, "bytes"); +``` + +The application then decrypts the message: + +```javascript + // Decrypt the message + console.log("== Decrypting message using buffers"); + const decrypted = await client.crypto.decrypt(encrypted, { + componentName: "localstorage", + }); + + console.log("Decrypted the message, got", decrypted.length, "bytes"); + console.log(decrypted.toString("utf8")); + + // ... +} +``` + +##### Encrypt and decrpyt a large file using the AES key + +Next, the application encrypts a large image file: + +```javascript +async function encryptDecryptStream(client) { + // First, encrypt the message + console.log("== Encrypting message using streams"); + console.log("Encrypting", testFileName, "to encrypted.out"); + + await pipeline( + createReadStream(testFileName), + await client.crypto.encrypt({ + componentName: "localstorage", + keyName: "symmetric-key-256", + keyWrapAlgorithm: "A256KW", + }), + createWriteStream("encrypted.out"), + ); + + console.log("Encrypted the message to encrypted.out"); +``` + +The application then decrypts the large image file: + +```javascript + // Decrypt the message + console.log("== Decrypting message using streams"); + console.log("Decrypting encrypted.out to decrypted.out.jpg"); + await pipeline( + createReadStream("encrypted.out"), + await client.crypto.decrypt({ + componentName: "localstorage", + }), + createWriteStream("decrypted.out.jpg"), + ); + + console.log("Decrypted the message to decrypted.out.jpg"); +} +``` + +{{% /codetab %}} {{% codetab %}} From a64e00b0e210774cefe32ed489ac7ba1613866b7 Mon Sep 17 00:00:00 2001 From: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Date: Thu, 26 Oct 2023 19:34:54 -0400 Subject: [PATCH 141/162] remove ms (#3850) Signed-off-by: Hannah Hunter --- .../supported-state-stores/setup-memcached.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-memcached.md b/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-memcached.md index fab220a6618..2d00042c199 100644 --- a/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-memcached.md +++ b/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-memcached.md @@ -25,7 +25,7 @@ spec: - name: maxIdleConnections value: # Optional. default: "2" - name: timeout - value: # Optional. default: "1000ms" + value: # Optional. default: "1000" ``` {{% alert title="Warning" color="warning" %}} @@ -38,7 +38,7 @@ The above example uses secrets as plain strings. It is recommended to use a secr |--------------------|:--------:|---------|---------| | hosts | Y | Comma delimited endpoints | `"memcached.default.svc.cluster.local:11211"` | maxIdleConnections | N | The max number of idle connections. Defaults to `"2"` | `"3"` -| timeout | N | The timeout for the calls. Defaults to `"1000ms"` | `"1000ms"` +| timeout | N | The timeout for the calls in milliseconds. Defaults to `"1000"` | `"1000"` ## Setup Memcached From 0abf03a343b798422e1c1a1f7034b67f41e7c810 Mon Sep 17 00:00:00 2001 From: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Date: Fri, 27 Oct 2023 13:34:31 -0400 Subject: [PATCH 142/162] Redis limitation about transactions (#3848) * add redis limitations note Signed-off-by: Hannah Hunter * edits Signed-off-by: Hannah Hunter --------- Signed-off-by: Hannah Hunter Signed-off-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> --- .../components-reference/supported-bindings/redis.md | 1 - .../redis-configuration-store.md | 1 + .../supported-state-stores/setup-redis.md | 4 ++++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/redis.md b/daprdocs/content/en/reference/components-reference/supported-bindings/redis.md index 0f74473d38c..e147d101c27 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/redis.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/redis.md @@ -11,7 +11,6 @@ aliases: To setup Redis binding create a component of type `bindings.redis`. See [this guide]({{< ref "howto-bindings.md#1-create-a-binding" >}}) on how to create and apply a binding configuration. - ```yaml apiVersion: dapr.io/v1alpha1 kind: Component diff --git a/daprdocs/content/en/reference/components-reference/supported-configuration-stores/redis-configuration-store.md b/daprdocs/content/en/reference/components-reference/supported-configuration-stores/redis-configuration-store.md index d4649c19502..205cc98ad70 100644 --- a/daprdocs/content/en/reference/components-reference/supported-configuration-stores/redis-configuration-store.md +++ b/daprdocs/content/en/reference/components-reference/supported-configuration-stores/redis-configuration-store.md @@ -10,6 +10,7 @@ aliases: ## Component format To setup Redis configuration store create a component of type `configuration.redis`. See [this guide]({{< ref "howto-manage-configuration.md#configure-a-dapr-configuration-store" >}}) on how to create and apply a configuration store configuration. + ```yaml apiVersion: dapr.io/v1alpha1 kind: Component diff --git a/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-redis.md b/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-redis.md index 3237b109284..834a43ebfbd 100644 --- a/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-redis.md +++ b/daprdocs/content/en/reference/components-reference/supported-state-stores/setup-redis.md @@ -11,6 +11,10 @@ aliases: To setup Redis state store create a component of type `state.redis`. See [this guide]({{< ref "howto-get-save-state.md#step-1-setup-a-state-store" >}}) on how to create and apply a state store configuration. +{{% alert title="Limitations" color="warning" %}} +Before using Redis and the Transactions API, make sure you're familiar with [Redis limitations regarding transactions](https://redis.io/docs/interact/transactions/#what-about-rollbacks). +{{% /alert %}} + ```yaml apiVersion: dapr.io/v1alpha1 kind: Component From d42df929fde36938a2413070338ea89fac3e3bc8 Mon Sep 17 00:00:00 2001 From: Jorim Van Hove Date: Fri, 27 Oct 2023 06:21:10 +0200 Subject: [PATCH 143/162] Fixes #3851 Signed-off-by: Jorim Van Hove --- translations/docs-zh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/translations/docs-zh b/translations/docs-zh index 794330f6cab..7938567259e 160000 --- a/translations/docs-zh +++ b/translations/docs-zh @@ -1 +1 @@ -Subproject commit 794330f6cab2db8e09053bb7bf19233eb3237538 +Subproject commit 7938567259e1dcaba7bb3fbfca88ed9db92cefaa From 83791e5e34651c99780d661396d9c55c03808812 Mon Sep 17 00:00:00 2001 From: Jorim Van Hove Date: Thu, 2 Nov 2023 21:17:29 +0100 Subject: [PATCH 144/162] Added module mounts for translated java, go & js sdk pages Signed-off-by: Jorim Van Hove --- daprdocs/config.toml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/daprdocs/config.toml b/daprdocs/config.toml index 3aa6d95f85a..2d2d5f0a8ec 100644 --- a/daprdocs/config.toml +++ b/daprdocs/config.toml @@ -124,6 +124,18 @@ id = "G-60C6Q1ETC1" source = "../translations/docs-zh/content/sdks_dotnet" target = "content/developing-applications/sdks/dotnet" lang = "zh-hans" + [[module.mounts]] + source = "../translations/docs-zh/content/sdks_java" + target = "content/developing-applications/sdks/java" + lang = "zh-hans" + [[module.mounts]] + source = "../translations/docs-zh/content/sdks_go" + target = "content/developing-applications/sdks/go" + lang = "zh-hans" + [[module.mounts]] + source = "../translations/docs-zh/content/sdks_js" + target = "content/developing-applications/sdks/js" + lang = "zh-hans" # Markdown Engine - Allow inline html [markup] From 976beeed4db818cdfe9d0e5fe63990371f8f5744 Mon Sep 17 00:00:00 2001 From: Stuart Leeks Date: Fri, 3 Nov 2023 13:38:12 +0000 Subject: [PATCH 145/162] Add missing arg value in multi-app-template.md Signed-off-by: Stuart Leeks --- .../local-development/multi-app-dapr-run/multi-app-template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md index 67183f7af6b..2d85efa1136 100644 --- a/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md +++ b/daprdocs/content/en/developing-applications/local-development/multi-app-dapr-run/multi-app-template.md @@ -93,7 +93,7 @@ Stop the multi-app run template anytime with either of the following commands: ```cmd # the template file needs to be called `dapr.yaml` by default if a directory path is given -dapr stop -f +dapr stop -f ``` or: From 5c53ef2e1053eb84b4f737786adc77a6f3732210 Mon Sep 17 00:00:00 2001 From: Stuart Leeks Date: Thu, 9 Nov 2023 09:30:35 +0000 Subject: [PATCH 146/162] Update broken links https://account.kubemq.io/login/register is redirected to https://account.kubemq.io/auth/signup Signed-off-by: Stuart Leeks --- .../components-reference/supported-bindings/kubemq.md | 4 ++-- .../components-reference/supported-pubsub/setup-kubemq.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/kubemq.md b/daprdocs/content/en/reference/components-reference/supported-bindings/kubemq.md index 5cf333ea213..daf7d1981ad 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/kubemq.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/kubemq.md @@ -51,7 +51,7 @@ This component supports both **input and output** binding interfaces. {{< tabs "Self-Hosted" "Kubernetes">}} {{% codetab %}} -1. Obtain KubeMQ Key by visiting [https://account.kubemq.io/login/register](https://account.kubemq.io/login/register) and register for a key. +1. Obtain KubeMQ Key by visiting [https://account.kubemq.io/auth/signup](https://account.kubemq.io/auth/signup) and register for a key. 2. Wait for an email confirmation with your Key You can run a KubeMQ broker with Docker: @@ -64,7 +64,7 @@ You can then interact with the server using the client port: `localhost:50000` {{% /codetab %}} {{% codetab %}} -1. Obtain KubeMQ Key by visiting [https://account.kubemq.io/login/register](https://account.kubemq.io/login/register) and register for a key. +1. Obtain KubeMQ Key by visiting [https://account.kubemq.io/auth/signup](https://account.kubemq.io/auth/signup) and register for a key. 2. Wait for an email confirmation with your Key Then Run the following kubectl commands: diff --git a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-kubemq.md b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-kubemq.md index 28080ac150c..2bae7c62974 100644 --- a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-kubemq.md +++ b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-kubemq.md @@ -45,7 +45,7 @@ spec: {{< tabs "Self-Hosted" "Kubernetes">}} {{% codetab %}} -1. Obtain KubeMQ Key by visiting [https://account.kubemq.io/login/register](https://account.kubemq.io/login/register) and register for a key. +1. Obtain KubeMQ Key by visiting [https://account.kubemq.io/auth/signup](https://account.kubemq.io/auth/signup) and register for a key. 2. Wait for an email confirmation with your Key You can run a KubeMQ broker with Docker: @@ -58,7 +58,7 @@ You can then interact with the server using the client port: `localhost:50000` {{% /codetab %}} {{% codetab %}} -1. Obtain KubeMQ Key by visiting [https://account.kubemq.io/login/register](https://account.kubemq.io/login/register) and register for a key. +1. Obtain KubeMQ Key by visiting [https://account.kubemq.io/auth/signup](https://account.kubemq.io/auth/signup) and register for a key. 2. Wait for an email confirmation with your Key Then Run the following kubectl commands: From 35ed773345073474bf1ac0d7f5d7eaeb5c02ba83 Mon Sep 17 00:00:00 2001 From: Anish Pai Date: Fri, 10 Nov 2023 22:02:19 -0500 Subject: [PATCH 147/162] In Section 3: Run the Dapr Sidecar, the --resources-path flag in the dapr run command was listed incorrectly. It now correctly ends in resources-path ../ Signed-off-by: Anish Pai --- .../en/getting-started/tutorials/get-started-component.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/getting-started/tutorials/get-started-component.md b/daprdocs/content/en/getting-started/tutorials/get-started-component.md index 9f460ed0e7d..9fbd776b3a6 100644 --- a/daprdocs/content/en/getting-started/tutorials/get-started-component.md +++ b/daprdocs/content/en/getting-started/tutorials/get-started-component.md @@ -65,7 +65,7 @@ In the above file definition: Launch a Dapr sidecar that will listen on port 3500 for a blank application named `myapp`: ```bash -dapr run --app-id myapp --dapr-http-port 3500 --resources-path . +dapr run --app-id myapp --dapr-http-port 3500 --resources-path ../ ``` {{% alert title="Tip" color="primary" %}} From b0d5eb2ed53b9ca070041a10a3f142aeb4874e17 Mon Sep 17 00:00:00 2001 From: Anish Pai Date: Sat, 11 Nov 2023 11:49:25 -0500 Subject: [PATCH 148/162] Created a powershell and non powershell environment version for the dapr run command in section 3 for running the Dapr Sidecar. Signed-off-by: Anish Pai --- .../en/getting-started/tutorials/get-started-component.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/daprdocs/content/en/getting-started/tutorials/get-started-component.md b/daprdocs/content/en/getting-started/tutorials/get-started-component.md index 9fbd776b3a6..7c829e5d779 100644 --- a/daprdocs/content/en/getting-started/tutorials/get-started-component.md +++ b/daprdocs/content/en/getting-started/tutorials/get-started-component.md @@ -64,9 +64,15 @@ In the above file definition: Launch a Dapr sidecar that will listen on port 3500 for a blank application named `myapp`: + +PowerShell environment: ```bash dapr run --app-id myapp --dapr-http-port 3500 --resources-path ../ ``` +non-PowerShell environment: +```bash +dapr run --app-id myapp --dapr-http-port 3500 --resources-path . +``` {{% alert title="Tip" color="primary" %}} If an error message occurs, stating the `app-id` is already in use, you may need to stop any currently running Dapr sidecars. Stop the sidecar before running the next `dapr run` command by either: From 96f5841b61b2837a736b1990621b0ccd1dd94a4e Mon Sep 17 00:00:00 2001 From: kaibocai Date: Mon, 13 Nov 2023 01:32:42 +0000 Subject: [PATCH 149/162] fix worflow examples for java Signed-off-by: kaibocai --- .../workflow/workflow-patterns.md | 173 +++++++++--------- 1 file changed, 87 insertions(+), 86 deletions(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md index 9d23a64062f..530dc1edadc 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md @@ -109,21 +109,53 @@ catch (TaskFailedException) // Task failures are surfaced as TaskFailedException ```java -public static void main(String[] args) throws InterruptedException { - DaprWorkflowClient client = new DaprWorkflowClient(); +public class ChainWorkflow extends Workflow { + @Override + public WorkflowStub create() { + return ctx -> { + StringBuilder sb = new StringBuilder(); + String wfInput = ctx.getInput(String.class); + String result1 = ctx.callActivity("event1", wfInput, String.class).await(); + String result2 = ctx.callActivity("event2", result1, String.class).await(); + String result3 = ctx.callActivity("event3", result2, String.class).await(); + String result = sb.append(result1).append(',').append(result2).append(',').append(result3).toString(); + ctx.complete(result); + }; + } +} - try (client) { - client.raiseEvent(instanceId, "TestEvent", "TestEventPayload"); + class Event1 implements WorkflowActivity { - System.out.println(separatorStr); - System.out.println("** Registering parallel Events to be captured by allOf(t1,t2,t3) **"); - client.raiseEvent(instanceId, "event1", "TestEvent 1 Payload"); - client.raiseEvent(instanceId, "event2", "TestEvent 2 Payload"); - client.raiseEvent(instanceId, "event3", "TestEvent 3 Payload"); - System.out.printf("Events raised for workflow with instanceId: %s\n", instanceId); + @Override + public Object run(WorkflowActivityContext ctx) { + Logger logger = LoggerFactory.getLogger(Event1.class); + logger.info("Starting Activity: " + ctx.getName()); + // Do some work + return null; + } + } - } -} + class Event2 implements WorkflowActivity { + + @Override + public Object run(WorkflowActivityContext ctx) { + Logger logger = LoggerFactory.getLogger(Event2.class); + logger.info("Starting Activity: " + ctx.getName()); + // Do some work + return null; + } + } + + class Event3 implements WorkflowActivity { + + @Override + public Object run(WorkflowActivityContext ctx) { + Logger logger = LoggerFactory.getLogger(Event3.class); + logger.info("Starting Activity: " + ctx.getName()); + // Do some work + return null; + } + } ``` {{% /codetab %}} @@ -225,46 +257,23 @@ await context.CallActivityAsync("PostResults", sum); ```java -public static void main(String[] args) throws InterruptedException { - DaprWorkflowClient client = new DaprWorkflowClient(); - - try (client) { - - System.out.println(separatorStr); - System.out.println("**SendExternalMessage**"); - client.raiseEvent(instanceId, "TestEvent", "TestEventPayload"); - - // Get events to process in parallel - System.out.println(separatorStr); - System.out.println("** Registering parallel Events to be captured by allOf(t1,t2,t3) **"); - client.raiseEvent(instanceId, "event1", "TestEvent 1 Payload"); - client.raiseEvent(instanceId, "event2", "TestEvent 2 Payload"); - client.raiseEvent(instanceId, "event3", "TestEvent 3 Payload"); - System.out.printf("Events raised for workflow with instanceId: %s\n", instanceId); - - // Register the raised events to be captured - System.out.println(separatorStr); - System.out.println("** Registering Event to be captured by anyOf(t1,t2,t3) **"); - client.raiseEvent(instanceId, "e2", "event 2 Payload"); - System.out.printf("Event raised for workflow with instanceId: %s\n", instanceId); - - // Wait for all tasks to complete and aggregate results - System.out.println(separatorStr); - System.out.println("**WaitForInstanceCompletion**"); - try { - WorkflowInstanceStatus waitForInstanceCompletionResult = - client.waitForInstanceCompletion(instanceId, Duration.ofSeconds(60), true); - System.out.printf("Result: %s%n", waitForInstanceCompletionResult); - } catch (TimeoutException ex) { - System.out.printf("waitForInstanceCompletion has an exception:%s%n", ex); +public class FaninoutWorkflow extends Workflow { + @Override + public WorkflowStub create() { + return ctx -> { + // Get a list of N work items to process in parallel. + Object[] workBatch = ctx.callActivity("GetWorkBatch", Object[].class).await(); + // Schedule the parallel tasks, but don't wait for them to complete yet. + List> tasks = Arrays.stream(workBatch) + .map(workItem -> ctx.callActivity("ProcessWorkItem", workItem, int.class)) + .collect(Collectors.toList()); + // Everything is scheduled. Wait here until all parallel tasks have completed. + List results = ctx.allOf(tasks).await(); + // Aggregate all N outputs and publish the result. + int sum = results.stream().mapToInt(Integer::intValue).sum(); + ctx.complete(sum); + }; } - - System.out.println(separatorStr); - System.out.println("**purgeInstance**"); - boolean purgeResult = client.purgeInstance(instanceId); - System.out.printf("purgeResult: %s%n", purgeResult); - - } } ``` @@ -640,42 +649,34 @@ public override async Task RunAsync(WorkflowContext context, OrderP ```java -public static void main(String[] args) throws InterruptedException { - DaprWorkflowClient client = new DaprWorkflowClient(); - - try (client) { - String eventInstanceId = client.scheduleNewWorkflow(DemoWorkflow.class); - System.out.printf("Started new workflow instance with random ID: %s%n", eventInstanceId); - client.raiseEvent(eventInstanceId, "TestException", null); - System.out.printf("Event raised for workflow with instanceId: %s\n", eventInstanceId); - - System.out.println(separatorStr); - String instanceToTerminateId = "terminateMe"; - client.scheduleNewWorkflow(DemoWorkflow.class, null, instanceToTerminateId); - System.out.printf("Started new workflow instance with specified ID: %s%n", instanceToTerminateId); - - TimeUnit.SECONDS.sleep(5); - System.out.println("Terminate this workflow instance manually before the timeout is reached"); - client.terminateWorkflow(instanceToTerminateId, null); - System.out.println(separatorStr); - - String restartingInstanceId = "restarting"; - client.scheduleNewWorkflow(DemoWorkflow.class, null, restartingInstanceId); - System.out.printf("Started new workflow instance with ID: %s%n", restartingInstanceId); - System.out.println("Sleeping 30 seconds to restart the workflow"); - TimeUnit.SECONDS.sleep(30); - - System.out.println("**SendExternalMessage: RestartEvent**"); - client.raiseEvent(restartingInstanceId, "RestartEvent", "RestartEventPayload"); - - System.out.println("Sleeping 30 seconds to terminate the eternal workflow"); - TimeUnit.SECONDS.sleep(30); - client.terminateWorkflow(restartingInstanceId, null); - } - - System.out.println("Exiting DemoWorkflowClient."); - System.exit(0); +public class ExternalSystemInteractionWorkflow extends Workflow { + @Override + public WorkflowStub create() { + return ctx -> { + // ...other steps... + Integer orderCost = ctx.getInput(int.class); + // Require orders over a certain threshold to be approved + if (orderCost > ORDER_APPROVAL_THRESHOLD) { + try { + // Request human approval for this order + ctx.callActivity("RequestApprovalActivity", orderCost, Void.class).await(); + // Pause and wait for a human to approve the order + boolean approved = ctx.waitForExternalEvent("ManagerApproval", Duration.ofDays(3), boolean.class).await(); + if (!approved) { + // The order was rejected, end the workflow here + ctx.complete("Process reject"); + } + } catch (TaskCanceledException e) { + // An approval timeout results in automatic order cancellation + ctx.complete("Process cancel"); + } + } + // ...other steps... + // End the workflow with a success result + ctx.complete("Process approved"); + }; + } } ``` From 866e2498a3f6918f4cca59de1ca6fdb2eba82ff7 Mon Sep 17 00:00:00 2001 From: kaibocai Date: Mon, 13 Nov 2023 16:43:34 +0000 Subject: [PATCH 150/162] minor updates Signed-off-by: kaibocai --- .../workflow/workflow-patterns.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md index 530dc1edadc..c7aebca4e9e 100644 --- a/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md +++ b/daprdocs/content/en/developing-applications/building-blocks/workflow/workflow-patterns.md @@ -115,42 +115,42 @@ public class ChainWorkflow extends Workflow { return ctx -> { StringBuilder sb = new StringBuilder(); String wfInput = ctx.getInput(String.class); - String result1 = ctx.callActivity("event1", wfInput, String.class).await(); - String result2 = ctx.callActivity("event2", result1, String.class).await(); - String result3 = ctx.callActivity("event3", result2, String.class).await(); + String result1 = ctx.callActivity("Step1", wfInput, String.class).await(); + String result2 = ctx.callActivity("Step2", result1, String.class).await(); + String result3 = ctx.callActivity("Step3", result2, String.class).await(); String result = sb.append(result1).append(',').append(result2).append(',').append(result3).toString(); ctx.complete(result); }; } } - class Event1 implements WorkflowActivity { + class Step1 implements WorkflowActivity { @Override public Object run(WorkflowActivityContext ctx) { - Logger logger = LoggerFactory.getLogger(Event1.class); + Logger logger = LoggerFactory.getLogger(Step1.class); logger.info("Starting Activity: " + ctx.getName()); // Do some work return null; } } - class Event2 implements WorkflowActivity { + class Step2 implements WorkflowActivity { @Override public Object run(WorkflowActivityContext ctx) { - Logger logger = LoggerFactory.getLogger(Event2.class); + Logger logger = LoggerFactory.getLogger(Step2.class); logger.info("Starting Activity: " + ctx.getName()); // Do some work return null; } } - class Event3 implements WorkflowActivity { + class Step3 implements WorkflowActivity { @Override public Object run(WorkflowActivityContext ctx) { - Logger logger = LoggerFactory.getLogger(Event3.class); + Logger logger = LoggerFactory.getLogger(Step3.class); logger.info("Starting Activity: " + ctx.getName()); // Do some work return null; From bc9ee47fcae85354a36190fb0b208bf711744d39 Mon Sep 17 00:00:00 2001 From: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Date: Mon, 13 Nov 2023 17:33:23 -0500 Subject: [PATCH 151/162] Update daprdocs/content/en/reference/components-reference/supported-bindings/kubemq.md Signed-off-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> --- .../reference/components-reference/supported-bindings/kubemq.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/kubemq.md b/daprdocs/content/en/reference/components-reference/supported-bindings/kubemq.md index daf7d1981ad..e8e975d20c5 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/kubemq.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/kubemq.md @@ -51,7 +51,9 @@ This component supports both **input and output** binding interfaces. {{< tabs "Self-Hosted" "Kubernetes">}} {{% codetab %}} + 1. Obtain KubeMQ Key by visiting [https://account.kubemq.io/auth/signup](https://account.kubemq.io/auth/signup) and register for a key. + 2. Wait for an email confirmation with your Key You can run a KubeMQ broker with Docker: From 78ffacba64ec1bc5360acfeaecae2b895b9d31a9 Mon Sep 17 00:00:00 2001 From: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> Date: Mon, 13 Nov 2023 17:33:29 -0500 Subject: [PATCH 152/162] Update daprdocs/content/en/reference/components-reference/supported-bindings/kubemq.md Signed-off-by: Hannah Hunter <94493363+hhunter-ms@users.noreply.github.com> --- .../reference/components-reference/supported-bindings/kubemq.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/kubemq.md b/daprdocs/content/en/reference/components-reference/supported-bindings/kubemq.md index e8e975d20c5..b16f77b00c3 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/kubemq.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/kubemq.md @@ -66,7 +66,9 @@ You can then interact with the server using the client port: `localhost:50000` {{% /codetab %}} {{% codetab %}} + 1. Obtain KubeMQ Key by visiting [https://account.kubemq.io/auth/signup](https://account.kubemq.io/auth/signup) and register for a key. + 2. Wait for an email confirmation with your Key Then Run the following kubectl commands: From 87b306ecfd8df91163e9fd9de556189a30d9473f Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Mon, 13 Nov 2023 17:48:45 -0500 Subject: [PATCH 153/162] fix link Signed-off-by: Hannah Hunter --- .../components-reference/supported-bindings/kubemq.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/kubemq.md b/daprdocs/content/en/reference/components-reference/supported-bindings/kubemq.md index b16f77b00c3..1338c7e4ee4 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/kubemq.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/kubemq.md @@ -51,9 +51,7 @@ This component supports both **input and output** binding interfaces. {{< tabs "Self-Hosted" "Kubernetes">}} {{% codetab %}} - -1. Obtain KubeMQ Key by visiting [https://account.kubemq.io/auth/signup](https://account.kubemq.io/auth/signup) and register for a key. - +1. Obtain KubeMQ Key by visiting [https://account.kubemq.io/auth/signup](https://account.kubemq.io/auth/signin) and register for a key. 2. Wait for an email confirmation with your Key You can run a KubeMQ broker with Docker: @@ -66,9 +64,7 @@ You can then interact with the server using the client port: `localhost:50000` {{% /codetab %}} {{% codetab %}} - -1. Obtain KubeMQ Key by visiting [https://account.kubemq.io/auth/signup](https://account.kubemq.io/auth/signup) and register for a key. - +1. Obtain KubeMQ Key by visiting [https://account.kubemq.io/auth/signup](https://account.kubemq.io/auth/signin) and register for a key. 2. Wait for an email confirmation with your Key Then Run the following kubectl commands: From bd30cd2c8baa3073918d3b66aeed3ab935f50c31 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Mon, 13 Nov 2023 17:52:31 -0500 Subject: [PATCH 154/162] try again with link Signed-off-by: Hannah Hunter --- .../components-reference/supported-bindings/kubemq.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-bindings/kubemq.md b/daprdocs/content/en/reference/components-reference/supported-bindings/kubemq.md index 1338c7e4ee4..e5112d2b29a 100644 --- a/daprdocs/content/en/reference/components-reference/supported-bindings/kubemq.md +++ b/daprdocs/content/en/reference/components-reference/supported-bindings/kubemq.md @@ -51,7 +51,7 @@ This component supports both **input and output** binding interfaces. {{< tabs "Self-Hosted" "Kubernetes">}} {{% codetab %}} -1. Obtain KubeMQ Key by visiting [https://account.kubemq.io/auth/signup](https://account.kubemq.io/auth/signin) and register for a key. +1. [Obtain KubeMQ Key](https://docs.kubemq.io/getting-started/quick-start#obtain-kubemq-license-key). 2. Wait for an email confirmation with your Key You can run a KubeMQ broker with Docker: @@ -64,7 +64,7 @@ You can then interact with the server using the client port: `localhost:50000` {{% /codetab %}} {{% codetab %}} -1. Obtain KubeMQ Key by visiting [https://account.kubemq.io/auth/signup](https://account.kubemq.io/auth/signin) and register for a key. +1. [Obtain KubeMQ Key](https://docs.kubemq.io/getting-started/quick-start#obtain-kubemq-license-key). 2. Wait for an email confirmation with your Key Then Run the following kubectl commands: From b78063f2ee89e81c4e87bc366587bc5dc714acc0 Mon Sep 17 00:00:00 2001 From: Hannah Hunter Date: Mon, 13 Nov 2023 17:58:16 -0500 Subject: [PATCH 155/162] pub-sub Signed-off-by: Hannah Hunter --- .../components-reference/supported-pubsub/setup-kubemq.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-kubemq.md b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-kubemq.md index 2bae7c62974..d6c22d5b552 100644 --- a/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-kubemq.md +++ b/daprdocs/content/en/reference/components-reference/supported-pubsub/setup-kubemq.md @@ -45,7 +45,7 @@ spec: {{< tabs "Self-Hosted" "Kubernetes">}} {{% codetab %}} -1. Obtain KubeMQ Key by visiting [https://account.kubemq.io/auth/signup](https://account.kubemq.io/auth/signup) and register for a key. +1. [Obtain KubeMQ Key](https://docs.kubemq.io/getting-started/quick-start#obtain-kubemq-license-key). 2. Wait for an email confirmation with your Key You can run a KubeMQ broker with Docker: @@ -58,7 +58,7 @@ You can then interact with the server using the client port: `localhost:50000` {{% /codetab %}} {{% codetab %}} -1. Obtain KubeMQ Key by visiting [https://account.kubemq.io/auth/signup](https://account.kubemq.io/auth/signup) and register for a key. +1. [Obtain KubeMQ Key](https://docs.kubemq.io/getting-started/quick-start#obtain-kubemq-license-key). 2. Wait for an email confirmation with your Key Then Run the following kubectl commands: From 060120691561e6ff708a14a194c140cdbb91eba3 Mon Sep 17 00:00:00 2001 From: Josh van Leeuwen Date: Tue, 21 Nov 2023 01:01:15 +0000 Subject: [PATCH 156/162] 1.12 update supported releases up to 1.12 (#3873) Signed-off-by: joshvanl --- .../en/operations/support/support-release-policy.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/daprdocs/content/en/operations/support/support-release-policy.md b/daprdocs/content/en/operations/support/support-release-policy.md index 32ee3f1e7bf..b44ba75dc91 100644 --- a/daprdocs/content/en/operations/support/support-release-policy.md +++ b/daprdocs/content/en/operations/support/support-release-policy.md @@ -45,11 +45,17 @@ The table below shows the versions of Dapr releases that have been tested togeth | Release date | Runtime | CLI | SDKs | Dashboard | Status | Release notes | |--------------------|:--------:|:--------|---------|---------|---------|------------| -| October 11th 2023 | 1.12.0
| 1.12.0 | Java 1.10.0
Go 1.9.0
PHP 1.1.0
Python 1.11.0
.NET 1.12.0
JS 3.1.2 | 0.14.0 | Supported (current) | [v1.12.0 release notes](https://github.com/dapr/dapr/releases/tag/v1.12.0) | +| November 18th 2023 | 1.12.2
| 1.12.0 | Java 1.10.0
Go 1.9.1
PHP 1.2.0
Python 1.12.0
.NET 1.12.0
JS 3.2.0 | 0.14.0 | Supported (current) | [v1.12.2 release notes](https://github.com/dapr/dapr/releases/tag/v1.12.2) | +| November 16th 2023 | 1.12.1
| 1.12.0 | Java 1.10.0
Go 1.9.1
PHP 1.2.0
Python 1.12.0
.NET 1.12.0
JS 3.2.0 | 0.14.0 | Supported | [v1.12.1 release notes](https://github.com/dapr/dapr/releases/tag/v1.12.1) | +| October 11th 2023 | 1.12.0
| 1.12.0 | Java 1.10.0
Go 1.9.0
PHP 1.1.0
Python 1.11.0
.NET 1.12.0
JS 3.1.2 | 0.14.0 | Supported | [v1.12.0 release notes](https://github.com/dapr/dapr/releases/tag/v1.12.0) | +| November 18th 2023 | 1.11.6
| 1.11.0 | Java 1.9.0
Go 1.8.0
PHP 1.1.0
Python 1.10.0
.NET 1.11.0
JS 3.1.0 | 0.13.0 | Supported | [v1.11.6 release notes](https://github.com/dapr/dapr/releases/tag/v1.11.6) | +| November 3rd 2023 | 1.11.5
| 1.11.0 | Java 1.9.0
Go 1.8.0
PHP 1.1.0
Python 1.10.0
.NET 1.11.0
JS 3.1.0 | 0.13.0 | Supported | [v1.11.5 release notes](https://github.com/dapr/dapr/releases/tag/v1.11.5) | +| October 5th 2023 | 1.11.4
| 1.11.0 | Java 1.9.0
Go 1.8.0
PHP 1.1.0
Python 1.10.0
.NET 1.11.0
JS 3.1.0 | 0.13.0 | Supported | [v1.11.4 release notes](https://github.com/dapr/dapr/releases/tag/v1.11.4) | | August 31st 2023 | 1.11.3
| 1.11.0 | Java 1.9.0
Go 1.8.0
PHP 1.1.0
Python 1.10.0
.NET 1.11.0
JS 3.1.0 | 0.13.0 | Supported | [v1.11.3 release notes](https://github.com/dapr/dapr/releases/tag/v1.11.3) | | July 20th 2023 | 1.11.2
| 1.11.0 | Java 1.9.0
Go 1.8.0
PHP 1.1.0
Python 1.10.0
.NET 1.11.0
JS 3.1.0 | 0.13.0 | Supported | [v1.11.2 release notes](https://github.com/dapr/dapr/releases/tag/v1.11.2) | | June 22nd 2023 | 1.11.1
| 1.11.0 | Java 1.9.0
Go 1.8.0
PHP 1.1.0
Python 1.10.0
.NET 1.11.0
JS 3.1.0 | 0.13.0 | Supported | [v1.11.1 release notes](https://github.com/dapr/dapr/releases/tag/v1.11.1) | | June 12th 2023 | 1.11.0
| 1.11.0 | Java 1.9.0
Go 1.8.0
PHP 1.1.0
Python 1.10.0
.NET 1.11.0
JS 3.1.0 | 0.13.0 | Supported | [v1.11.0 release notes](https://github.com/dapr/dapr/releases/tag/v1.11.0) | +| November 18th 2023 | 1.10.10
| 1.10.0 | Java 1.8.0
Go 1.7.0
PHP 1.1.0
Python 1.9.0
.NET 1.10.0
JS 3.0.0 | 0.11.0 | Supported | [v1.10.10 release notes](https://github.com/dapr/dapr/releases/tag/v1.10.10) | | July 20th 2023 | 1.10.9
| 1.10.0 | Java 1.8.0
Go 1.7.0
PHP 1.1.0
Python 1.9.0
.NET 1.10.0
JS 3.0.0 | 0.11.0 | Supported | [v1.10.9 release notes](https://github.com/dapr/dapr/releases/tag/v1.10.9) | | June 22nd 2023 | 1.10.8
| 1.10.0 | Java 1.8.0
Go 1.7.0
PHP 1.1.0
Python 1.9.0
.NET 1.10.0
JS 3.0.0 | 0.11.0 | Supported | [v1.10.8 release notes](https://github.com/dapr/dapr/releases/tag/v1.10.8) | | May 15th 2023 | 1.10.7
| 1.10.0 | Java 1.8.0
Go 1.7.0
PHP 1.1.0
Python 1.9.0
.NET 1.10.0
JS 3.0.0 | 0.11.0 | Supported | | From 4cb2cc732c17aeacb78360efec2c7d48f696f220 Mon Sep 17 00:00:00 2001 From: Oliver Tomlinson Date: Tue, 21 Nov 2023 21:51:39 +0000 Subject: [PATCH 157/162] Update daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md Co-authored-by: Mark Fussell Signed-off-by: Oliver Tomlinson --- .../debugging/debugging-docker-compose.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md b/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md index 4266b40f7dd..44f6aa3c497 100644 --- a/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md +++ b/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md @@ -1,6 +1,6 @@ --- type: docs -title: "Debugging dapr Apps running in Docker Compose" +title: "Debugging Dapr Apps running in Docker Compose" linkTitle: "Debugging Docker Compose" weight: 300 description: "Debug Dapr apps locally which are part of a docker compose deployment" From 0bfe63ffdbe52543b562bcd24836a8f9c55d1d07 Mon Sep 17 00:00:00 2001 From: Oliver Tomlinson Date: Tue, 21 Nov 2023 21:51:49 +0000 Subject: [PATCH 158/162] Update daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md Co-authored-by: Mark Fussell Signed-off-by: Oliver Tomlinson --- .../debugging/debugging-docker-compose.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md b/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md index 44f6aa3c497..46a9b7b721e 100644 --- a/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md +++ b/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md @@ -3,7 +3,7 @@ type: docs title: "Debugging Dapr Apps running in Docker Compose" linkTitle: "Debugging Docker Compose" weight: 300 -description: "Debug Dapr apps locally which are part of a docker compose deployment" +description: "Debug Dapr apps locally which are part of a Docker Compose deployment" --- The goal of this article is to demonstrate a way to debug one or more daprised applications (via your IDE, locally) while remaining integrated with the other applications that have deployed in the docker compose environment. From 7e1201f00d29e0a0db2deda7ba9d8a3aed46df1e Mon Sep 17 00:00:00 2001 From: Oliver Tomlinson Date: Tue, 21 Nov 2023 21:52:01 +0000 Subject: [PATCH 159/162] Update daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md Co-authored-by: Mark Fussell Signed-off-by: Oliver Tomlinson --- .../debugging/debugging-docker-compose.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md b/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md index 46a9b7b721e..a6c9e240e70 100644 --- a/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md +++ b/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md @@ -38,7 +38,7 @@ networks: hello-dapr ``` -When you run this docker file with `docker compose -f compose.yml up` this will deploy to docker and run as normal. +When you run this docker file with `docker compose -f compose.yml up` this will deploy to Docker and run as normal. But how do we debug the `nodeapp` while still integrated to the running dapr sidecar process, and anything else that you may have deployed via the docker compose file? From fc836e09ea141be1adb3007c7aa8d15ebae521c7 Mon Sep 17 00:00:00 2001 From: Oliver Tomlinson Date: Tue, 21 Nov 2023 21:52:07 +0000 Subject: [PATCH 160/162] Update daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md Co-authored-by: Mark Fussell Signed-off-by: Oliver Tomlinson --- .../debugging/debugging-docker-compose.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md b/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md index a6c9e240e70..04e9815fc1b 100644 --- a/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md +++ b/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md @@ -72,7 +72,7 @@ Next, stop any existing compose sessions you may have started, and run the follo `docker compose -f compose.yml -f compose.debug.yml up` -You should now find that the dapr sidecar and your debugging app will have bi-directional communication with each other as if they were running together as normal in the docker compose environment. +You should now find that the dapr sidecar and your debugging app will have bi-directional communication with each other as if they were running together as normal in the Docker compose environment. **Note** : It's important to highlight that the `nodeapp` service in the docker compose environment is actually still running, however it has been removed from the docker network so it is effectively orphaned as nothing can communicate to it. From 5af84a3ec100571ae51462922a693b4c850ab709 Mon Sep 17 00:00:00 2001 From: Oliver Tomlinson Date: Tue, 21 Nov 2023 21:52:18 +0000 Subject: [PATCH 161/162] Update daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md Co-authored-by: Mark Fussell Signed-off-by: Oliver Tomlinson --- .../debugging/debugging-docker-compose.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md b/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md index 04e9815fc1b..0eab2bbdd6b 100644 --- a/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md +++ b/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md @@ -40,7 +40,7 @@ networks: When you run this docker file with `docker compose -f compose.yml up` this will deploy to Docker and run as normal. -But how do we debug the `nodeapp` while still integrated to the running dapr sidecar process, and anything else that you may have deployed via the docker compose file? +But how do we debug the `nodeapp` while still integrated to the running dapr sidecar process, and anything else that you may have deployed via the Docker compose file? Lets start by introducing a *second* docker compose file called `compose.debug.yml`. This second compose file will augment with the first compose file when the `up` command is ran. From 99598ff7ba871a70b5b01ab80d748a3af0111417 Mon Sep 17 00:00:00 2001 From: Oliver Tomlinson Date: Tue, 21 Nov 2023 21:54:07 +0000 Subject: [PATCH 162/162] Update debugging-docker-compose.md Include demo video content Signed-off-by: Oliver Tomlinson --- .../debugging/debugging-docker-compose.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md b/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md index 0eab2bbdd6b..ae8962bc895 100644 --- a/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md +++ b/daprdocs/content/en/developing-applications/debugging/debugging-docker-compose.md @@ -76,4 +76,7 @@ You should now find that the dapr sidecar and your debugging app will have bi-di **Note** : It's important to highlight that the `nodeapp` service in the docker compose environment is actually still running, however it has been removed from the docker network so it is effectively orphaned as nothing can communicate to it. +**Demo** +Watch this video on how to debug local Dapr apps with Docker Compose +