[
  {
    "path": ".gitignore",
    "content": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n\n# VS Code \nDebug\n\n# Test binary, built with `go test -c`\n*.test\n\n# Output\n*.out\n\n# mac\n.DS_Store\n\n# bin \nbin\n\n# vendored dependnacies \nvendor\n\n# certs \n*.pem \n\n# secrets\nsecrets.json\n\n# certs\ncerts\nsetup/certs\n\n# other \nsetup/config/ingress.yaml\nsetup/config/namespace.yml\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Mark Chmarny\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Dapr demos\n\nCollection of personal [Dapr](https://dapr.io) demos.\n\n> Note, some of these demos require latest version of Dapr, Ingress gateway, Observability components, or cluster-local Redis and Mongo services. To create Kubernetes cluster with all these components on AKS, or quickly configure an existing cluster, consider the [Dapr Cluster Setup](./setup) utility.\n\n\n* Bindings\n  * [Scheduling using cron](./cron-binding) - Using scheduler to execute service \n  * [Tweet stream](./pipeline/tweet-provider) - Subscribing to a Twitter even stream and publishing to a pub/sub topic\n  * [State change handler](./state-change-handler) - RethinkDB state changes streamed into topic\n* Eventing\n  * [gRPC event subscriber](./grpc-event-subscriber) - Subscribing to topic and processing its events using gRPC service\n  * [HTTP event subscriber](./http-event-subscriber) - Subscribing to topic and processing its events using HTTP service\n* Services \n  * [gRPC echo service](./grpc-echo-service) - gRPC service invocation example\n  * [HTTP echo service](./http-echo-service) - HTTP service invocation example\n  * [Sentiment Scorer](./pipeline/sentiment-scorer) - Sentiment scoring serving backed by Azure Cognitive Service \n* Integrations\n  * [Components in ACI](./dapr-api-on-aci) - Dapr components as microservices on ACI without app\n  * [Component in Kubernetes](./component-api) - Dapr components as microservices on Kubernetes without app\n  * [Dapr Apps in ACI](./dapr-aci) - Deploying apps with Dapr sidecar and components on ACI\n  * [Dapr with APIM](./apim-gateway) - Dapr API using Azure API Management self-hosted gateway\n  * [Dapr API on Ingress](./daprized-ingress) - Expose Dapr API on Kubernetes Ingress Controller using NGINX\n  * [Dapr GitOps](https://github.com/mchmarny/git-ops) - GitHub Actions build pipeline for Dapr apps\n* Solutions\n  * [Order cancellation](./order-cancellation) - multiple Dapr service integrations with observability\n  * [Pipeline](./pipeline) - Demos combining Twitter binding, Sentiment scoring, Multi Pub/Sub Processor, and WebSocket Viewer app\n  * [Fan-out](./fan-out) - Single message source \"broadcasted\" to multiple, configurable targets (e.g. Redis PubSub, HTTP, gRPC)\n  * [Hardened](./hardened) - Example of multi-microservice app with tightly controlled access to secrets, components, and full invoking service identity validation\n* Templates\n  * [Dapr gRPC Service](https://github.com/mchmarny/dapr-grpc-service-template) - gRPC service template\n  * [Dapr HTTP Event Subscriber](https://github.com/mchmarny/dapr-http-event-subscriber-template) - Event subscriber HTTP service template\n  * [Dapr gRPC Event Subscriber](https://github.com/mchmarny/dapr-grpc-event-subscriber-template) - Event subscriber gRPC service template \n  * [dapr-http-cron-handler](https://github.com/mchmarny/dapr-http-cron-handler-template) - Scheduled service development template\n\n## Disclaimer\n\nThis is my personal project and it does not represent my employer. While I do my best to ensure that everything works, I take no responsibility for issues caused by this code.\n\n## License\n\nThis software is released under the [MIT](./LICENSE)\n"
  },
  {
    "path": "apim-gateway/README.md",
    "content": "# Dapr & Azure API Management Integration Demo\n\nDapr integration with [Azure API Management](https://azure.microsoft.com/en-us/services/api-management/) (APIM) using self-hosted gateway on Kubernetes. \n\n![APIM Self-hosted Gateway Overview](img/overview-diagram.png)\n\nIn this demo we will walk through the configuration of API Management service and its self-hosted gateway on Kubernetes. To illustrate the Dapr integration we will also review three Dapr use-cases:\n\n* Invocation of a specific Dapr service method\n* Publishing content to a Pub/Sub topic \n* Binding invocation with request content transformation\n\nIn addition, we will overview the use of APIM tracing to debug your configuration. \n\n> While you can accomplish everything we show here in Azure portal, to make this demo easier to reliably reproduce, we will be using only the Azure CLI and APIs.\n\n## Prerequisite \n\n* [Azure account](https://azure.microsoft.com/en-us/free/)\n* [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest)\n* [Kubernetes cluster with Dapr](https://github.com/dapr/docs/blob/v0.9.0/getting-started/environment-setup.md#installing-dapr-on-a-kubernetes-cluster)\n* [Helm](https://helm.sh/docs/intro/install/)\n\n## Terminology\n\nYou will see a few different APIs being used throughout this demo. At one point we are even going to use one API to manage another (API inception?). Here is short summary to help you keep all these APIs straight. Hope it helps: \n\n* [Azure API](https://docs.microsoft.com/en-us/rest/api/apimanagement/) - this is the API provided by Azure to manage its service (yes, including the API Management Service)\n* [API in APIM](https://docs.microsoft.com/en-us/azure/api-management/edit-api) - is the API which we will define in APIM service. Its operations will be used by the users\n* [Dapr API](https://github.com/dapr/docs/tree/master/reference/api#dapr-api-reference) - are the RESTful HTTP APIs defined by Dapr which developers interact with building applications\n\n## Setup \n\nTo make this demo easier to reproduce, start by exporting the name for your new Azure API Management (APIM) service.\n\n> Note, the name of your API Management service instance has to be globally unique!\n\n```shell\nexport APIM_SERVICE_NAME=\"dapr-apim-demo\"\n```\n\nIn addition also export the Azure [Subscription ID](https://docs.bitnami.com/azure/faq/administration/find-subscription-id/) and [Resource Group](https://docs.bitnami.com/azure/faq/administration/find-deployment-resourcegroup-id/) where you want to create that service.\n\n```shell\nexport AZ_SUBSCRIPTION_ID=\"your-subscription-id\"\nexport AZ_RESOURCE_GROUP=\"your-resource-group\"\n```\n\n## Azure API Management \n\nWe will start by configuring the Azure API Management service.\n\n### Service Creation\n\nCreate service instance:\n\n> The `publisher-email` and `publisher-name` are only required to receive system notifications e-mails.\n\n```shell\naz apim create --name $APIM_SERVICE_NAME \\\n               --subscription $AZ_SUBSCRIPTION_ID \\\n               --resource-group $AZ_RESOURCE_GROUP \\\n               --publisher-email \"you@your-domain.com\" \\\n               --publisher-name \"Your Name\"\n```\n\n> Note, depending on the SKU and resource group configuration, this operation may take 15+ min. While this running, consider quick read on [API Management Concepts](https://docs.microsoft.com/en-us/azure/api-management/api-management-key-concepts#-apis-and-operations)\n\n### API Configuration\n\nEach [API operation](https://docs.microsoft.com/en-us/azure/api-management/api-management-key-concepts#-apis-and-operations) defined in APIM will map to one Dapr API. To define these mappings you will use OpenAPI format defined in [apim/api.yaml](./apim/api.yaml) file. You will need to update the OpenAPI file with the name of the APIM service created above:\n\n```yaml\nservers:\n  - url: http://<YOUR-APIM-SERVICE-NAME>.azure-api.net\n  - url: https://<YOUR-APIM-SERVICE-NAME>.azure-api.net\n```\n\nWhen finished, import that OpenAPI definition fle into APIM service instance:\n\n```shell\naz apim api import --path / \\\n                   --api-id dapr \\\n                   --subscription $AZ_SUBSCRIPTION_ID \\\n                   --resource-group $AZ_RESOURCE_GROUP \\\n                   --service-name $APIM_SERVICE_NAME \\\n                   --display-name \"Demo Dapr Service API\" \\\n                   --protocols http https \\\n                   --subscription-required true \\\n                   --specification-path apim/api.yaml \\\n                   --specification-format OpenApi\n```\n\n> Notice the `subscription-required` parameter is set to `true` which means that all invocations against the `dapr` API will need a subscription key. We cover how to obtain the subscription key later. \n\n### Azure API Token\n\nExport the Azure management API token to use through this demo.\n\n```shell\nexport AZ_API_TOKEN=$(az account get-access-token --resource=https://management.azure.com --query accessToken --output tsv)\n```\n\n> If you receive an error later that your token expired, just re-run this command\n\n### Policy Management\n\nAPIM [Policies](https://docs.microsoft.com/en-us/azure/api-management/api-management-key-concepts#--policies) are sequentially executed on each request. We will start by defining \"global\" policy to throttle all operation invocations on our API, then add individual policies for each operation to add specific options.\n\n#### Global Policy\n\nAPIM policies are defined inside of inbound, outbound, and backend elements. In our case to apply policy that will rate-limit all requests on all operations (before they are forwarded to Dapr API), we will place the global policy within the `inbound` section. \n\n> Note, the rate limit quota we defined here is being shared across all the replicas of self-hosted gateway. In default configuration, where there are 2 replicas, this policy would actually be half of the permitted calls per minute.\n\n```xml\n<policies>\n     <inbound>\n          <rate-limit-by-key  \n               calls=\"120\"\n               renewal-period=\"60\"\n               increment-condition=\"@(context.Response.StatusCode == 200)\"\n               counter-key=\"@(context.Request.IpAddress)\" />\n     </inbound>\n     ...\n</policies>\n``` \n\nApply that [policy](apim/policy-all.json) to all operations submit it to the Azure management API.\n\n```shell\ncurl -i -X PUT \\\n     -d @apim/policy-all.json \\\n     -H \"Content-Type: application/json\" \\\n     -H \"If-Match: *\" \\\n     -H \"Authorization: Bearer ${AZ_API_TOKEN}\" \\\n     \"https://management.azure.com/subscriptions/${AZ_SUBSCRIPTION_ID}/resourceGroups/${AZ_RESOURCE_GROUP}/providers/Microsoft.ApiManagement/service/${APIM_SERVICE_NAME}/apis/dapr/policies/policy?api-version=2019-12-01\"\n```\n\nIf everything goes well, the management API will return the created policy.\n\n#### Echo Service Policy \n\nThe Dapr service invocation handles all the service discovery, so to invoke a specific method on any Dapr service users follow this API: \n\n```http\nPOST/GET/PUT/DELETE /v1.0/invoke/<appId>/method/<method-name>\n```\n\nTo enable users to invoke the `echo` method on Dapr service with ID of `echo-service` we will create a policy that inherits the global policy (`<base />`) first, to ensure only authorize service invocation are passed to the backend Dapr API. Then to \"map\" the invocation we set `dapr` as the \"backend-id\" and define the Dapr service and method attributes to specific service ID and method name.\n\n```xml\n<policies>\n     <inbound>\n          <base />\n          <set-backend-service \n               backend-id=\"dapr\" \n               dapr-app-id=\"echo-service\" \n               dapr-method=\"echo\" />\n     </inbound>\n     ...\n</policies>\n```\n\nTo apply [this policy](apim/policy-echo.json) to the `echo` operation on our API, submit it to the Azure management API:\n\n```shell\ncurl -i -X PUT \\\n     -d @apim/policy-echo.json \\\n     -H \"Content-Type: application/json\" \\\n     -H \"If-Match: *\" \\\n     -H \"Authorization: Bearer ${AZ_API_TOKEN}\" \\\n     \"https://management.azure.com/subscriptions/${AZ_SUBSCRIPTION_ID}/resourceGroups/${AZ_RESOURCE_GROUP}/providers/Microsoft.ApiManagement/service/${APIM_SERVICE_NAME}/apis/dapr/operations/echo/policies/policy?api-version=2019-12-01\"\n```\n\nIf everything goes well, the management API will return the created policy. Additional information about Dapr Service Invocation in APIM are available [here](https://aka.ms/apim/dapr/invoke). \n\nAlso, since the external mapping of the API user invocations to Dapr is done in APIM policy, it can be easily re-mapped to any other version as the API implementation evolves over time. \n\n![](img/backend-policy.png)\n\n#### Message Topic Policy \n\nIn addition to Dapr service invocation, APIM can also be used to publish to Dapr Pub/Sub API:\n\n```http\nPOST /v1.0/publish/<pubsubname>/<topic>\n```\n\nTo expose the `messages` topic configured in the `demo-events` component we will start by inheriting the global policy like before, and then set the publish policy to format the request that will be passed to the Dapr Pub/Sub API:\n\n```xml\n<policies>\n     <inbound>\n        <base />\n        <publish-to-dapr \n               topic=\"@(\"demo-events/messages\")\" \n               response-variable-name=\"pubsub-response\"\n        >@(context.Request.Body.As<string>())</publish-to-dapr>\n        <return-response \n               response-variable-name=\"pubsub-response\" />\n    </inbound>\n     ...\n</policies>\n```\n\nTo apply [this policy](apim/policy-message.json) to the `message` operation on our API, submit it to the Azure management API:\n\n\n```shell\ncurl -i -X PUT \\\n     -d @apim/policy-message.json \\\n     -H \"Content-Type: application/json\" \\\n     -H \"If-Match: *\" \\\n     -H \"Authorization: Bearer ${AZ_API_TOKEN}\" \\\n     \"https://management.azure.com/subscriptions/${AZ_SUBSCRIPTION_ID}/resourceGroups/${AZ_RESOURCE_GROUP}/providers/Microsoft.ApiManagement/service/${APIM_SERVICE_NAME}/apis/dapr/operations/message/policies/policy?api-version=2019-12-01\"\n```\n\nIf everything goes well, the management API will return the created policy. Additional information about Dapr Pub/Sub support in APIM are available [here](https://aka.ms/apim/dapr/pubsub).\n\n#### Save Binding Policy \n\nIn our final case, we are going to overview exposing the Dapr binding API.\n\n```http\nPOST/PUT /v1.0/bindings/<name>\n```\n\nIn contrast to the previous policies, rather than just forwarding the original request content, we are going to create a brand new request based on the content of the original request and mapping it to the format expected by Dapr API. This capability comes handy when your API needs to stay the same while the backing service evolves API evolves over time. Consider the payload expected by Dapr binding API: \n\n```json\n{\n  \"data\": \"\",\n  \"metadata\": {\n    \"\": \"\",\n    \"\": \"\"\n  },\n  \"operation\": \"\"\n}\n```\n\nThe policy will first define a `key` variable that will be generated using system guid. Once defined, that variable can be used later on in the policy. To accommodate the binding format expected by Dapr, the policy will then set `operation` attribute in APIM `invoke-dapr-binding` policy, and set `metadata` items to:\n\n* `source` which will be a static value indicating the record came from `APIM`\n* `client-ip` which will be set to the client request IP\n* `key` which will be set to the value of the variable defined above\n\nFinally, for `data`, we simply use the original content of the client request.\n\n```xml\n<policies>\n    <inbound>\n        <base />\n        <set-variable name=\"key\" \n                      value=\"@{ return Guid.NewGuid().ToString(); }\" />\n        <invoke-dapr-binding \n                      name=\"demo-binding\" \n                      operation=\"create\" \n                      response-variable-name=\"binding-response\">\n            <metadata>\n                <item key=\"source\">APIM</item>\n                <item key=\"client-ip\">@( context.Request.IpAddress )</item>\n                <item key=\"key\">@( (string)context.Variables[\"key\"] )</item>\n            </metadata>\n            <data>@( context.Request.Body.As<string>() )</data>\n        </invoke-dapr-binding>\n        <return-response response-variable-name=\"binding-response\" />\n    </inbound>\n     ...\n</policies>\n```\n\nTo apply [this policy](apim/policy-save.json) to the `save` operation on our API, submit it to the Azure management API:\n\n```shell\ncurl -i -X PUT \\\n     -d @apim/policy-save.json \\\n     -H \"Content-Type: application/json\" \\\n     -H \"If-Match: *\" \\\n     -H \"Authorization: Bearer ${AZ_API_TOKEN}\" \\\n     \"https://management.azure.com/subscriptions/${AZ_SUBSCRIPTION_ID}/resourceGroups/${AZ_RESOURCE_GROUP}/providers/Microsoft.ApiManagement/service/${APIM_SERVICE_NAME}/apis/dapr/operations/save/policies/policy?api-version=2019-12-01\"\n```\n\n> Note, the support in APIM for bindings is still rolling out across Azure regions. You can safely skip this section and just demo service invocation and topic publishing if you receive an error that `invoke-dapr-binding` is not recognize.\n\nIf everything goes well, the management API will return the created policy. Additional information about Dapr Binding support in APIM are available [here](https://aka.ms/apim/dapr/bind). \n \n### Gateway Configuration\n\nTo create a self-hosted gateway which will be then deployed to the Kubernetes cluster, first, we need to create the `demo-apim-gateway` object in APIM:\n\n```shell\ncurl -i -X PUT -d '{\"properties\": {\"description\": \"Dapr Gateway\",\"locationData\": {\"name\": \"Virtual\"}}}' \\\n     -H \"Content-Type: application/json\" \\\n     -H \"If-Match: *\" \\\n     -H \"Authorization: Bearer ${AZ_API_TOKEN}\" \\\n     \"https://management.azure.com/subscriptions/${AZ_SUBSCRIPTION_ID}/resourceGroups/${AZ_RESOURCE_GROUP}/providers/Microsoft.ApiManagement/service/${APIM_SERVICE_NAME}/gateways/demo-apim-gateway?api-version=2019-12-01\"\n```\n\nAnd then map that gateway to the previously created API:\n\n```shell\ncurl -i -X PUT -d '{ \"properties\": { \"provisioningState\": \"created\" } }' \\\n     -H \"Content-Type: application/json\" \\\n     -H \"If-Match: *\" \\\n     -H \"Authorization: Bearer ${AZ_API_TOKEN}\" \\\n     \"https://management.azure.com/subscriptions/${AZ_SUBSCRIPTION_ID}/resourceGroups/${AZ_RESOURCE_GROUP}/providers/Microsoft.ApiManagement/service/${APIM_SERVICE_NAME}/gateways/demo-apim-gateway/apis/dapr?api-version=2019-12-01\"\n```\n\nIf everything goes well, the API returns JSON of the created objects.\n\n## Kubernetes \n\nSwitching now to the Kubernetes cluster...\n\n### Dependencies \n\nTo showcase the ability to expose Dapr pub/sub and binding APIs in APIM, we are going to need [Dapr components](https://github.com/dapr/docs/tree/master/concepts#components) configured on the cluster. \n\n> Note, while Dapr supports some 75+ different components, to keep things simple in this demo we will use Redis as both pub/sub and binding backing service\n\nStart with adding the Redis repo to your Helm charts:\n\n```shell\nhelm repo add bitnami https://charts.bitnami.com/bitnami\nhelm repo update\n```\n\nAnd install Redis and wait for the deployment to complete:\n\n> Note, for simplicity, we are deploying everything into the `default` namespace.\n\n```shell\nhelm install redis bitnami/redis  \nkubectl rollout status statefulset.apps/redis-master\nkubectl rollout status statefulset.apps/redis-slave\n```\n\n### Dapr Components \n\nDapr's modular design means that we can easily extend its functionality using [components](https://github.com/dapr/docs/tree/master/concepts#components). The specific implementation for these components which can be any number of the readily available Dapr building blocks is done in configuration. That means that it's also easy to swap or reconfigure them at runtime without the need to modify your code. \n\n![](img/dapr-building-blocks.png)\n\nTo create the binding component to point to the above created Redis cluster the configuration looks like this:\n\n```yaml\napiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: demo-events\nspec:\n  type: pubsub.redis\n  metadata:\n  - name: redisHost\n    value: redis-master.default.svc.cluster.local:6379\n  - name: redisPassword\n    secretKeyRef:\n      name: redis\n      key: redis-password\n  - name: allowedTopics\n    value: \"messages\"\n```\n\nNotice we are using the secret created by Redis for password so that our configuration doesn't include any secure information. We also specify that only the `messages` topic should be supported in this case. \n\nTo apply these component configurations run: \n\n```shell\nkubectl apply -f k8s/pubsub.yaml\nkubectl apply -f k8s/binding.yaml\n```\n\n> Note, if you updated components after deploying the gateway you will need to restart the deployments.\n\n```shell\nkubectl rollout restart deployment/event-subscriber\nkubectl rollout status deployment/event-subscriber\nkubectl rollout restart deployment/demo-apim-gateway\nkubectl rollout status deployment/demo-apim-gateway\n```\n\nTo check if the components were registered correctly in Dapr, inspect the `daprd` logs in `demo-apim-gateway` pod for `demo-events` and `demo-binding`:\n\n```shell\nkubectl logs -l app=demo-apim-gateway -c daprd --tail=200\n```\n\n### Dapr Services \n\nTo deploy your application as a Dapr service all you need to do is augment your Kubernetes deployment template with few Dapr annotations.\n\n```yaml\nannotations:\n     dapr.io/enabled: \"true\"\n     dapr.io/app-id: \"event-subscriber\"\n     dapr.io/app-port: \"8080\"\n```\n\n> To learn more about Kubernetes sidecar configuration see [Dapr docs](https://github.com/dapr/docs/blob/master/concepts/configuration/README.md#kubernetes-sidecar-configuration).\n\nFor this demo we will use a pre-build Docker images of two applications: [gRPC Echo Service](https://github.com/mchmarny/dapr-demos/tree/master/grpc-echo-service) and [HTTP Event Subscriber Service](https://github.com/mchmarny/dapr-demos/tree/master/http-event-subscriber). The Kubernetes deployment files for both of these are located here:\n\n* [k8s/echo-service.yaml](k8s/echo-service.yaml)\n* [k8s/event-subscriber.yaml](k8s/event-subscriber.yaml)\n\nDeploy both of these and check that it is ready:\n\n```shell\nkubectl apply -f k8s/echo-service.yaml\nkubectl apply -f k8s/event-subscriber.yaml\nkubectl get pods -l demo=dapr-apim -w\n```\n\n> Service is ready when its status is `Running` and the ready column is `2/2` (Dapr and our echo service containers both started)\n\n```shell\nNAME                                READY   STATUS    RESTARTS   AGE\necho-service-668986b998-v2ssp       2/2     Running   0          10m\nevent-subscriber-7d68b67d9d-5v7bf   2/2     Running   0          10m\n```\n\nTo make sure that the event subscriber connects to the Redis service you can query the service logs\n\n```shell\nkubectl logs -l app=event-subscriber -c daprd | grep demo-events\n```\n\nYou should see entries containing: \n\n```shell\napp responded with subscriptions [{demo-events messages /messages map[]}]\napp is subscribed to the following topics: [messages] through pubsub=demo-events\nsubscribing to topic=messages on pubsub=demo-events\n```\n\n### Self-hosted APIM Gateway \n\nTo connect the self-hosted gateway to APIM service, we will need to create a Kubernetes secret with the APIM gateway key. Start by getting the key which your gateway will use to connect to from APIM:\n\n> Note, the maximum validity for access tokens is 30 days. Update the below `expiry` parameter to be withing 30 days from today\n\n```shell\ncurl -i -X POST -d '{ \"keyType\": \"primary\", \"expiry\": \"2020-10-10T00:00:01Z\" }' \\\n     -H \"Content-Type: application/json\" \\\n     -H \"Authorization: Bearer ${AZ_API_TOKEN}\" \\\n     \"https://management.azure.com/subscriptions/${AZ_SUBSCRIPTION_ID}/resourceGroups/${AZ_RESOURCE_GROUP}/providers/Microsoft.ApiManagement/service/${APIM_SERVICE_NAME}/gateways/demo-apim-gateway/generateToken?api-version=2019-12-01\"\n```\n\nCopy the content of `value` from the response and create a secret:\n\n```shell\nkubectl create secret generic demo-apim-gateway-token --type Opaque --from-literal value=\"GatewayKey paste-the-key-here\"\n```\n\n> Make sure the secret includes the `GatewayKey` and a space ` ` as well as the value of your token (e.g. `GatewayKey a1b2c3...`)\n\nNow, create a config map containing the APIM service endpoint that will be used to configure your self-hosted gateway:\n\n```shell\nkubectl create configmap demo-apim-gateway-env --from-literal \\\n     \"config.service.endpoint=https://${APIM_SERVICE_NAME}.management.azure-api.net/subscriptions/${AZ_SUBSCRIPTION_ID}/resourceGroups/${AZ_RESOURCE_GROUP}/providers/Microsoft.ApiManagement/service/${APIM_SERVICE_NAME}?api-version=2019-12-01\"\n```\n\nAnd finally, deploy the gateway and check that it's ready:\n\n```shell\nkubectl apply -f k8s/gateway.yaml\nkubectl get pods -l app=demo-apim-gateway\n```\n\n> Note, the self-hosted gateway is deployed with 2 replicas to ensure availability during upgrades. \n\nMake sure both instances have status `Running` and container is ready `2/2` (gateway container + Dapr side-car).\n\n```shell\nNAME                                 READY   STATUS    RESTARTS   AGE\ndemo-apim-gateway-6dfb968f5c-cb4t7   2/2     Running   0          26s\ndemo-apim-gateway-6dfb968f5c-gxrrq   2/2     Running   0          26s\n```\n\nTo check on the gateway logs:\n\n```shell\nkubectl logs -l app=demo-apim-gateway -c demo-apim-gateway\n```\n\n## Usage (API Test)\n\nWith APIM configured and self-hosted gateway deployed we are ready to test. Start by capturing the cluster load balancer ingress IP:\n\n```shell\nexport GATEWAY_IP=$(kubectl get svc demo-apim-gateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')\n```\n\n### API Subscription Key \n\nAll of the APIs we defined in this demo are protected with subscription key. To invoke them, we will first need a subscription key: \n\n```shell\ncurl -i -H POST -d '{}' -H \"Authorization: Bearer ${AZ_API_TOKEN}\" \\\n     \"https://management.azure.com/subscriptions/${AZ_SUBSCRIPTION_ID}/resourceGroups/${AZ_RESOURCE_GROUP}/providers/Microsoft.ApiManagement/service/${APIM_SERVICE_NAME}/subscriptions/master/listSecrets?api-version=2019-12-01\"\n```\n\nThe response will include both the primary and secondary keys. Copy one of them and export so we can use it for the rest of the demo:\n\n```shell\nexport AZ_API_SUB_KEY=\"your-api-subscription-key\"\n```\n\n### Service Invocation \n\nTo invoke the backing gRPC service over Dapr API exposed by APIM run:\n\n```shell\ncurl -i -X POST -d '{ \"message\": \"hello\" }' \\\n     -H \"Content-Type: application/json\" \\\n     -H \"Ocp-Apim-Subscription-Key: ${AZ_API_SUB_KEY}\" \\\n     -H \"Ocp-Apim-Trace: true\" \\\n     \"http://${GATEWAY_IP}/echo\"\n```\n\nIf everything is configured correctly, you should see the response from your backing Dapr service: \n\n```json \n{ \"message\": \"hello\" }\n```\n\nIn addition, you can also check the `echo-service` logs:\n\n```shell\nkubectl logs -l app=echo-service -c service\n```\n\n### Message Publishing \n\nTo post a message to the Dapr Pub/Sub API exposed on APIM run:\n\n```shell\ncurl -i -X POST \\\n     -d '{ \"content\": \"hello\" }' \\\n     -H \"Content-Type: application/json\" \\\n     -H \"Ocp-Apim-Subscription-Key: ${AZ_API_SUB_KEY}\" \\\n     -H \"Ocp-Apim-Trace: true\" \\\n     \"http://${GATEWAY_IP}/message\"\n```\n\nIf everything is configured correctly, you will see `200` status code in the header, indicating the message was successfully delivered to the Dapr API.\n\nYou can also check the `event-subscriber` logs:\n\n```shell\nkubectl logs -l app=event-subscriber -c service\n```\n\nThere should be an entry similar to this: \n\n```shell\nevent - PubsubName:demo-events, Topic:messages, ID:24f0e6f0-ab29-4cd6-8617-6c6c36ac1171, Data: map[message:hello]\n```\n\n### Binding Invocation\n\nTo save a record into database using the Dapr binding API exposed by APIM run:\n\n```shell\ncurl -i -X POST \\\n     -d '{\"city\":\"PDX\",\"time\":\"1600171062\",\"metric\":\"aqi\",\"value\": 457}' \\\n     -H \"Content-Type: application/json\" \\\n     -H \"Ocp-Apim-Subscription-Key: ${AZ_API_SUB_KEY}\" \\\n     -H \"Ocp-Apim-Trace: true\" \\\n     \"http://${GATEWAY_IP}/save\"\n```\n\nIf everything is configured correctly, you will see `200` status code in the header indicating the binding was successfully triggered on the Dapr API and our record successfully saved into the DB. \n\n### Debugging \n\nNotice in each one of our API invocations we have been including the `Ocp-Apim-Trace: true` header parameter. APIM provides an ability to trace requests across the policy execution chain which is helps in debugging your policy. The response of each one fo the above invocation includes the `Ocp-Apim-Trace-Location` header parameter. Just paste the value of that parameter into your browser to see the full trace stack in JSON. The trace can get pretty long so here are few Dapr-specific snippets: \n\n\n```json \n...\n{\n    \"source\": \"request-forwarder\",\n    \"timestamp\": \"2020-09-11T11:15:52.9405903Z\",\n    \"elapsed\": \"00:00:00.1382166\",\n    \"data\": {\n        \"message\": \"Request is being forwarded to the backend service. Timeout set to 300 seconds\",\n        \"request\": {\n            \"method\": \"POST\",\n            \"url\": \"http://localhost:3500/v1.0/publish/demo-events/messages\"\n        }\n    }\n},\n{\n    \"source\": \"publish-to-dapr\",\n    \"timestamp\": \"2020-09-11T11:15:53.1899121Z\",\n    \"elapsed\": \"00:00:00.3875400\",\n    \"data\": {\n        \"response\": {\n            \"status\": {\n                \"code\": 200,\n                \"reason\": \"OK\"\n            },\n            \"headers\": [\n                {\n                    \"name\": \"Server\",\n                    \"value\": \"fasthttp\"\n                },\n                {\n                    \"name\": \"Date\",\n                    \"value\": \"Fri, 11 Sep 2020 11:15:52 GMT\"\n                },\n                {\n                    \"name\": \"Content-Length\",\n                    \"value\": \"0\"\n                },\n                {\n                    \"name\": \"Traceparent\",\n                    \"value\": \"00-5b1f0bdfc2191742a4635a906359a7aa-196f5df2e977b00a-01\"\n                }\n            ]\n        }\n    }\n}\n...\n```\n\n## Summary \n\nThis demo illustrated how to setup the APIM service and deploy the self-hosted gateway into your cluster. Using this gateway you can mange access to any number of Dapr services hosted on Kubernetes. You can find out more about all the features of APIM (e.g. Discovery, Caching, Logging etc.) [here](https://azure.microsoft.com/en-us/services/api-management/).\n\n## Cleanup \n\n```shell\nkubectl delete -f k8s/gateway.yaml\nkubectl delete secret demo-apim-gateway-token\nkubectl delete configmap demo-apim-gateway-env\n\nkubectl delete -f k8s/echo-service.yaml\nkubectl delete -f k8s/event-subscriber.yaml\n\nkubectl delete -f k8s/pubsub.yaml\nkubectl delete -f k8s/binding.yaml\n\naz apim delete --name $APIM_SERVICE_NAME --no-wait --yes\n```\n\n"
  },
  {
    "path": "apim-gateway/apim/api.yaml",
    "content": "openapi: 3.0.1\ninfo:\n  title: dapr\n  version: '1.0'\nservers:\n  - url: http://dapr-apim-demo.azure-api.net\n  - url: https://dapr-apim-demo.azure-api.net\npaths:\n  /echo:\n    post:\n      summary: Echo Service\n      description: Invoke service using Dapr API\n      operationId: echo\n      requestBody:\n        content:\n          application/json:\n            example:\n              message: hello\n      responses:\n        '200':\n          description: ''\n  /message:\n    post:\n      summary: Message Topic\n      description: Post to topic using Dapr API\n      operationId: message\n      requestBody:\n        content:\n          application/json:\n            example:\n              content: hello\n      responses:\n        '200':\n          description: ''\n  /save:\n    post:\n      summary: DB Binding\n      description: DB binding using Dapr API\n      operationId: save\n      requestBody:\n        content:\n          application/json:\n            example:\n              city: PDX,\n              time: 1600171062\n              metric: aqi\n              value: 457\n      responses:\n        '200':\n          description: ''"
  },
  {
    "path": "apim-gateway/apim/policy-all.json",
    "content": "{\n    \"properties\": {\n      \"format\": \"xml\",\n      \"value\": \"<policies><inbound><rate-limit-by-key calls=\\\"120\\\" renewal-period=\\\"60\\\" increment-condition=\\\"@(context.Response.StatusCode == 200)\\\" counter-key=\\\"@(context.Request.IpAddress)\\\" /></inbound><backend><forward-request /></backend><outbound /><on-error /></policies>\"\n    }\n}"
  },
  {
    "path": "apim-gateway/apim/policy-echo.json",
    "content": "{\n    \"properties\": {\n      \"format\": \"xml\",\n      \"value\": \"<policies><inbound><base /><set-backend-service backend-id=\\\"dapr\\\" dapr-app-id=\\\"echo-service\\\" dapr-method=\\\"echo\\\" /></inbound><backend><base /></backend><outbound></outbound><on-error><base /></on-error></policies>\"\n    }\n}"
  },
  {
    "path": "apim-gateway/apim/policy-message.json",
    "content": "{\n    \"properties\": {\n      \"format\": \"xml\",\n      \"value\": \"<policies><inbound><base /><publish-to-dapr topic=\\\"@(&quot;demo-events/messages&quot;)\\\" response-variable-name=\\\"pubsub-response\\\">@( context.Request.Body.As&lt;string&gt;() )</publish-to-dapr><return-response response-variable-name=\\\"pubsub-response\\\" /></inbound><backend /><outbound><base /></outbound><on-error><base /></on-error></policies>\"\n    }\n}"
  },
  {
    "path": "apim-gateway/apim/policy-save.json",
    "content": "{\n    \"properties\": {\n      \"format\": \"xml\",\n      \"value\": \"<policies><inbound><base /><set-variable name=\\\"key\\\" value=\\\"@{ return Guid.NewGuid().ToString(); }\\\" /><invoke-dapr-binding name=\\\"demo-binding\\\" operation=\\\"create\\\" response-variable-name=\\\"binding-response\\\"><metadata><item key=\\\"source\\\">APIM</item><item key=\\\"client-ip\\\">@( context.Request.IpAddress )</item><item key=\\\"key\\\">@( (string)context.Variables[\\\"key\\\"] )</item></metadata><data>@( context.Request.Body.As&lt;string&gt;() )</data></invoke-dapr-binding><return-response response-variable-name=\\\"binding-response\\\" /></inbound><backend /><outbound><base /></outbound><on-error><base /></on-error></policies>\"\n    }\n}"
  },
  {
    "path": "apim-gateway/k8s/binding.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: demo-binding\nspec:\n  type: bindings.redis\n  metadata:\n  - name: redisHost\n    value: redis-master.default.svc.cluster.local:6379\n  - name: redisPassword\n    secretKeyRef:\n      name: redis\n      key: redis-password"
  },
  {
    "path": "apim-gateway/k8s/echo-service.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: echo-service\n  labels:\n    app: echo-service\n    demo: dapr-apim\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: echo-service\n  template:\n    metadata:\n      labels:\n        app: echo-service\n        demo: dapr-apim\n      annotations:\n        dapr.io/enabled: \"true\"\n        dapr.io/app-id: \"echo-service\"\n        dapr.io/app-protocol: \"grpc\"\n        dapr.io/app-port: \"60002\"\n        dapr.io/config: \"tracing\"\n        dapr.io/log-as-json: \"true\"\n        dapr.io/log-level: \"debug\"\n    spec:\n      containers:\n      - name: service\n        image: mchmarny/grpc-echo-service:v0.11.1\n        imagePullPolicy: Always\n        ports:\n        - containerPort: 60002\n        env:\n        - name: ADDRESS\n          value: \":60002\""
  },
  {
    "path": "apim-gateway/k8s/event-subscriber.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: event-subscriber\n  labels:\n    app: event-subscriber\n    demo: dapr-apim\nspec:\n  selector:\n    matchLabels:\n      app: event-subscriber\n  template:\n    metadata:\n      labels:\n        app: event-subscriber\n        demo: dapr-apim\n      annotations:\n        dapr.io/enabled: \"true\"\n        dapr.io/app-id: \"event-subscriber\"\n        dapr.io/app-port: \"8080\"\n        dapr.io/config: \"tracing\"\n        dapr.io/log-as-json: \"true\"\n        dapr.io/log-level: \"debug\"\n    spec:\n      containers:\n      - name: service\n        image: mchmarny/http-event-subscriber:v0.11.1\n        ports:\n        - containerPort: 8080\n        env:\n        - name: PORT\n          value: \"8080\"\n        - name: PUBSUB_NAME\n          value: \"demo-events\"\n        - name: TOPIC_NAME\n          value: \"messages\""
  },
  {
    "path": "apim-gateway/k8s/gateway.yaml",
    "content": "# NOTE: Before deploying to a production environment, please review the documentation -> https://aka.ms/self-hosted-gateway-production\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: demo-apim-gateway\nspec:\n  replicas: 2\n  selector:\n    matchLabels:\n      app: demo-apim-gateway\n  strategy:\n    type: RollingUpdate\n    rollingUpdate:\n      maxUnavailable: 0\n      maxSurge: 25%\n  template:\n    metadata:\n      labels:\n        app: demo-apim-gateway\n      annotations:\n        dapr.io/enabled: \"true\"\n        dapr.io/app-id: \"demo-apim-gateway\"\n        dapr.io/config: \"tracing\"\n        dapr.io/log-as-json: \"true\"\n        dapr.io/log-level: \"debug\"\n    spec:\n      terminationGracePeriodSeconds: 60\n      containers:\n      - name: demo-apim-gateway\n        image: mcr.microsoft.com/azure-api-management/gateway:beta\n        ports:\n        - name: http\n          containerPort: 8080\n        - name: https\n          containerPort: 8081\n        readinessProbe:\n          httpGet:\n            path: /internal-status-0123456789abcdef\n            port: http\n            scheme: HTTP\n          initialDelaySeconds: 0\n          periodSeconds: 5\n          failureThreshold: 3\n          successThreshold: 1\n        env:\n        - name: config.service.auth\n          valueFrom:\n            secretKeyRef:\n              name: demo-apim-gateway-token\n              key: value\n        envFrom:\n        - configMapRef:\n            name: demo-apim-gateway-env\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: demo-apim-gateway\nspec:\n  type: LoadBalancer\n  externalTrafficPolicy: Local\n  ports:\n  - name: http\n    port: 80\n    targetPort: 8080\n  - name: https\n    port: 443\n    targetPort: 8081\n  selector:\n    app: demo-apim-gateway"
  },
  {
    "path": "apim-gateway/k8s/pubsub.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: demo-events\nspec:\n  type: pubsub.redis\n  metadata:\n  - name: redisHost\n    value: redis-master.default.svc.cluster.local:6379\n  - name: redisPassword\n    secretKeyRef:\n      name: redis\n      key: redis-password\n  - name: allowedTopics\n    value: \"messages\""
  },
  {
    "path": "autoscaling-on-queue/README.md",
    "content": "# Autoscaling Dapr service based on queue depth \n\nDapr, with its modular building-block approach, along with the 10+ different Pub/Sub components, makes it easy to write message processing microservices. Since Dapr runs in VM, on bare-metal, in the Cloud, or even on the Edge... the autoscaling Dapr services is left to the hosting later. \n\nIn case of Kubernetes, Dapr integrates with [Keda](https://github.com/kedacore/keda), an event driven autoscaler for Kubernetes. In this demo I'll walk through the configuration of Dapr microservice to scale along with the back pressure on [Kafka](https://kafka.apache.org) queue that service processes. \n\n![](image/diagram.png)\n\n## Setup \n\nThe autoscaling demo requires [Dapr](https://dapr.io). If you don't already have a Kubernetes cluster with Dapr installed you can use the included [setup](../setup) to configure all the dependencies. \n\n### Keda \n\nStart by install [Keda](https://github.com/kedacore/keda) into the cluster and wait for it become ready:\n\n```shell\nkubectl apply -f deployment/keda-2.0.0-beta.yaml\nkubectl rollout status deployment.apps/keda-operator -n keda\n```\n\n### Kafka (optional)\n\nNext, if you don't have access to Kafka you can use these instructions to install Kafka into the cluster:\n\n```shell\nhelm repo add confluentinc https://confluentinc.github.io/cp-helm-charts/\nhelm repo update\nkubectl create ns kafka\nhelm install kafka confluentinc/cp-helm-charts -n kafka \\\n\t\t--set cp-schema-registry.enabled=false \\\n\t\t--set cp-kafka-rest.enabled=false \\\n\t\t--set cp-kafka-connect.enabled=false \\\n\t\t--set dataLogDirStorageClass=default \\\n\t\t--set dataDirStorageClass=default \\\n\t\t--set storageClass=default\nkubectl rollout status deployment.apps/kafka-cp-control-center -n kafka\nkubectl rollout status deployment.apps/kafka-cp-ksql-server -n kafka\nkubectl rollout status statefulset.apps/kafka-cp-kafka -n kafka\nkubectl rollout status statefulset.apps/kafka-cp-zookeeper -n kafka\n```\n\nWhen done, also deploy Kafka client and wait until it's ready:\n\n```shell\nkubectl apply -n kafka -f deployment/kafka-client.yaml\nkubectl wait -n kafka --for=condition=ready pod kafka-client --timeout=120s\n```\n\nNext, create the `metric` topic which we will use in this demo:\n\n> The number of `partitions` is connected to the maximum number of replicas Keda will create. \n\n```shell\nkubectl -n kafka exec -it kafka-client -- kafka-topics \\\n\t\t--zookeeper kafka-cp-zookeeper-headless:2181 \\\n\t\t--topic metric \\\n\t\t--create \\\n\t\t--partitions 10 \\\n\t\t--replication-factor 3 \\\n\t\t--if-not-exists\n```\n\n## Deployment\n\nTo configure the autoscaling demo we will deploy two deployments: `subscriber` which will be used to process messages of the `metric` queue in Kafka, and the `producer`, which will be publishing messages. To make the `producer` compatible with any one of the Pub/Sub components supported by Dapr we will publish the events onto the Kafka queue using Dapr APIs. \n\n### Subscriber\n\nThe `subscriber` service doesn't really do anything with the messages. To resemble real-life processing which may take some time to process messages, the `subscriber` allows for explicit processing time setting. The default value is `300ms`. We will go over how to modify that later. \n\nTo deploy the `subscriber` service, apply the [Kafka Dapr component](deployment/kafka-pubsub.yaml), the [message subscriber service](deployment/subscriber.yaml), and the [subscriber service Keda scaler](subscriber-scaler.yaml):\n\n```shell\nkubectl apply -f deployment/kafka-pubsub.yaml\nkubectl apply -f deployment/subscriber.yaml\nkubectl apply -f deployment/subscriber-scaler.yaml\n```\n\nWhen done, start watching for the number of replicas of the deployed `subscriber` service: \n\n```shell\nwatch kubectl get pods -l app=autoscaling-subscriber\n```\n\n> Note, by default the subscriber service Keda scaler is set to scale to 0, so you will not see any pods yet. We will publish data on the `metric` topic with the `producer`.\n\n### Producer\n\nIn a second terminal session, deploy the [producer service](deployment/producer.yaml) and wait for it to be ready:\n\n```shell\nkubectl apply -f deployment/producer.yaml\nkubectl rollout status deployment/autoscaling-producer\n```\n\n## Demo \n\nBack in the initial terminal now, some 20-30 seconds after the `producer` starts, you should see the number of `subscriber` pods being adjusted by Keda based on the number of the `metric` topic:\n\n```shell\nNAME                                      READY   STATUS    RESTARTS   AGE\nautoscaling-subscriber-696ffb5c7b-64zqq   2/2     Running   0          31s\nautoscaling-subscriber-696ffb5c7b-67f74   2/2     Running   0          15s\nautoscaling-subscriber-696ffb5c7b-gpc2d   2/2     Running   0          7m42s\n```\n\nBy default the `subscriber-scaler` is set to scale-to-zero and has the polling frequency of `15s`. You can adjust these values in [deployment/subscriber-scaler.yaml](deployment/subscriber-scaler.yaml):\n\n```yaml\npollingInterval: 15\nminReplicaCount: 0\nmaxReplicaCount: 10\ncooldownPeriod: 30\n```\n\nTo modify how long should the `subscriber` take to process each message adjust `PROCESS_DURATION` in [deployment/subscriber.yaml](deployment/subscriber.yaml) and re-apply it to the cluster:\n\n```yaml\n- name: PROCESS_DURATION\n  value: \"300ms\"\n```\n\nFinally, to adjust the number of messages published by the producer change the `producer` in [deployment/producer.yaml](./deployment/producer.yaml) and re-apply it to the cluster:\n\n\n```yaml\n- name: NUMBER_OF_PUBLISHERS\n  value: \"1\"\n- name: PUBLISHERS_FREQ\n  value: \"100ms\"\n```\n\nThe `NUMBER_OF_PUBLISHERS` setting is number of channels that are used to publish events (default: 1). And the `PUBLISHERS_FREQ` is the frequency with which each channel publishes events (default: 1s). \n\n> There is a limit to the amount of messages a single container can produce. If you need to scale beyond that number, increase the number of `autoscaling-producer` replicas\n\n```shell\nkubectl scale -n kafka deployment/autoscaling-producer --replicas=10 \n```\n\n### Updating Components \n\nIf you have changed already deployed Dapr component, make sure to reload the `subscriber` and `producer` deployments:\n\n```shell\nkubectl rollout restart deployment/autoscaling-subscriber\nkubectl rollout status deployment/autoscaling-subscriber\nkubectl rollout restart deployment/autoscaling-producer\nkubectl rollout status deployment/autoscaling-producer\n```\n\n### Kafka Helpers \n\nGet `metric` topic offsets for `autoscaling-subscriber` consumer group:\n\n```shell\nkubectl -n kafka exec -it kafka-client -- kafka-consumer-groups \\\n\t--bootstrap-server kafka-cp-kafka:9092 \\\n\t--describe \\\n\t--group autoscaling-subscriber\n```\n\nPurge the `metric` topic:\n\n```shell\nkubectl -n kafka exec -it kafka-client -- kafka-topics \\\n\t--zookeeper kafka-cp-zookeeper:2181 \\\n\t--alter \\\n\t--topic metric \\\n\t--config retention.ms=1000\nsleep 15\nkubectl -n kafka exec -it kafka-client -- kafka-topics \\\n\t--zookeeper kafka-cp-zookeeper:2181 \\\n\t--alter \\\n\t--topic metric \\\n\t--delete-config retention.ms\n```\n\nDelete `metric` topic\n\n```shell\nkubectl -n kafka exec -it kafka-client -- kafka-topics \\\n\t--zookeeper kafka-cp-zookeeper:2181 \\\n\t--delete \\\n\t--topic metric\n```\n\n## Cleanup \n\n```shell\nkubectl delete -f deployment/producer.yaml\nkubectl delete -f deployment/kafka-pubsub.yaml\nkubectl delete -f deployment/subscriber.yaml\nkubectl delete -f deployment/subscriber-scaler.yaml\nkubectl delete -f deployment/keda-2.0.0-beta.yaml\n```\n\n## Disclaimer\n\nThis is my personal project and it does not represent my employer. While I do my best to ensure that everything works, I take no responsibility for issues caused by this code.\n\n## License\n\nThis software is released under the [MIT](../LICENSE)\n"
  },
  {
    "path": "autoscaling-on-queue/deployment/kafka-client.yaml",
    "content": "apiVersion: v1\nkind: Pod\nmetadata:\n name: kafka-client\nspec:\n containers:\n  - name: kafka-client\n    image: confluentinc/cp-enterprise-kafka:5.5.0\n    command:\n     - sh\n     - -c\n     - \"exec tail -f /dev/null\""
  },
  {
    "path": "autoscaling-on-queue/deployment/kafka-pubsub.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: autoscaling-pubsub\nspec:\n  type: pubsub.kafka\n  metadata:\n    - name: brokers\n      value: kafka-cp-kafka.kafka.svc.cluster.local:9092\n    - name: authRequired\n      value: \"false\"\n    - name: allowedTopics\n      value: metric\n    - name: consumerID\n      value: autoscaling-subscriber"
  },
  {
    "path": "autoscaling-on-queue/deployment/keda-2.0.0-beta.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    app.kubernetes.io/name: keda\n    app.kubernetes.io/part-of: keda-operator\n    app.kubernetes.io/version: 2.0.0-beta\n  name: keda\n---\napiVersion: apiextensions.k8s.io/v1beta1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.3.0\n  creationTimestamp: null\n  labels:\n    app.kubernetes.io/part-of: keda-operator\n    app.kubernetes.io/version: 2.0.0-beta\n  name: scaledjobs.keda.sh\nspec:\n  additionalPrinterColumns:\n  - JSONPath: .spec.triggers[*].type\n    name: Triggers\n    type: string\n  - JSONPath: .spec.triggers[*].authenticationRef.name\n    name: Authentication\n    type: string\n  - JSONPath: .status.conditions[?(@.type==\"Ready\")].status\n    name: Ready\n    type: string\n  - JSONPath: .status.conditions[?(@.type==\"Active\")].status\n    name: Active\n    type: string\n  - JSONPath: .metadata.creationTimestamp\n    name: Age\n    type: date\n  group: keda.sh\n  names:\n    kind: ScaledJob\n    listKind: ScaledJobList\n    plural: scaledjobs\n    shortNames:\n    - sj\n    singular: scaledjob\n  scope: Namespaced\n  subresources:\n    status: {}\n  validation:\n    openAPIV3Schema:\n      description: ScaledJob is the Schema for the scaledjobs API\n      properties:\n        apiVersion:\n          description: 'APIVersion defines the versioned schema of this representation\n            of an object. Servers should convert recognized schemas to the latest\n            internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n          type: string\n        kind:\n          description: 'Kind is a string value representing the REST resource this\n            object represents. Servers may infer this from the endpoint the client\n            submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n          type: string\n        metadata:\n          type: object\n        spec:\n          description: ScaledJobSpec defines the desired state of ScaledJob\n          properties:\n            envSourceContainerName:\n              type: string\n            failedJobsHistoryLimit:\n              format: int32\n              type: integer\n            jobTargetRef:\n              description: JobSpec describes how the job execution will look like.\n              properties:\n                activeDeadlineSeconds:\n                  description: Specifies the duration in seconds relative to the startTime\n                    that the job may be active before the system tries to terminate\n                    it; value must be positive integer\n                  format: int64\n                  type: integer\n                backoffLimit:\n                  description: Specifies the number of retries before marking this\n                    job failed. Defaults to 6\n                  format: int32\n                  type: integer\n                completions:\n                  description: 'Specifies the desired number of successfully finished\n                    pods the job should be run with.  Setting to nil means that the\n                    success of any pod signals the success of all pods, and allows\n                    parallelism to have any positive value.  Setting to 1 means that\n                    parallelism is limited to 1 and the success of that pod signals\n                    the success of the job. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/'\n                  format: int32\n                  type: integer\n                manualSelector:\n                  description: 'manualSelector controls generation of pod labels and\n                    pod selectors. Leave `manualSelector` unset unless you are certain\n                    what you are doing. When false or unset, the system pick labels\n                    unique to this job and appends those labels to the pod template.  When\n                    true, the user is responsible for picking unique labels and specifying\n                    the selector.  Failure to pick a unique label may cause this and\n                    other jobs to not function correctly.  However, You may see `manualSelector=true`\n                    in jobs that were created with the old `extensions/v1beta1` API.\n                    More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/#specifying-your-own-pod-selector'\n                  type: boolean\n                parallelism:\n                  description: 'Specifies the maximum desired number of pods the job\n                    should run at any given time. The actual number of pods running\n                    in steady state will be less than this number when ((.spec.completions\n                    - .status.successful) < .spec.parallelism), i.e. when the work\n                    left to do is less than max parallelism. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/'\n                  format: int32\n                  type: integer\n                selector:\n                  description: 'A label query over pods that should match the pod\n                    count. Normally, the system sets this field for you. More info:\n                    https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors'\n                  properties:\n                    matchExpressions:\n                      description: matchExpressions is a list of label selector requirements.\n                        The requirements are ANDed.\n                      items:\n                        description: A label selector requirement is a selector that\n                          contains values, a key, and an operator that relates the\n                          key and values.\n                        properties:\n                          key:\n                            description: key is the label key that the selector applies\n                              to.\n                            type: string\n                          operator:\n                            description: operator represents a key's relationship\n                              to a set of values. Valid operators are In, NotIn, Exists\n                              and DoesNotExist.\n                            type: string\n                          values:\n                            description: values is an array of string values. If the\n                              operator is In or NotIn, the values array must be non-empty.\n                              If the operator is Exists or DoesNotExist, the values\n                              array must be empty. This array is replaced during a\n                              strategic merge patch.\n                            items:\n                              type: string\n                            type: array\n                        required:\n                        - key\n                        - operator\n                        type: object\n                      type: array\n                    matchLabels:\n                      additionalProperties:\n                        type: string\n                      description: matchLabels is a map of {key,value} pairs. A single\n                        {key,value} in the matchLabels map is equivalent to an element\n                        of matchExpressions, whose key field is \"key\", the operator\n                        is \"In\", and the values array contains only \"value\". The requirements\n                        are ANDed.\n                      type: object\n                  type: object\n                template:\n                  description: 'Describes the pod that will be created when executing\n                    a job. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/'\n                  properties:\n                    metadata:\n                      description: 'Standard object''s metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata'\n                      type: object\n                    spec:\n                      description: 'Specification of the desired behavior of the pod.\n                        More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status'\n                      properties:\n                        activeDeadlineSeconds:\n                          description: Optional duration in seconds the pod may be\n                            active on the node relative to StartTime before the system\n                            will actively try to mark it failed and kill associated\n                            containers. Value must be a positive integer.\n                          format: int64\n                          type: integer\n                        affinity:\n                          description: If specified, the pod's scheduling constraints\n                          properties:\n                            nodeAffinity:\n                              description: Describes node affinity scheduling rules\n                                for the pod.\n                              properties:\n                                preferredDuringSchedulingIgnoredDuringExecution:\n                                  description: The scheduler will prefer to schedule\n                                    pods to nodes that satisfy the affinity expressions\n                                    specified by this field, but it may choose a node\n                                    that violates one or more of the expressions.\n                                    The node that is most preferred is the one with\n                                    the greatest sum of weights, i.e. for each node\n                                    that meets all of the scheduling requirements\n                                    (resource request, requiredDuringScheduling affinity\n                                    expressions, etc.), compute a sum by iterating\n                                    through the elements of this field and adding\n                                    \"weight\" to the sum if the node matches the corresponding\n                                    matchExpressions; the node(s) with the highest\n                                    sum are the most preferred.\n                                  items:\n                                    description: An empty preferred scheduling term\n                                      matches all objects with implicit weight 0 (i.e.\n                                      it's a no-op). A null preferred scheduling term\n                                      matches no objects (i.e. is also a no-op).\n                                    properties:\n                                      preference:\n                                        description: A node selector term, associated\n                                          with the corresponding weight.\n                                        properties:\n                                          matchExpressions:\n                                            description: A list of node selector requirements\n                                              by node's labels.\n                                            items:\n                                              description: A node selector requirement\n                                                is a selector that contains values,\n                                                a key, and an operator that relates\n                                                the key and values.\n                                              properties:\n                                                key:\n                                                  description: The label key that\n                                                    the selector applies to.\n                                                  type: string\n                                                operator:\n                                                  description: Represents a key's\n                                                    relationship to a set of values.\n                                                    Valid operators are In, NotIn,\n                                                    Exists, DoesNotExist. Gt, and\n                                                    Lt.\n                                                  type: string\n                                                values:\n                                                  description: An array of string\n                                                    values. If the operator is In\n                                                    or NotIn, the values array must\n                                                    be non-empty. If the operator\n                                                    is Exists or DoesNotExist, the\n                                                    values array must be empty. If\n                                                    the operator is Gt or Lt, the\n                                                    values array must have a single\n                                                    element, which will be interpreted\n                                                    as an integer. This array is replaced\n                                                    during a strategic merge patch.\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                              required:\n                                              - key\n                                              - operator\n                                              type: object\n                                            type: array\n                                          matchFields:\n                                            description: A list of node selector requirements\n                                              by node's fields.\n                                            items:\n                                              description: A node selector requirement\n                                                is a selector that contains values,\n                                                a key, and an operator that relates\n                                                the key and values.\n                                              properties:\n                                                key:\n                                                  description: The label key that\n                                                    the selector applies to.\n                                                  type: string\n                                                operator:\n                                                  description: Represents a key's\n                                                    relationship to a set of values.\n                                                    Valid operators are In, NotIn,\n                                                    Exists, DoesNotExist. Gt, and\n                                                    Lt.\n                                                  type: string\n                                                values:\n                                                  description: An array of string\n                                                    values. If the operator is In\n                                                    or NotIn, the values array must\n                                                    be non-empty. If the operator\n                                                    is Exists or DoesNotExist, the\n                                                    values array must be empty. If\n                                                    the operator is Gt or Lt, the\n                                                    values array must have a single\n                                                    element, which will be interpreted\n                                                    as an integer. This array is replaced\n                                                    during a strategic merge patch.\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                              required:\n                                              - key\n                                              - operator\n                                              type: object\n                                            type: array\n                                        type: object\n                                      weight:\n                                        description: Weight associated with matching\n                                          the corresponding nodeSelectorTerm, in the\n                                          range 1-100.\n                                        format: int32\n                                        type: integer\n                                    required:\n                                    - preference\n                                    - weight\n                                    type: object\n                                  type: array\n                                requiredDuringSchedulingIgnoredDuringExecution:\n                                  description: If the affinity requirements specified\n                                    by this field are not met at scheduling time,\n                                    the pod will not be scheduled onto the node. If\n                                    the affinity requirements specified by this field\n                                    cease to be met at some point during pod execution\n                                    (e.g. due to an update), the system may or may\n                                    not try to eventually evict the pod from its node.\n                                  properties:\n                                    nodeSelectorTerms:\n                                      description: Required. A list of node selector\n                                        terms. The terms are ORed.\n                                      items:\n                                        description: A null or empty node selector\n                                          term matches no objects. The requirements\n                                          of them are ANDed. The TopologySelectorTerm\n                                          type implements a subset of the NodeSelectorTerm.\n                                        properties:\n                                          matchExpressions:\n                                            description: A list of node selector requirements\n                                              by node's labels.\n                                            items:\n                                              description: A node selector requirement\n                                                is a selector that contains values,\n                                                a key, and an operator that relates\n                                                the key and values.\n                                              properties:\n                                                key:\n                                                  description: The label key that\n                                                    the selector applies to.\n                                                  type: string\n                                                operator:\n                                                  description: Represents a key's\n                                                    relationship to a set of values.\n                                                    Valid operators are In, NotIn,\n                                                    Exists, DoesNotExist. Gt, and\n                                                    Lt.\n                                                  type: string\n                                                values:\n                                                  description: An array of string\n                                                    values. If the operator is In\n                                                    or NotIn, the values array must\n                                                    be non-empty. If the operator\n                                                    is Exists or DoesNotExist, the\n                                                    values array must be empty. If\n                                                    the operator is Gt or Lt, the\n                                                    values array must have a single\n                                                    element, which will be interpreted\n                                                    as an integer. This array is replaced\n                                                    during a strategic merge patch.\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                              required:\n                                              - key\n                                              - operator\n                                              type: object\n                                            type: array\n                                          matchFields:\n                                            description: A list of node selector requirements\n                                              by node's fields.\n                                            items:\n                                              description: A node selector requirement\n                                                is a selector that contains values,\n                                                a key, and an operator that relates\n                                                the key and values.\n                                              properties:\n                                                key:\n                                                  description: The label key that\n                                                    the selector applies to.\n                                                  type: string\n                                                operator:\n                                                  description: Represents a key's\n                                                    relationship to a set of values.\n                                                    Valid operators are In, NotIn,\n                                                    Exists, DoesNotExist. Gt, and\n                                                    Lt.\n                                                  type: string\n                                                values:\n                                                  description: An array of string\n                                                    values. If the operator is In\n                                                    or NotIn, the values array must\n                                                    be non-empty. If the operator\n                                                    is Exists or DoesNotExist, the\n                                                    values array must be empty. If\n                                                    the operator is Gt or Lt, the\n                                                    values array must have a single\n                                                    element, which will be interpreted\n                                                    as an integer. This array is replaced\n                                                    during a strategic merge patch.\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                              required:\n                                              - key\n                                              - operator\n                                              type: object\n                                            type: array\n                                        type: object\n                                      type: array\n                                  required:\n                                  - nodeSelectorTerms\n                                  type: object\n                              type: object\n                            podAffinity:\n                              description: Describes pod affinity scheduling rules\n                                (e.g. co-locate this pod in the same node, zone, etc.\n                                as some other pod(s)).\n                              properties:\n                                preferredDuringSchedulingIgnoredDuringExecution:\n                                  description: The scheduler will prefer to schedule\n                                    pods to nodes that satisfy the affinity expressions\n                                    specified by this field, but it may choose a node\n                                    that violates one or more of the expressions.\n                                    The node that is most preferred is the one with\n                                    the greatest sum of weights, i.e. for each node\n                                    that meets all of the scheduling requirements\n                                    (resource request, requiredDuringScheduling affinity\n                                    expressions, etc.), compute a sum by iterating\n                                    through the elements of this field and adding\n                                    \"weight\" to the sum if the node has pods which\n                                    matches the corresponding podAffinityTerm; the\n                                    node(s) with the highest sum are the most preferred.\n                                  items:\n                                    description: The weights of all of the matched\n                                      WeightedPodAffinityTerm fields are added per-node\n                                      to find the most preferred node(s)\n                                    properties:\n                                      podAffinityTerm:\n                                        description: Required. A pod affinity term,\n                                          associated with the corresponding weight.\n                                        properties:\n                                          labelSelector:\n                                            description: A label query over a set\n                                              of resources, in this case pods.\n                                            properties:\n                                              matchExpressions:\n                                                description: matchExpressions is a\n                                                  list of label selector requirements.\n                                                  The requirements are ANDed.\n                                                items:\n                                                  description: A label selector requirement\n                                                    is a selector that contains values,\n                                                    a key, and an operator that relates\n                                                    the key and values.\n                                                  properties:\n                                                    key:\n                                                      description: key is the label\n                                                        key that the selector applies\n                                                        to.\n                                                      type: string\n                                                    operator:\n                                                      description: operator represents\n                                                        a key's relationship to a\n                                                        set of values. Valid operators\n                                                        are In, NotIn, Exists and\n                                                        DoesNotExist.\n                                                      type: string\n                                                    values:\n                                                      description: values is an array\n                                                        of string values. If the operator\n                                                        is In or NotIn, the values\n                                                        array must be non-empty. If\n                                                        the operator is Exists or\n                                                        DoesNotExist, the values array\n                                                        must be empty. This array\n                                                        is replaced during a strategic\n                                                        merge patch.\n                                                      items:\n                                                        type: string\n                                                      type: array\n                                                  required:\n                                                  - key\n                                                  - operator\n                                                  type: object\n                                                type: array\n                                              matchLabels:\n                                                additionalProperties:\n                                                  type: string\n                                                description: matchLabels is a map\n                                                  of {key,value} pairs. A single {key,value}\n                                                  in the matchLabels map is equivalent\n                                                  to an element of matchExpressions,\n                                                  whose key field is \"key\", the operator\n                                                  is \"In\", and the values array contains\n                                                  only \"value\". The requirements are\n                                                  ANDed.\n                                                type: object\n                                            type: object\n                                          namespaces:\n                                            description: namespaces specifies which\n                                              namespaces the labelSelector applies\n                                              to (matches against); null or empty\n                                              list means \"this pod's namespace\"\n                                            items:\n                                              type: string\n                                            type: array\n                                          topologyKey:\n                                            description: This pod should be co-located\n                                              (affinity) or not co-located (anti-affinity)\n                                              with the pods matching the labelSelector\n                                              in the specified namespaces, where co-located\n                                              is defined as running on a node whose\n                                              value of the label with key topologyKey\n                                              matches that of any node on which any\n                                              of the selected pods is running. Empty\n                                              topologyKey is not allowed.\n                                            type: string\n                                        required:\n                                        - topologyKey\n                                        type: object\n                                      weight:\n                                        description: weight associated with matching\n                                          the corresponding podAffinityTerm, in the\n                                          range 1-100.\n                                        format: int32\n                                        type: integer\n                                    required:\n                                    - podAffinityTerm\n                                    - weight\n                                    type: object\n                                  type: array\n                                requiredDuringSchedulingIgnoredDuringExecution:\n                                  description: If the affinity requirements specified\n                                    by this field are not met at scheduling time,\n                                    the pod will not be scheduled onto the node. If\n                                    the affinity requirements specified by this field\n                                    cease to be met at some point during pod execution\n                                    (e.g. due to a pod label update), the system may\n                                    or may not try to eventually evict the pod from\n                                    its node. When there are multiple elements, the\n                                    lists of nodes corresponding to each podAffinityTerm\n                                    are intersected, i.e. all terms must be satisfied.\n                                  items:\n                                    description: Defines a set of pods (namely those\n                                      matching the labelSelector relative to the given\n                                      namespace(s)) that this pod should be co-located\n                                      (affinity) or not co-located (anti-affinity)\n                                      with, where co-located is defined as running\n                                      on a node whose value of the label with key\n                                      <topologyKey> matches that of any node on which\n                                      a pod of the set of pods is running\n                                    properties:\n                                      labelSelector:\n                                        description: A label query over a set of resources,\n                                          in this case pods.\n                                        properties:\n                                          matchExpressions:\n                                            description: matchExpressions is a list\n                                              of label selector requirements. The\n                                              requirements are ANDed.\n                                            items:\n                                              description: A label selector requirement\n                                                is a selector that contains values,\n                                                a key, and an operator that relates\n                                                the key and values.\n                                              properties:\n                                                key:\n                                                  description: key is the label key\n                                                    that the selector applies to.\n                                                  type: string\n                                                operator:\n                                                  description: operator represents\n                                                    a key's relationship to a set\n                                                    of values. Valid operators are\n                                                    In, NotIn, Exists and DoesNotExist.\n                                                  type: string\n                                                values:\n                                                  description: values is an array\n                                                    of string values. If the operator\n                                                    is In or NotIn, the values array\n                                                    must be non-empty. If the operator\n                                                    is Exists or DoesNotExist, the\n                                                    values array must be empty. This\n                                                    array is replaced during a strategic\n                                                    merge patch.\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                              required:\n                                              - key\n                                              - operator\n                                              type: object\n                                            type: array\n                                          matchLabels:\n                                            additionalProperties:\n                                              type: string\n                                            description: matchLabels is a map of {key,value}\n                                              pairs. A single {key,value} in the matchLabels\n                                              map is equivalent to an element of matchExpressions,\n                                              whose key field is \"key\", the operator\n                                              is \"In\", and the values array contains\n                                              only \"value\". The requirements are ANDed.\n                                            type: object\n                                        type: object\n                                      namespaces:\n                                        description: namespaces specifies which namespaces\n                                          the labelSelector applies to (matches against);\n                                          null or empty list means \"this pod's namespace\"\n                                        items:\n                                          type: string\n                                        type: array\n                                      topologyKey:\n                                        description: This pod should be co-located\n                                          (affinity) or not co-located (anti-affinity)\n                                          with the pods matching the labelSelector\n                                          in the specified namespaces, where co-located\n                                          is defined as running on a node whose value\n                                          of the label with key topologyKey matches\n                                          that of any node on which any of the selected\n                                          pods is running. Empty topologyKey is not\n                                          allowed.\n                                        type: string\n                                    required:\n                                    - topologyKey\n                                    type: object\n                                  type: array\n                              type: object\n                            podAntiAffinity:\n                              description: Describes pod anti-affinity scheduling\n                                rules (e.g. avoid putting this pod in the same node,\n                                zone, etc. as some other pod(s)).\n                              properties:\n                                preferredDuringSchedulingIgnoredDuringExecution:\n                                  description: The scheduler will prefer to schedule\n                                    pods to nodes that satisfy the anti-affinity expressions\n                                    specified by this field, but it may choose a node\n                                    that violates one or more of the expressions.\n                                    The node that is most preferred is the one with\n                                    the greatest sum of weights, i.e. for each node\n                                    that meets all of the scheduling requirements\n                                    (resource request, requiredDuringScheduling anti-affinity\n                                    expressions, etc.), compute a sum by iterating\n                                    through the elements of this field and adding\n                                    \"weight\" to the sum if the node has pods which\n                                    matches the corresponding podAffinityTerm; the\n                                    node(s) with the highest sum are the most preferred.\n                                  items:\n                                    description: The weights of all of the matched\n                                      WeightedPodAffinityTerm fields are added per-node\n                                      to find the most preferred node(s)\n                                    properties:\n                                      podAffinityTerm:\n                                        description: Required. A pod affinity term,\n                                          associated with the corresponding weight.\n                                        properties:\n                                          labelSelector:\n                                            description: A label query over a set\n                                              of resources, in this case pods.\n                                            properties:\n                                              matchExpressions:\n                                                description: matchExpressions is a\n                                                  list of label selector requirements.\n                                                  The requirements are ANDed.\n                                                items:\n                                                  description: A label selector requirement\n                                                    is a selector that contains values,\n                                                    a key, and an operator that relates\n                                                    the key and values.\n                                                  properties:\n                                                    key:\n                                                      description: key is the label\n                                                        key that the selector applies\n                                                        to.\n                                                      type: string\n                                                    operator:\n                                                      description: operator represents\n                                                        a key's relationship to a\n                                                        set of values. Valid operators\n                                                        are In, NotIn, Exists and\n                                                        DoesNotExist.\n                                                      type: string\n                                                    values:\n                                                      description: values is an array\n                                                        of string values. If the operator\n                                                        is In or NotIn, the values\n                                                        array must be non-empty. If\n                                                        the operator is Exists or\n                                                        DoesNotExist, the values array\n                                                        must be empty. This array\n                                                        is replaced during a strategic\n                                                        merge patch.\n                                                      items:\n                                                        type: string\n                                                      type: array\n                                                  required:\n                                                  - key\n                                                  - operator\n                                                  type: object\n                                                type: array\n                                              matchLabels:\n                                                additionalProperties:\n                                                  type: string\n                                                description: matchLabels is a map\n                                                  of {key,value} pairs. A single {key,value}\n                                                  in the matchLabels map is equivalent\n                                                  to an element of matchExpressions,\n                                                  whose key field is \"key\", the operator\n                                                  is \"In\", and the values array contains\n                                                  only \"value\". The requirements are\n                                                  ANDed.\n                                                type: object\n                                            type: object\n                                          namespaces:\n                                            description: namespaces specifies which\n                                              namespaces the labelSelector applies\n                                              to (matches against); null or empty\n                                              list means \"this pod's namespace\"\n                                            items:\n                                              type: string\n                                            type: array\n                                          topologyKey:\n                                            description: This pod should be co-located\n                                              (affinity) or not co-located (anti-affinity)\n                                              with the pods matching the labelSelector\n                                              in the specified namespaces, where co-located\n                                              is defined as running on a node whose\n                                              value of the label with key topologyKey\n                                              matches that of any node on which any\n                                              of the selected pods is running. Empty\n                                              topologyKey is not allowed.\n                                            type: string\n                                        required:\n                                        - topologyKey\n                                        type: object\n                                      weight:\n                                        description: weight associated with matching\n                                          the corresponding podAffinityTerm, in the\n                                          range 1-100.\n                                        format: int32\n                                        type: integer\n                                    required:\n                                    - podAffinityTerm\n                                    - weight\n                                    type: object\n                                  type: array\n                                requiredDuringSchedulingIgnoredDuringExecution:\n                                  description: If the anti-affinity requirements specified\n                                    by this field are not met at scheduling time,\n                                    the pod will not be scheduled onto the node. If\n                                    the anti-affinity requirements specified by this\n                                    field cease to be met at some point during pod\n                                    execution (e.g. due to a pod label update), the\n                                    system may or may not try to eventually evict\n                                    the pod from its node. When there are multiple\n                                    elements, the lists of nodes corresponding to\n                                    each podAffinityTerm are intersected, i.e. all\n                                    terms must be satisfied.\n                                  items:\n                                    description: Defines a set of pods (namely those\n                                      matching the labelSelector relative to the given\n                                      namespace(s)) that this pod should be co-located\n                                      (affinity) or not co-located (anti-affinity)\n                                      with, where co-located is defined as running\n                                      on a node whose value of the label with key\n                                      <topologyKey> matches that of any node on which\n                                      a pod of the set of pods is running\n                                    properties:\n                                      labelSelector:\n                                        description: A label query over a set of resources,\n                                          in this case pods.\n                                        properties:\n                                          matchExpressions:\n                                            description: matchExpressions is a list\n                                              of label selector requirements. The\n                                              requirements are ANDed.\n                                            items:\n                                              description: A label selector requirement\n                                                is a selector that contains values,\n                                                a key, and an operator that relates\n                                                the key and values.\n                                              properties:\n                                                key:\n                                                  description: key is the label key\n                                                    that the selector applies to.\n                                                  type: string\n                                                operator:\n                                                  description: operator represents\n                                                    a key's relationship to a set\n                                                    of values. Valid operators are\n                                                    In, NotIn, Exists and DoesNotExist.\n                                                  type: string\n                                                values:\n                                                  description: values is an array\n                                                    of string values. If the operator\n                                                    is In or NotIn, the values array\n                                                    must be non-empty. If the operator\n                                                    is Exists or DoesNotExist, the\n                                                    values array must be empty. This\n                                                    array is replaced during a strategic\n                                                    merge patch.\n                                                  items:\n                                                    type: string\n                                                  type: array\n                                              required:\n                                              - key\n                                              - operator\n                                              type: object\n                                            type: array\n                                          matchLabels:\n                                            additionalProperties:\n                                              type: string\n                                            description: matchLabels is a map of {key,value}\n                                              pairs. A single {key,value} in the matchLabels\n                                              map is equivalent to an element of matchExpressions,\n                                              whose key field is \"key\", the operator\n                                              is \"In\", and the values array contains\n                                              only \"value\". The requirements are ANDed.\n                                            type: object\n                                        type: object\n                                      namespaces:\n                                        description: namespaces specifies which namespaces\n                                          the labelSelector applies to (matches against);\n                                          null or empty list means \"this pod's namespace\"\n                                        items:\n                                          type: string\n                                        type: array\n                                      topologyKey:\n                                        description: This pod should be co-located\n                                          (affinity) or not co-located (anti-affinity)\n                                          with the pods matching the labelSelector\n                                          in the specified namespaces, where co-located\n                                          is defined as running on a node whose value\n                                          of the label with key topologyKey matches\n                                          that of any node on which any of the selected\n                                          pods is running. Empty topologyKey is not\n                                          allowed.\n                                        type: string\n                                    required:\n                                    - topologyKey\n                                    type: object\n                                  type: array\n                              type: object\n                          type: object\n                        automountServiceAccountToken:\n                          description: AutomountServiceAccountToken indicates whether\n                            a service account token should be automatically mounted.\n                          type: boolean\n                        containers:\n                          description: List of containers belonging to the pod. Containers\n                            cannot currently be added or removed. There must be at\n                            least one container in a Pod. Cannot be updated.\n                          items:\n                            description: A single application container that you want\n                              to run within a pod.\n                            properties:\n                              args:\n                                description: 'Arguments to the entrypoint. The docker\n                                  image''s CMD is used if this is not provided. Variable\n                                  references $(VAR_NAME) are expanded using the container''s\n                                  environment. If a variable cannot be resolved, the\n                                  reference in the input string will be unchanged.\n                                  The $(VAR_NAME) syntax can be escaped with a double\n                                  $$, ie: $$(VAR_NAME). Escaped references will never\n                                  be expanded, regardless of whether the variable\n                                  exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell'\n                                items:\n                                  type: string\n                                type: array\n                              command:\n                                description: 'Entrypoint array. Not executed within\n                                  a shell. The docker image''s ENTRYPOINT is used\n                                  if this is not provided. Variable references $(VAR_NAME)\n                                  are expanded using the container''s environment.\n                                  If a variable cannot be resolved, the reference\n                                  in the input string will be unchanged. The $(VAR_NAME)\n                                  syntax can be escaped with a double $$, ie: $$(VAR_NAME).\n                                  Escaped references will never be expanded, regardless\n                                  of whether the variable exists or not. Cannot be\n                                  updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell'\n                                items:\n                                  type: string\n                                type: array\n                              env:\n                                description: List of environment variables to set\n                                  in the container. Cannot be updated.\n                                items:\n                                  description: EnvVar represents an environment variable\n                                    present in a Container.\n                                  properties:\n                                    name:\n                                      description: Name of the environment variable.\n                                        Must be a C_IDENTIFIER.\n                                      type: string\n                                    value:\n                                      description: 'Variable references $(VAR_NAME)\n                                        are expanded using the previous defined environment\n                                        variables in the container and any service\n                                        environment variables. If a variable cannot\n                                        be resolved, the reference in the input string\n                                        will be unchanged. The $(VAR_NAME) syntax\n                                        can be escaped with a double $$, ie: $$(VAR_NAME).\n                                        Escaped references will never be expanded,\n                                        regardless of whether the variable exists\n                                        or not. Defaults to \"\".'\n                                      type: string\n                                    valueFrom:\n                                      description: Source for the environment variable's\n                                        value. Cannot be used if value is not empty.\n                                      properties:\n                                        configMapKeyRef:\n                                          description: Selects a key of a ConfigMap.\n                                          properties:\n                                            key:\n                                              description: The key to select.\n                                              type: string\n                                            name:\n                                              description: 'Name of the referent.\n                                                More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                                TODO: Add other useful fields. apiVersion,\n                                                kind, uid?'\n                                              type: string\n                                            optional:\n                                              description: Specify whether the ConfigMap\n                                                or its key must be defined\n                                              type: boolean\n                                          required:\n                                          - key\n                                          type: object\n                                        fieldRef:\n                                          description: 'Selects a field of the pod:\n                                            supports metadata.name, metadata.namespace,\n                                            metadata.labels, metadata.annotations,\n                                            spec.nodeName, spec.serviceAccountName,\n                                            status.hostIP, status.podIP, status.podIPs.'\n                                          properties:\n                                            apiVersion:\n                                              description: Version of the schema the\n                                                FieldPath is written in terms of,\n                                                defaults to \"v1\".\n                                              type: string\n                                            fieldPath:\n                                              description: Path of the field to select\n                                                in the specified API version.\n                                              type: string\n                                          required:\n                                          - fieldPath\n                                          type: object\n                                        resourceFieldRef:\n                                          description: 'Selects a resource of the\n                                            container: only resources limits and requests\n                                            (limits.cpu, limits.memory, limits.ephemeral-storage,\n                                            requests.cpu, requests.memory and requests.ephemeral-storage)\n                                            are currently supported.'\n                                          properties:\n                                            containerName:\n                                              description: 'Container name: required\n                                                for volumes, optional for env vars'\n                                              type: string\n                                            divisor:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              description: Specifies the output format\n                                                of the exposed resources, defaults\n                                                to \"1\"\n                                              pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                              x-kubernetes-int-or-string: true\n                                            resource:\n                                              description: 'Required: resource to\n                                                select'\n                                              type: string\n                                          required:\n                                          - resource\n                                          type: object\n                                        secretKeyRef:\n                                          description: Selects a key of a secret in\n                                            the pod's namespace\n                                          properties:\n                                            key:\n                                              description: The key of the secret to\n                                                select from.  Must be a valid secret\n                                                key.\n                                              type: string\n                                            name:\n                                              description: 'Name of the referent.\n                                                More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                                TODO: Add other useful fields. apiVersion,\n                                                kind, uid?'\n                                              type: string\n                                            optional:\n                                              description: Specify whether the Secret\n                                                or its key must be defined\n                                              type: boolean\n                                          required:\n                                          - key\n                                          type: object\n                                      type: object\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                              envFrom:\n                                description: List of sources to populate environment\n                                  variables in the container. The keys defined within\n                                  a source must be a C_IDENTIFIER. All invalid keys\n                                  will be reported as an event when the container\n                                  is starting. When a key exists in multiple sources,\n                                  the value associated with the last source will take\n                                  precedence. Values defined by an Env with a duplicate\n                                  key will take precedence. Cannot be updated.\n                                items:\n                                  description: EnvFromSource represents the source\n                                    of a set of ConfigMaps\n                                  properties:\n                                    configMapRef:\n                                      description: The ConfigMap to select from\n                                      properties:\n                                        name:\n                                          description: 'Name of the referent. More\n                                            info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                            TODO: Add other useful fields. apiVersion,\n                                            kind, uid?'\n                                          type: string\n                                        optional:\n                                          description: Specify whether the ConfigMap\n                                            must be defined\n                                          type: boolean\n                                      type: object\n                                    prefix:\n                                      description: An optional identifier to prepend\n                                        to each key in the ConfigMap. Must be a C_IDENTIFIER.\n                                      type: string\n                                    secretRef:\n                                      description: The Secret to select from\n                                      properties:\n                                        name:\n                                          description: 'Name of the referent. More\n                                            info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                            TODO: Add other useful fields. apiVersion,\n                                            kind, uid?'\n                                          type: string\n                                        optional:\n                                          description: Specify whether the Secret\n                                            must be defined\n                                          type: boolean\n                                      type: object\n                                  type: object\n                                type: array\n                              image:\n                                description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images\n                                  This field is optional to allow higher level config\n                                  management to default or override container images\n                                  in workload controllers like Deployments and StatefulSets.'\n                                type: string\n                              imagePullPolicy:\n                                description: 'Image pull policy. One of Always, Never,\n                                  IfNotPresent. Defaults to Always if :latest tag\n                                  is specified, or IfNotPresent otherwise. Cannot\n                                  be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images'\n                                type: string\n                              lifecycle:\n                                description: Actions that the management system should\n                                  take in response to container lifecycle events.\n                                  Cannot be updated.\n                                properties:\n                                  postStart:\n                                    description: 'PostStart is called immediately\n                                      after a container is created. If the handler\n                                      fails, the container is terminated and restarted\n                                      according to its restart policy. Other management\n                                      of the container blocks until the hook completes.\n                                      More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks'\n                                    properties:\n                                      exec:\n                                        description: One and only one of the following\n                                          should be specified. Exec specifies the\n                                          action to take.\n                                        properties:\n                                          command:\n                                            description: Command is the command line\n                                              to execute inside the container, the\n                                              working directory for the command  is\n                                              root ('/') in the container's filesystem.\n                                              The command is simply exec'd, it is\n                                              not run inside a shell, so traditional\n                                              shell instructions ('|', etc) won't\n                                              work. To use a shell, you need to explicitly\n                                              call out to that shell. Exit status\n                                              of 0 is treated as live/healthy and\n                                              non-zero is unhealthy.\n                                            items:\n                                              type: string\n                                            type: array\n                                        type: object\n                                      httpGet:\n                                        description: HTTPGet specifies the http request\n                                          to perform.\n                                        properties:\n                                          host:\n                                            description: Host name to connect to,\n                                              defaults to the pod IP. You probably\n                                              want to set \"Host\" in httpHeaders instead.\n                                            type: string\n                                          httpHeaders:\n                                            description: Custom headers to set in\n                                              the request. HTTP allows repeated headers.\n                                            items:\n                                              description: HTTPHeader describes a\n                                                custom header to be used in HTTP probes\n                                              properties:\n                                                name:\n                                                  description: The header field name\n                                                  type: string\n                                                value:\n                                                  description: The header field value\n                                                  type: string\n                                              required:\n                                              - name\n                                              - value\n                                              type: object\n                                            type: array\n                                          path:\n                                            description: Path to access on the HTTP\n                                              server.\n                                            type: string\n                                          port:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            description: Name or number of the port\n                                              to access on the container. Number must\n                                              be in the range 1 to 65535. Name must\n                                              be an IANA_SVC_NAME.\n                                            x-kubernetes-int-or-string: true\n                                          scheme:\n                                            description: Scheme to use for connecting\n                                              to the host. Defaults to HTTP.\n                                            type: string\n                                        required:\n                                        - port\n                                        type: object\n                                      tcpSocket:\n                                        description: 'TCPSocket specifies an action\n                                          involving a TCP port. TCP hooks not yet\n                                          supported TODO: implement a realistic TCP\n                                          lifecycle hook'\n                                        properties:\n                                          host:\n                                            description: 'Optional: Host name to connect\n                                              to, defaults to the pod IP.'\n                                            type: string\n                                          port:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            description: Number or name of the port\n                                              to access on the container. Number must\n                                              be in the range 1 to 65535. Name must\n                                              be an IANA_SVC_NAME.\n                                            x-kubernetes-int-or-string: true\n                                        required:\n                                        - port\n                                        type: object\n                                    type: object\n                                  preStop:\n                                    description: 'PreStop is called immediately before\n                                      a container is terminated due to an API request\n                                      or management event such as liveness/startup\n                                      probe failure, preemption, resource contention,\n                                      etc. The handler is not called if the container\n                                      crashes or exits. The reason for termination\n                                      is passed to the handler. The Pod''s termination\n                                      grace period countdown begins before the PreStop\n                                      hooked is executed. Regardless of the outcome\n                                      of the handler, the container will eventually\n                                      terminate within the Pod''s termination grace\n                                      period. Other management of the container blocks\n                                      until the hook completes or until the termination\n                                      grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks'\n                                    properties:\n                                      exec:\n                                        description: One and only one of the following\n                                          should be specified. Exec specifies the\n                                          action to take.\n                                        properties:\n                                          command:\n                                            description: Command is the command line\n                                              to execute inside the container, the\n                                              working directory for the command  is\n                                              root ('/') in the container's filesystem.\n                                              The command is simply exec'd, it is\n                                              not run inside a shell, so traditional\n                                              shell instructions ('|', etc) won't\n                                              work. To use a shell, you need to explicitly\n                                              call out to that shell. Exit status\n                                              of 0 is treated as live/healthy and\n                                              non-zero is unhealthy.\n                                            items:\n                                              type: string\n                                            type: array\n                                        type: object\n                                      httpGet:\n                                        description: HTTPGet specifies the http request\n                                          to perform.\n                                        properties:\n                                          host:\n                                            description: Host name to connect to,\n                                              defaults to the pod IP. You probably\n                                              want to set \"Host\" in httpHeaders instead.\n                                            type: string\n                                          httpHeaders:\n                                            description: Custom headers to set in\n                                              the request. HTTP allows repeated headers.\n                                            items:\n                                              description: HTTPHeader describes a\n                                                custom header to be used in HTTP probes\n                                              properties:\n                                                name:\n                                                  description: The header field name\n                                                  type: string\n                                                value:\n                                                  description: The header field value\n                                                  type: string\n                                              required:\n                                              - name\n                                              - value\n                                              type: object\n                                            type: array\n                                          path:\n                                            description: Path to access on the HTTP\n                                              server.\n                                            type: string\n                                          port:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            description: Name or number of the port\n                                              to access on the container. Number must\n                                              be in the range 1 to 65535. Name must\n                                              be an IANA_SVC_NAME.\n                                            x-kubernetes-int-or-string: true\n                                          scheme:\n                                            description: Scheme to use for connecting\n                                              to the host. Defaults to HTTP.\n                                            type: string\n                                        required:\n                                        - port\n                                        type: object\n                                      tcpSocket:\n                                        description: 'TCPSocket specifies an action\n                                          involving a TCP port. TCP hooks not yet\n                                          supported TODO: implement a realistic TCP\n                                          lifecycle hook'\n                                        properties:\n                                          host:\n                                            description: 'Optional: Host name to connect\n                                              to, defaults to the pod IP.'\n                                            type: string\n                                          port:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            description: Number or name of the port\n                                              to access on the container. Number must\n                                              be in the range 1 to 65535. Name must\n                                              be an IANA_SVC_NAME.\n                                            x-kubernetes-int-or-string: true\n                                        required:\n                                        - port\n                                        type: object\n                                    type: object\n                                type: object\n                              livenessProbe:\n                                description: 'Periodic probe of container liveness.\n                                  Container will be restarted if the probe fails.\n                                  Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                                properties:\n                                  exec:\n                                    description: One and only one of the following\n                                      should be specified. Exec specifies the action\n                                      to take.\n                                    properties:\n                                      command:\n                                        description: Command is the command line to\n                                          execute inside the container, the working\n                                          directory for the command  is root ('/')\n                                          in the container's filesystem. The command\n                                          is simply exec'd, it is not run inside a\n                                          shell, so traditional shell instructions\n                                          ('|', etc) won't work. To use a shell, you\n                                          need to explicitly call out to that shell.\n                                          Exit status of 0 is treated as live/healthy\n                                          and non-zero is unhealthy.\n                                        items:\n                                          type: string\n                                        type: array\n                                    type: object\n                                  failureThreshold:\n                                    description: Minimum consecutive failures for\n                                      the probe to be considered failed after having\n                                      succeeded. Defaults to 3. Minimum value is 1.\n                                    format: int32\n                                    type: integer\n                                  httpGet:\n                                    description: HTTPGet specifies the http request\n                                      to perform.\n                                    properties:\n                                      host:\n                                        description: Host name to connect to, defaults\n                                          to the pod IP. You probably want to set\n                                          \"Host\" in httpHeaders instead.\n                                        type: string\n                                      httpHeaders:\n                                        description: Custom headers to set in the\n                                          request. HTTP allows repeated headers.\n                                        items:\n                                          description: HTTPHeader describes a custom\n                                            header to be used in HTTP probes\n                                          properties:\n                                            name:\n                                              description: The header field name\n                                              type: string\n                                            value:\n                                              description: The header field value\n                                              type: string\n                                          required:\n                                          - name\n                                          - value\n                                          type: object\n                                        type: array\n                                      path:\n                                        description: Path to access on the HTTP server.\n                                        type: string\n                                      port:\n                                        anyOf:\n                                        - type: integer\n                                        - type: string\n                                        description: Name or number of the port to\n                                          access on the container. Number must be\n                                          in the range 1 to 65535. Name must be an\n                                          IANA_SVC_NAME.\n                                        x-kubernetes-int-or-string: true\n                                      scheme:\n                                        description: Scheme to use for connecting\n                                          to the host. Defaults to HTTP.\n                                        type: string\n                                    required:\n                                    - port\n                                    type: object\n                                  initialDelaySeconds:\n                                    description: 'Number of seconds after the container\n                                      has started before liveness probes are initiated.\n                                      More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                                    format: int32\n                                    type: integer\n                                  periodSeconds:\n                                    description: How often (in seconds) to perform\n                                      the probe. Default to 10 seconds. Minimum value\n                                      is 1.\n                                    format: int32\n                                    type: integer\n                                  successThreshold:\n                                    description: Minimum consecutive successes for\n                                      the probe to be considered successful after\n                                      having failed. Defaults to 1. Must be 1 for\n                                      liveness and startup. Minimum value is 1.\n                                    format: int32\n                                    type: integer\n                                  tcpSocket:\n                                    description: 'TCPSocket specifies an action involving\n                                      a TCP port. TCP hooks not yet supported TODO:\n                                      implement a realistic TCP lifecycle hook'\n                                    properties:\n                                      host:\n                                        description: 'Optional: Host name to connect\n                                          to, defaults to the pod IP.'\n                                        type: string\n                                      port:\n                                        anyOf:\n                                        - type: integer\n                                        - type: string\n                                        description: Number or name of the port to\n                                          access on the container. Number must be\n                                          in the range 1 to 65535. Name must be an\n                                          IANA_SVC_NAME.\n                                        x-kubernetes-int-or-string: true\n                                    required:\n                                    - port\n                                    type: object\n                                  timeoutSeconds:\n                                    description: 'Number of seconds after which the\n                                      probe times out. Defaults to 1 second. Minimum\n                                      value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                                    format: int32\n                                    type: integer\n                                type: object\n                              name:\n                                description: Name of the container specified as a\n                                  DNS_LABEL. Each container in a pod must have a unique\n                                  name (DNS_LABEL). Cannot be updated.\n                                type: string\n                              ports:\n                                description: List of ports to expose from the container.\n                                  Exposing a port here gives the system additional\n                                  information about the network connections a container\n                                  uses, but is primarily informational. Not specifying\n                                  a port here DOES NOT prevent that port from being\n                                  exposed. Any port which is listening on the default\n                                  \"0.0.0.0\" address inside a container will be accessible\n                                  from the network. Cannot be updated.\n                                items:\n                                  description: ContainerPort represents a network\n                                    port in a single container.\n                                  properties:\n                                    containerPort:\n                                      description: Number of port to expose on the\n                                        pod's IP address. This must be a valid port\n                                        number, 0 < x < 65536.\n                                      format: int32\n                                      type: integer\n                                    hostIP:\n                                      description: What host IP to bind the external\n                                        port to.\n                                      type: string\n                                    hostPort:\n                                      description: Number of port to expose on the\n                                        host. If specified, this must be a valid port\n                                        number, 0 < x < 65536. If HostNetwork is specified,\n                                        this must match ContainerPort. Most containers\n                                        do not need this.\n                                      format: int32\n                                      type: integer\n                                    name:\n                                      description: If specified, this must be an IANA_SVC_NAME\n                                        and unique within the pod. Each named port\n                                        in a pod must have a unique name. Name for\n                                        the port that can be referred to by services.\n                                      type: string\n                                    protocol:\n                                      description: Protocol for port. Must be UDP,\n                                        TCP, or SCTP. Defaults to \"TCP\".\n                                      type: string\n                                  required:\n                                  - containerPort\n                                  - protocol\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - containerPort\n                                - protocol\n                                x-kubernetes-list-type: map\n                              readinessProbe:\n                                description: 'Periodic probe of container service\n                                  readiness. Container will be removed from service\n                                  endpoints if the probe fails. Cannot be updated.\n                                  More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                                properties:\n                                  exec:\n                                    description: One and only one of the following\n                                      should be specified. Exec specifies the action\n                                      to take.\n                                    properties:\n                                      command:\n                                        description: Command is the command line to\n                                          execute inside the container, the working\n                                          directory for the command  is root ('/')\n                                          in the container's filesystem. The command\n                                          is simply exec'd, it is not run inside a\n                                          shell, so traditional shell instructions\n                                          ('|', etc) won't work. To use a shell, you\n                                          need to explicitly call out to that shell.\n                                          Exit status of 0 is treated as live/healthy\n                                          and non-zero is unhealthy.\n                                        items:\n                                          type: string\n                                        type: array\n                                    type: object\n                                  failureThreshold:\n                                    description: Minimum consecutive failures for\n                                      the probe to be considered failed after having\n                                      succeeded. Defaults to 3. Minimum value is 1.\n                                    format: int32\n                                    type: integer\n                                  httpGet:\n                                    description: HTTPGet specifies the http request\n                                      to perform.\n                                    properties:\n                                      host:\n                                        description: Host name to connect to, defaults\n                                          to the pod IP. You probably want to set\n                                          \"Host\" in httpHeaders instead.\n                                        type: string\n                                      httpHeaders:\n                                        description: Custom headers to set in the\n                                          request. HTTP allows repeated headers.\n                                        items:\n                                          description: HTTPHeader describes a custom\n                                            header to be used in HTTP probes\n                                          properties:\n                                            name:\n                                              description: The header field name\n                                              type: string\n                                            value:\n                                              description: The header field value\n                                              type: string\n                                          required:\n                                          - name\n                                          - value\n                                          type: object\n                                        type: array\n                                      path:\n                                        description: Path to access on the HTTP server.\n                                        type: string\n                                      port:\n                                        anyOf:\n                                        - type: integer\n                                        - type: string\n                                        description: Name or number of the port to\n                                          access on the container. Number must be\n                                          in the range 1 to 65535. Name must be an\n                                          IANA_SVC_NAME.\n                                        x-kubernetes-int-or-string: true\n                                      scheme:\n                                        description: Scheme to use for connecting\n                                          to the host. Defaults to HTTP.\n                                        type: string\n                                    required:\n                                    - port\n                                    type: object\n                                  initialDelaySeconds:\n                                    description: 'Number of seconds after the container\n                                      has started before liveness probes are initiated.\n                                      More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                                    format: int32\n                                    type: integer\n                                  periodSeconds:\n                                    description: How often (in seconds) to perform\n                                      the probe. Default to 10 seconds. Minimum value\n                                      is 1.\n                                    format: int32\n                                    type: integer\n                                  successThreshold:\n                                    description: Minimum consecutive successes for\n                                      the probe to be considered successful after\n                                      having failed. Defaults to 1. Must be 1 for\n                                      liveness and startup. Minimum value is 1.\n                                    format: int32\n                                    type: integer\n                                  tcpSocket:\n                                    description: 'TCPSocket specifies an action involving\n                                      a TCP port. TCP hooks not yet supported TODO:\n                                      implement a realistic TCP lifecycle hook'\n                                    properties:\n                                      host:\n                                        description: 'Optional: Host name to connect\n                                          to, defaults to the pod IP.'\n                                        type: string\n                                      port:\n                                        anyOf:\n                                        - type: integer\n                                        - type: string\n                                        description: Number or name of the port to\n                                          access on the container. Number must be\n                                          in the range 1 to 65535. Name must be an\n                                          IANA_SVC_NAME.\n                                        x-kubernetes-int-or-string: true\n                                    required:\n                                    - port\n                                    type: object\n                                  timeoutSeconds:\n                                    description: 'Number of seconds after which the\n                                      probe times out. Defaults to 1 second. Minimum\n                                      value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                                    format: int32\n                                    type: integer\n                                type: object\n                              resources:\n                                description: 'Compute Resources required by this container.\n                                  Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/'\n                                properties:\n                                  limits:\n                                    additionalProperties:\n                                      anyOf:\n                                      - type: integer\n                                      - type: string\n                                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                      x-kubernetes-int-or-string: true\n                                    description: 'Limits describes the maximum amount\n                                      of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/'\n                                    type: object\n                                  requests:\n                                    additionalProperties:\n                                      anyOf:\n                                      - type: integer\n                                      - type: string\n                                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                      x-kubernetes-int-or-string: true\n                                    description: 'Requests describes the minimum amount\n                                      of compute resources required. If Requests is\n                                      omitted for a container, it defaults to Limits\n                                      if that is explicitly specified, otherwise to\n                                      an implementation-defined value. More info:\n                                      https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/'\n                                    type: object\n                                type: object\n                              securityContext:\n                                description: 'Security options the pod should run\n                                  with. More info: https://kubernetes.io/docs/concepts/policy/security-context/\n                                  More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/'\n                                properties:\n                                  allowPrivilegeEscalation:\n                                    description: 'AllowPrivilegeEscalation controls\n                                      whether a process can gain more privileges than\n                                      its parent process. This bool directly controls\n                                      if the no_new_privs flag will be set on the\n                                      container process. AllowPrivilegeEscalation\n                                      is true always when the container is: 1) run\n                                      as Privileged 2) has CAP_SYS_ADMIN'\n                                    type: boolean\n                                  capabilities:\n                                    description: The capabilities to add/drop when\n                                      running containers. Defaults to the default\n                                      set of capabilities granted by the container\n                                      runtime.\n                                    properties:\n                                      add:\n                                        description: Added capabilities\n                                        items:\n                                          description: Capability represent POSIX\n                                            capabilities type\n                                          type: string\n                                        type: array\n                                      drop:\n                                        description: Removed capabilities\n                                        items:\n                                          description: Capability represent POSIX\n                                            capabilities type\n                                          type: string\n                                        type: array\n                                    type: object\n                                  privileged:\n                                    description: Run container in privileged mode.\n                                      Processes in privileged containers are essentially\n                                      equivalent to root on the host. Defaults to\n                                      false.\n                                    type: boolean\n                                  procMount:\n                                    description: procMount denotes the type of proc\n                                      mount to use for the containers. The default\n                                      is DefaultProcMount which uses the container\n                                      runtime defaults for readonly paths and masked\n                                      paths. This requires the ProcMountType feature\n                                      flag to be enabled.\n                                    type: string\n                                  readOnlyRootFilesystem:\n                                    description: Whether this container has a read-only\n                                      root filesystem. Default is false.\n                                    type: boolean\n                                  runAsGroup:\n                                    description: The GID to run the entrypoint of\n                                      the container process. Uses runtime default\n                                      if unset. May also be set in PodSecurityContext.  If\n                                      set in both SecurityContext and PodSecurityContext,\n                                      the value specified in SecurityContext takes\n                                      precedence.\n                                    format: int64\n                                    type: integer\n                                  runAsNonRoot:\n                                    description: Indicates that the container must\n                                      run as a non-root user. If true, the Kubelet\n                                      will validate the image at runtime to ensure\n                                      that it does not run as UID 0 (root) and fail\n                                      to start the container if it does. If unset\n                                      or false, no such validation will be performed.\n                                      May also be set in PodSecurityContext.  If set\n                                      in both SecurityContext and PodSecurityContext,\n                                      the value specified in SecurityContext takes\n                                      precedence.\n                                    type: boolean\n                                  runAsUser:\n                                    description: The UID to run the entrypoint of\n                                      the container process. Defaults to user specified\n                                      in image metadata if unspecified. May also be\n                                      set in PodSecurityContext.  If set in both SecurityContext\n                                      and PodSecurityContext, the value specified\n                                      in SecurityContext takes precedence.\n                                    format: int64\n                                    type: integer\n                                  seLinuxOptions:\n                                    description: The SELinux context to be applied\n                                      to the container. If unspecified, the container\n                                      runtime will allocate a random SELinux context\n                                      for each container.  May also be set in PodSecurityContext.  If\n                                      set in both SecurityContext and PodSecurityContext,\n                                      the value specified in SecurityContext takes\n                                      precedence.\n                                    properties:\n                                      level:\n                                        description: Level is SELinux level label\n                                          that applies to the container.\n                                        type: string\n                                      role:\n                                        description: Role is a SELinux role label\n                                          that applies to the container.\n                                        type: string\n                                      type:\n                                        description: Type is a SELinux type label\n                                          that applies to the container.\n                                        type: string\n                                      user:\n                                        description: User is a SELinux user label\n                                          that applies to the container.\n                                        type: string\n                                    type: object\n                                  windowsOptions:\n                                    description: The Windows specific settings applied\n                                      to all containers. If unspecified, the options\n                                      from the PodSecurityContext will be used. If\n                                      set in both SecurityContext and PodSecurityContext,\n                                      the value specified in SecurityContext takes\n                                      precedence.\n                                    properties:\n                                      gmsaCredentialSpec:\n                                        description: GMSACredentialSpec is where the\n                                          GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa)\n                                          inlines the contents of the GMSA credential\n                                          spec named by the GMSACredentialSpecName\n                                          field.\n                                        type: string\n                                      gmsaCredentialSpecName:\n                                        description: GMSACredentialSpecName is the\n                                          name of the GMSA credential spec to use.\n                                        type: string\n                                      runAsUserName:\n                                        description: The UserName in Windows to run\n                                          the entrypoint of the container process.\n                                          Defaults to the user specified in image\n                                          metadata if unspecified. May also be set\n                                          in PodSecurityContext. If set in both SecurityContext\n                                          and PodSecurityContext, the value specified\n                                          in SecurityContext takes precedence.\n                                        type: string\n                                    type: object\n                                type: object\n                              startupProbe:\n                                description: 'StartupProbe indicates that the Pod\n                                  has successfully initialized. If specified, no other\n                                  probes are executed until this completes successfully.\n                                  If this probe fails, the Pod will be restarted,\n                                  just as if the livenessProbe failed. This can be\n                                  used to provide different probe parameters at the\n                                  beginning of a Pod''s lifecycle, when it might take\n                                  a long time to load data or warm a cache, than during\n                                  steady-state operation. This cannot be updated.\n                                  This is a beta feature enabled by the StartupProbe\n                                  feature flag. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                                properties:\n                                  exec:\n                                    description: One and only one of the following\n                                      should be specified. Exec specifies the action\n                                      to take.\n                                    properties:\n                                      command:\n                                        description: Command is the command line to\n                                          execute inside the container, the working\n                                          directory for the command  is root ('/')\n                                          in the container's filesystem. The command\n                                          is simply exec'd, it is not run inside a\n                                          shell, so traditional shell instructions\n                                          ('|', etc) won't work. To use a shell, you\n                                          need to explicitly call out to that shell.\n                                          Exit status of 0 is treated as live/healthy\n                                          and non-zero is unhealthy.\n                                        items:\n                                          type: string\n                                        type: array\n                                    type: object\n                                  failureThreshold:\n                                    description: Minimum consecutive failures for\n                                      the probe to be considered failed after having\n                                      succeeded. Defaults to 3. Minimum value is 1.\n                                    format: int32\n                                    type: integer\n                                  httpGet:\n                                    description: HTTPGet specifies the http request\n                                      to perform.\n                                    properties:\n                                      host:\n                                        description: Host name to connect to, defaults\n                                          to the pod IP. You probably want to set\n                                          \"Host\" in httpHeaders instead.\n                                        type: string\n                                      httpHeaders:\n                                        description: Custom headers to set in the\n                                          request. HTTP allows repeated headers.\n                                        items:\n                                          description: HTTPHeader describes a custom\n                                            header to be used in HTTP probes\n                                          properties:\n                                            name:\n                                              description: The header field name\n                                              type: string\n                                            value:\n                                              description: The header field value\n                                              type: string\n                                          required:\n                                          - name\n                                          - value\n                                          type: object\n                                        type: array\n                                      path:\n                                        description: Path to access on the HTTP server.\n                                        type: string\n                                      port:\n                                        anyOf:\n                                        - type: integer\n                                        - type: string\n                                        description: Name or number of the port to\n                                          access on the container. Number must be\n                                          in the range 1 to 65535. Name must be an\n                                          IANA_SVC_NAME.\n                                        x-kubernetes-int-or-string: true\n                                      scheme:\n                                        description: Scheme to use for connecting\n                                          to the host. Defaults to HTTP.\n                                        type: string\n                                    required:\n                                    - port\n                                    type: object\n                                  initialDelaySeconds:\n                                    description: 'Number of seconds after the container\n                                      has started before liveness probes are initiated.\n                                      More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                                    format: int32\n                                    type: integer\n                                  periodSeconds:\n                                    description: How often (in seconds) to perform\n                                      the probe. Default to 10 seconds. Minimum value\n                                      is 1.\n                                    format: int32\n                                    type: integer\n                                  successThreshold:\n                                    description: Minimum consecutive successes for\n                                      the probe to be considered successful after\n                                      having failed. Defaults to 1. Must be 1 for\n                                      liveness and startup. Minimum value is 1.\n                                    format: int32\n                                    type: integer\n                                  tcpSocket:\n                                    description: 'TCPSocket specifies an action involving\n                                      a TCP port. TCP hooks not yet supported TODO:\n                                      implement a realistic TCP lifecycle hook'\n                                    properties:\n                                      host:\n                                        description: 'Optional: Host name to connect\n                                          to, defaults to the pod IP.'\n                                        type: string\n                                      port:\n                                        anyOf:\n                                        - type: integer\n                                        - type: string\n                                        description: Number or name of the port to\n                                          access on the container. Number must be\n                                          in the range 1 to 65535. Name must be an\n                                          IANA_SVC_NAME.\n                                        x-kubernetes-int-or-string: true\n                                    required:\n                                    - port\n                                    type: object\n                                  timeoutSeconds:\n                                    description: 'Number of seconds after which the\n                                      probe times out. Defaults to 1 second. Minimum\n                                      value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                                    format: int32\n                                    type: integer\n                                type: object\n                              stdin:\n                                description: Whether this container should allocate\n                                  a buffer for stdin in the container runtime. If\n                                  this is not set, reads from stdin in the container\n                                  will always result in EOF. Default is false.\n                                type: boolean\n                              stdinOnce:\n                                description: Whether the container runtime should\n                                  close the stdin channel after it has been opened\n                                  by a single attach. When stdin is true the stdin\n                                  stream will remain open across multiple attach sessions.\n                                  If stdinOnce is set to true, stdin is opened on\n                                  container start, is empty until the first client\n                                  attaches to stdin, and then remains open and accepts\n                                  data until the client disconnects, at which time\n                                  stdin is closed and remains closed until the container\n                                  is restarted. If this flag is false, a container\n                                  processes that reads from stdin will never receive\n                                  an EOF. Default is false\n                                type: boolean\n                              terminationMessagePath:\n                                description: 'Optional: Path at which the file to\n                                  which the container''s termination message will\n                                  be written is mounted into the container''s filesystem.\n                                  Message written is intended to be brief final status,\n                                  such as an assertion failure message. Will be truncated\n                                  by the node if greater than 4096 bytes. The total\n                                  message length across all containers will be limited\n                                  to 12kb. Defaults to /dev/termination-log. Cannot\n                                  be updated.'\n                                type: string\n                              terminationMessagePolicy:\n                                description: Indicate how the termination message\n                                  should be populated. File will use the contents\n                                  of terminationMessagePath to populate the container\n                                  status message on both success and failure. FallbackToLogsOnError\n                                  will use the last chunk of container log output\n                                  if the termination message file is empty and the\n                                  container exited with an error. The log output is\n                                  limited to 2048 bytes or 80 lines, whichever is\n                                  smaller. Defaults to File. Cannot be updated.\n                                type: string\n                              tty:\n                                description: Whether this container should allocate\n                                  a TTY for itself, also requires 'stdin' to be true.\n                                  Default is false.\n                                type: boolean\n                              volumeDevices:\n                                description: volumeDevices is the list of block devices\n                                  to be used by the container.\n                                items:\n                                  description: volumeDevice describes a mapping of\n                                    a raw block device within a container.\n                                  properties:\n                                    devicePath:\n                                      description: devicePath is the path inside of\n                                        the container that the device will be mapped\n                                        to.\n                                      type: string\n                                    name:\n                                      description: name must match the name of a persistentVolumeClaim\n                                        in the pod\n                                      type: string\n                                  required:\n                                  - devicePath\n                                  - name\n                                  type: object\n                                type: array\n                              volumeMounts:\n                                description: Pod volumes to mount into the container's\n                                  filesystem. Cannot be updated.\n                                items:\n                                  description: VolumeMount describes a mounting of\n                                    a Volume within a container.\n                                  properties:\n                                    mountPath:\n                                      description: Path within the container at which\n                                        the volume should be mounted.  Must not contain\n                                        ':'.\n                                      type: string\n                                    mountPropagation:\n                                      description: mountPropagation determines how\n                                        mounts are propagated from the host to container\n                                        and the other way around. When not set, MountPropagationNone\n                                        is used. This field is beta in 1.10.\n                                      type: string\n                                    name:\n                                      description: This must match the Name of a Volume.\n                                      type: string\n                                    readOnly:\n                                      description: Mounted read-only if true, read-write\n                                        otherwise (false or unspecified). Defaults\n                                        to false.\n                                      type: boolean\n                                    subPath:\n                                      description: Path within the volume from which\n                                        the container's volume should be mounted.\n                                        Defaults to \"\" (volume's root).\n                                      type: string\n                                    subPathExpr:\n                                      description: Expanded path within the volume\n                                        from which the container's volume should be\n                                        mounted. Behaves similarly to SubPath but\n                                        environment variable references $(VAR_NAME)\n                                        are expanded using the container's environment.\n                                        Defaults to \"\" (volume's root). SubPathExpr\n                                        and SubPath are mutually exclusive.\n                                      type: string\n                                  required:\n                                  - mountPath\n                                  - name\n                                  type: object\n                                type: array\n                              workingDir:\n                                description: Container's working directory. If not\n                                  specified, the container runtime's default will\n                                  be used, which might be configured in the container\n                                  image. Cannot be updated.\n                                type: string\n                            required:\n                            - name\n                            type: object\n                          type: array\n                        dnsConfig:\n                          description: Specifies the DNS parameters of a pod. Parameters\n                            specified here will be merged to the generated DNS configuration\n                            based on DNSPolicy.\n                          properties:\n                            nameservers:\n                              description: A list of DNS name server IP addresses.\n                                This will be appended to the base nameservers generated\n                                from DNSPolicy. Duplicated nameservers will be removed.\n                              items:\n                                type: string\n                              type: array\n                            options:\n                              description: A list of DNS resolver options. This will\n                                be merged with the base options generated from DNSPolicy.\n                                Duplicated entries will be removed. Resolution options\n                                given in Options will override those that appear in\n                                the base DNSPolicy.\n                              items:\n                                description: PodDNSConfigOption defines DNS resolver\n                                  options of a pod.\n                                properties:\n                                  name:\n                                    description: Required.\n                                    type: string\n                                  value:\n                                    type: string\n                                type: object\n                              type: array\n                            searches:\n                              description: A list of DNS search domains for host-name\n                                lookup. This will be appended to the base search paths\n                                generated from DNSPolicy. Duplicated search paths\n                                will be removed.\n                              items:\n                                type: string\n                              type: array\n                          type: object\n                        dnsPolicy:\n                          description: Set DNS policy for the pod. Defaults to \"ClusterFirst\".\n                            Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst',\n                            'Default' or 'None'. DNS parameters given in DNSConfig\n                            will be merged with the policy selected with DNSPolicy.\n                            To have DNS options set along with hostNetwork, you have\n                            to specify DNS policy explicitly to 'ClusterFirstWithHostNet'.\n                          type: string\n                        enableServiceLinks:\n                          description: 'EnableServiceLinks indicates whether information\n                            about services should be injected into pod''s environment\n                            variables, matching the syntax of Docker links. Optional:\n                            Defaults to true.'\n                          type: boolean\n                        ephemeralContainers:\n                          description: List of ephemeral containers run in this pod.\n                            Ephemeral containers may be run in an existing pod to\n                            perform user-initiated actions such as debugging. This\n                            list cannot be specified when creating a pod, and it cannot\n                            be modified by updating the pod spec. In order to add\n                            an ephemeral container to an existing pod, use the pod's\n                            ephemeralcontainers subresource. This field is alpha-level\n                            and is only honored by servers that enable the EphemeralContainers\n                            feature.\n                          items:\n                            description: An EphemeralContainer is a container that\n                              may be added temporarily to an existing pod for user-initiated\n                              activities such as debugging. Ephemeral containers have\n                              no resource or scheduling guarantees, and they will\n                              not be restarted when they exit or when a pod is removed\n                              or restarted. If an ephemeral container causes a pod\n                              to exceed its resource allocation, the pod may be evicted.\n                              Ephemeral containers may not be added by directly updating\n                              the pod spec. They must be added via the pod's ephemeralcontainers\n                              subresource, and they will appear in the pod spec once\n                              added. This is an alpha feature enabled by the EphemeralContainers\n                              feature flag.\n                            properties:\n                              args:\n                                description: 'Arguments to the entrypoint. The docker\n                                  image''s CMD is used if this is not provided. Variable\n                                  references $(VAR_NAME) are expanded using the container''s\n                                  environment. If a variable cannot be resolved, the\n                                  reference in the input string will be unchanged.\n                                  The $(VAR_NAME) syntax can be escaped with a double\n                                  $$, ie: $$(VAR_NAME). Escaped references will never\n                                  be expanded, regardless of whether the variable\n                                  exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell'\n                                items:\n                                  type: string\n                                type: array\n                              command:\n                                description: 'Entrypoint array. Not executed within\n                                  a shell. The docker image''s ENTRYPOINT is used\n                                  if this is not provided. Variable references $(VAR_NAME)\n                                  are expanded using the container''s environment.\n                                  If a variable cannot be resolved, the reference\n                                  in the input string will be unchanged. The $(VAR_NAME)\n                                  syntax can be escaped with a double $$, ie: $$(VAR_NAME).\n                                  Escaped references will never be expanded, regardless\n                                  of whether the variable exists or not. Cannot be\n                                  updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell'\n                                items:\n                                  type: string\n                                type: array\n                              env:\n                                description: List of environment variables to set\n                                  in the container. Cannot be updated.\n                                items:\n                                  description: EnvVar represents an environment variable\n                                    present in a Container.\n                                  properties:\n                                    name:\n                                      description: Name of the environment variable.\n                                        Must be a C_IDENTIFIER.\n                                      type: string\n                                    value:\n                                      description: 'Variable references $(VAR_NAME)\n                                        are expanded using the previous defined environment\n                                        variables in the container and any service\n                                        environment variables. If a variable cannot\n                                        be resolved, the reference in the input string\n                                        will be unchanged. The $(VAR_NAME) syntax\n                                        can be escaped with a double $$, ie: $$(VAR_NAME).\n                                        Escaped references will never be expanded,\n                                        regardless of whether the variable exists\n                                        or not. Defaults to \"\".'\n                                      type: string\n                                    valueFrom:\n                                      description: Source for the environment variable's\n                                        value. Cannot be used if value is not empty.\n                                      properties:\n                                        configMapKeyRef:\n                                          description: Selects a key of a ConfigMap.\n                                          properties:\n                                            key:\n                                              description: The key to select.\n                                              type: string\n                                            name:\n                                              description: 'Name of the referent.\n                                                More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                                TODO: Add other useful fields. apiVersion,\n                                                kind, uid?'\n                                              type: string\n                                            optional:\n                                              description: Specify whether the ConfigMap\n                                                or its key must be defined\n                                              type: boolean\n                                          required:\n                                          - key\n                                          type: object\n                                        fieldRef:\n                                          description: 'Selects a field of the pod:\n                                            supports metadata.name, metadata.namespace,\n                                            metadata.labels, metadata.annotations,\n                                            spec.nodeName, spec.serviceAccountName,\n                                            status.hostIP, status.podIP, status.podIPs.'\n                                          properties:\n                                            apiVersion:\n                                              description: Version of the schema the\n                                                FieldPath is written in terms of,\n                                                defaults to \"v1\".\n                                              type: string\n                                            fieldPath:\n                                              description: Path of the field to select\n                                                in the specified API version.\n                                              type: string\n                                          required:\n                                          - fieldPath\n                                          type: object\n                                        resourceFieldRef:\n                                          description: 'Selects a resource of the\n                                            container: only resources limits and requests\n                                            (limits.cpu, limits.memory, limits.ephemeral-storage,\n                                            requests.cpu, requests.memory and requests.ephemeral-storage)\n                                            are currently supported.'\n                                          properties:\n                                            containerName:\n                                              description: 'Container name: required\n                                                for volumes, optional for env vars'\n                                              type: string\n                                            divisor:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              description: Specifies the output format\n                                                of the exposed resources, defaults\n                                                to \"1\"\n                                              pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                              x-kubernetes-int-or-string: true\n                                            resource:\n                                              description: 'Required: resource to\n                                                select'\n                                              type: string\n                                          required:\n                                          - resource\n                                          type: object\n                                        secretKeyRef:\n                                          description: Selects a key of a secret in\n                                            the pod's namespace\n                                          properties:\n                                            key:\n                                              description: The key of the secret to\n                                                select from.  Must be a valid secret\n                                                key.\n                                              type: string\n                                            name:\n                                              description: 'Name of the referent.\n                                                More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                                TODO: Add other useful fields. apiVersion,\n                                                kind, uid?'\n                                              type: string\n                                            optional:\n                                              description: Specify whether the Secret\n                                                or its key must be defined\n                                              type: boolean\n                                          required:\n                                          - key\n                                          type: object\n                                      type: object\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                              envFrom:\n                                description: List of sources to populate environment\n                                  variables in the container. The keys defined within\n                                  a source must be a C_IDENTIFIER. All invalid keys\n                                  will be reported as an event when the container\n                                  is starting. When a key exists in multiple sources,\n                                  the value associated with the last source will take\n                                  precedence. Values defined by an Env with a duplicate\n                                  key will take precedence. Cannot be updated.\n                                items:\n                                  description: EnvFromSource represents the source\n                                    of a set of ConfigMaps\n                                  properties:\n                                    configMapRef:\n                                      description: The ConfigMap to select from\n                                      properties:\n                                        name:\n                                          description: 'Name of the referent. More\n                                            info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                            TODO: Add other useful fields. apiVersion,\n                                            kind, uid?'\n                                          type: string\n                                        optional:\n                                          description: Specify whether the ConfigMap\n                                            must be defined\n                                          type: boolean\n                                      type: object\n                                    prefix:\n                                      description: An optional identifier to prepend\n                                        to each key in the ConfigMap. Must be a C_IDENTIFIER.\n                                      type: string\n                                    secretRef:\n                                      description: The Secret to select from\n                                      properties:\n                                        name:\n                                          description: 'Name of the referent. More\n                                            info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                            TODO: Add other useful fields. apiVersion,\n                                            kind, uid?'\n                                          type: string\n                                        optional:\n                                          description: Specify whether the Secret\n                                            must be defined\n                                          type: boolean\n                                      type: object\n                                  type: object\n                                type: array\n                              image:\n                                description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images'\n                                type: string\n                              imagePullPolicy:\n                                description: 'Image pull policy. One of Always, Never,\n                                  IfNotPresent. Defaults to Always if :latest tag\n                                  is specified, or IfNotPresent otherwise. Cannot\n                                  be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images'\n                                type: string\n                              lifecycle:\n                                description: Lifecycle is not allowed for ephemeral\n                                  containers.\n                                properties:\n                                  postStart:\n                                    description: 'PostStart is called immediately\n                                      after a container is created. If the handler\n                                      fails, the container is terminated and restarted\n                                      according to its restart policy. Other management\n                                      of the container blocks until the hook completes.\n                                      More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks'\n                                    properties:\n                                      exec:\n                                        description: One and only one of the following\n                                          should be specified. Exec specifies the\n                                          action to take.\n                                        properties:\n                                          command:\n                                            description: Command is the command line\n                                              to execute inside the container, the\n                                              working directory for the command  is\n                                              root ('/') in the container's filesystem.\n                                              The command is simply exec'd, it is\n                                              not run inside a shell, so traditional\n                                              shell instructions ('|', etc) won't\n                                              work. To use a shell, you need to explicitly\n                                              call out to that shell. Exit status\n                                              of 0 is treated as live/healthy and\n                                              non-zero is unhealthy.\n                                            items:\n                                              type: string\n                                            type: array\n                                        type: object\n                                      httpGet:\n                                        description: HTTPGet specifies the http request\n                                          to perform.\n                                        properties:\n                                          host:\n                                            description: Host name to connect to,\n                                              defaults to the pod IP. You probably\n                                              want to set \"Host\" in httpHeaders instead.\n                                            type: string\n                                          httpHeaders:\n                                            description: Custom headers to set in\n                                              the request. HTTP allows repeated headers.\n                                            items:\n                                              description: HTTPHeader describes a\n                                                custom header to be used in HTTP probes\n                                              properties:\n                                                name:\n                                                  description: The header field name\n                                                  type: string\n                                                value:\n                                                  description: The header field value\n                                                  type: string\n                                              required:\n                                              - name\n                                              - value\n                                              type: object\n                                            type: array\n                                          path:\n                                            description: Path to access on the HTTP\n                                              server.\n                                            type: string\n                                          port:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            description: Name or number of the port\n                                              to access on the container. Number must\n                                              be in the range 1 to 65535. Name must\n                                              be an IANA_SVC_NAME.\n                                            x-kubernetes-int-or-string: true\n                                          scheme:\n                                            description: Scheme to use for connecting\n                                              to the host. Defaults to HTTP.\n                                            type: string\n                                        required:\n                                        - port\n                                        type: object\n                                      tcpSocket:\n                                        description: 'TCPSocket specifies an action\n                                          involving a TCP port. TCP hooks not yet\n                                          supported TODO: implement a realistic TCP\n                                          lifecycle hook'\n                                        properties:\n                                          host:\n                                            description: 'Optional: Host name to connect\n                                              to, defaults to the pod IP.'\n                                            type: string\n                                          port:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            description: Number or name of the port\n                                              to access on the container. Number must\n                                              be in the range 1 to 65535. Name must\n                                              be an IANA_SVC_NAME.\n                                            x-kubernetes-int-or-string: true\n                                        required:\n                                        - port\n                                        type: object\n                                    type: object\n                                  preStop:\n                                    description: 'PreStop is called immediately before\n                                      a container is terminated due to an API request\n                                      or management event such as liveness/startup\n                                      probe failure, preemption, resource contention,\n                                      etc. The handler is not called if the container\n                                      crashes or exits. The reason for termination\n                                      is passed to the handler. The Pod''s termination\n                                      grace period countdown begins before the PreStop\n                                      hooked is executed. Regardless of the outcome\n                                      of the handler, the container will eventually\n                                      terminate within the Pod''s termination grace\n                                      period. Other management of the container blocks\n                                      until the hook completes or until the termination\n                                      grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks'\n                                    properties:\n                                      exec:\n                                        description: One and only one of the following\n                                          should be specified. Exec specifies the\n                                          action to take.\n                                        properties:\n                                          command:\n                                            description: Command is the command line\n                                              to execute inside the container, the\n                                              working directory for the command  is\n                                              root ('/') in the container's filesystem.\n                                              The command is simply exec'd, it is\n                                              not run inside a shell, so traditional\n                                              shell instructions ('|', etc) won't\n                                              work. To use a shell, you need to explicitly\n                                              call out to that shell. Exit status\n                                              of 0 is treated as live/healthy and\n                                              non-zero is unhealthy.\n                                            items:\n                                              type: string\n                                            type: array\n                                        type: object\n                                      httpGet:\n                                        description: HTTPGet specifies the http request\n                                          to perform.\n                                        properties:\n                                          host:\n                                            description: Host name to connect to,\n                                              defaults to the pod IP. You probably\n                                              want to set \"Host\" in httpHeaders instead.\n                                            type: string\n                                          httpHeaders:\n                                            description: Custom headers to set in\n                                              the request. HTTP allows repeated headers.\n                                            items:\n                                              description: HTTPHeader describes a\n                                                custom header to be used in HTTP probes\n                                              properties:\n                                                name:\n                                                  description: The header field name\n                                                  type: string\n                                                value:\n                                                  description: The header field value\n                                                  type: string\n                                              required:\n                                              - name\n                                              - value\n                                              type: object\n                                            type: array\n                                          path:\n                                            description: Path to access on the HTTP\n                                              server.\n                                            type: string\n                                          port:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            description: Name or number of the port\n                                              to access on the container. Number must\n                                              be in the range 1 to 65535. Name must\n                                              be an IANA_SVC_NAME.\n                                            x-kubernetes-int-or-string: true\n                                          scheme:\n                                            description: Scheme to use for connecting\n                                              to the host. Defaults to HTTP.\n                                            type: string\n                                        required:\n                                        - port\n                                        type: object\n                                      tcpSocket:\n                                        description: 'TCPSocket specifies an action\n                                          involving a TCP port. TCP hooks not yet\n                                          supported TODO: implement a realistic TCP\n                                          lifecycle hook'\n                                        properties:\n                                          host:\n                                            description: 'Optional: Host name to connect\n                                              to, defaults to the pod IP.'\n                                            type: string\n                                          port:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            description: Number or name of the port\n                                              to access on the container. Number must\n                                              be in the range 1 to 65535. Name must\n                                              be an IANA_SVC_NAME.\n                                            x-kubernetes-int-or-string: true\n                                        required:\n                                        - port\n                                        type: object\n                                    type: object\n                                type: object\n                              livenessProbe:\n                                description: Probes are not allowed for ephemeral\n                                  containers.\n                                properties:\n                                  exec:\n                                    description: One and only one of the following\n                                      should be specified. Exec specifies the action\n                                      to take.\n                                    properties:\n                                      command:\n                                        description: Command is the command line to\n                                          execute inside the container, the working\n                                          directory for the command  is root ('/')\n                                          in the container's filesystem. The command\n                                          is simply exec'd, it is not run inside a\n                                          shell, so traditional shell instructions\n                                          ('|', etc) won't work. To use a shell, you\n                                          need to explicitly call out to that shell.\n                                          Exit status of 0 is treated as live/healthy\n                                          and non-zero is unhealthy.\n                                        items:\n                                          type: string\n                                        type: array\n                                    type: object\n                                  failureThreshold:\n                                    description: Minimum consecutive failures for\n                                      the probe to be considered failed after having\n                                      succeeded. Defaults to 3. Minimum value is 1.\n                                    format: int32\n                                    type: integer\n                                  httpGet:\n                                    description: HTTPGet specifies the http request\n                                      to perform.\n                                    properties:\n                                      host:\n                                        description: Host name to connect to, defaults\n                                          to the pod IP. You probably want to set\n                                          \"Host\" in httpHeaders instead.\n                                        type: string\n                                      httpHeaders:\n                                        description: Custom headers to set in the\n                                          request. HTTP allows repeated headers.\n                                        items:\n                                          description: HTTPHeader describes a custom\n                                            header to be used in HTTP probes\n                                          properties:\n                                            name:\n                                              description: The header field name\n                                              type: string\n                                            value:\n                                              description: The header field value\n                                              type: string\n                                          required:\n                                          - name\n                                          - value\n                                          type: object\n                                        type: array\n                                      path:\n                                        description: Path to access on the HTTP server.\n                                        type: string\n                                      port:\n                                        anyOf:\n                                        - type: integer\n                                        - type: string\n                                        description: Name or number of the port to\n                                          access on the container. Number must be\n                                          in the range 1 to 65535. Name must be an\n                                          IANA_SVC_NAME.\n                                        x-kubernetes-int-or-string: true\n                                      scheme:\n                                        description: Scheme to use for connecting\n                                          to the host. Defaults to HTTP.\n                                        type: string\n                                    required:\n                                    - port\n                                    type: object\n                                  initialDelaySeconds:\n                                    description: 'Number of seconds after the container\n                                      has started before liveness probes are initiated.\n                                      More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                                    format: int32\n                                    type: integer\n                                  periodSeconds:\n                                    description: How often (in seconds) to perform\n                                      the probe. Default to 10 seconds. Minimum value\n                                      is 1.\n                                    format: int32\n                                    type: integer\n                                  successThreshold:\n                                    description: Minimum consecutive successes for\n                                      the probe to be considered successful after\n                                      having failed. Defaults to 1. Must be 1 for\n                                      liveness and startup. Minimum value is 1.\n                                    format: int32\n                                    type: integer\n                                  tcpSocket:\n                                    description: 'TCPSocket specifies an action involving\n                                      a TCP port. TCP hooks not yet supported TODO:\n                                      implement a realistic TCP lifecycle hook'\n                                    properties:\n                                      host:\n                                        description: 'Optional: Host name to connect\n                                          to, defaults to the pod IP.'\n                                        type: string\n                                      port:\n                                        anyOf:\n                                        - type: integer\n                                        - type: string\n                                        description: Number or name of the port to\n                                          access on the container. Number must be\n                                          in the range 1 to 65535. Name must be an\n                                          IANA_SVC_NAME.\n                                        x-kubernetes-int-or-string: true\n                                    required:\n                                    - port\n                                    type: object\n                                  timeoutSeconds:\n                                    description: 'Number of seconds after which the\n                                      probe times out. Defaults to 1 second. Minimum\n                                      value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                                    format: int32\n                                    type: integer\n                                type: object\n                              name:\n                                description: Name of the ephemeral container specified\n                                  as a DNS_LABEL. This name must be unique among all\n                                  containers, init containers and ephemeral containers.\n                                type: string\n                              ports:\n                                description: Ports are not allowed for ephemeral containers.\n                                items:\n                                  description: ContainerPort represents a network\n                                    port in a single container.\n                                  properties:\n                                    containerPort:\n                                      description: Number of port to expose on the\n                                        pod's IP address. This must be a valid port\n                                        number, 0 < x < 65536.\n                                      format: int32\n                                      type: integer\n                                    hostIP:\n                                      description: What host IP to bind the external\n                                        port to.\n                                      type: string\n                                    hostPort:\n                                      description: Number of port to expose on the\n                                        host. If specified, this must be a valid port\n                                        number, 0 < x < 65536. If HostNetwork is specified,\n                                        this must match ContainerPort. Most containers\n                                        do not need this.\n                                      format: int32\n                                      type: integer\n                                    name:\n                                      description: If specified, this must be an IANA_SVC_NAME\n                                        and unique within the pod. Each named port\n                                        in a pod must have a unique name. Name for\n                                        the port that can be referred to by services.\n                                      type: string\n                                    protocol:\n                                      description: Protocol for port. Must be UDP,\n                                        TCP, or SCTP. Defaults to \"TCP\".\n                                      type: string\n                                  required:\n                                  - containerPort\n                                  - protocol\n                                  type: object\n                                type: array\n                              readinessProbe:\n                                description: Probes are not allowed for ephemeral\n                                  containers.\n                                properties:\n                                  exec:\n                                    description: One and only one of the following\n                                      should be specified. Exec specifies the action\n                                      to take.\n                                    properties:\n                                      command:\n                                        description: Command is the command line to\n                                          execute inside the container, the working\n                                          directory for the command  is root ('/')\n                                          in the container's filesystem. The command\n                                          is simply exec'd, it is not run inside a\n                                          shell, so traditional shell instructions\n                                          ('|', etc) won't work. To use a shell, you\n                                          need to explicitly call out to that shell.\n                                          Exit status of 0 is treated as live/healthy\n                                          and non-zero is unhealthy.\n                                        items:\n                                          type: string\n                                        type: array\n                                    type: object\n                                  failureThreshold:\n                                    description: Minimum consecutive failures for\n                                      the probe to be considered failed after having\n                                      succeeded. Defaults to 3. Minimum value is 1.\n                                    format: int32\n                                    type: integer\n                                  httpGet:\n                                    description: HTTPGet specifies the http request\n                                      to perform.\n                                    properties:\n                                      host:\n                                        description: Host name to connect to, defaults\n                                          to the pod IP. You probably want to set\n                                          \"Host\" in httpHeaders instead.\n                                        type: string\n                                      httpHeaders:\n                                        description: Custom headers to set in the\n                                          request. HTTP allows repeated headers.\n                                        items:\n                                          description: HTTPHeader describes a custom\n                                            header to be used in HTTP probes\n                                          properties:\n                                            name:\n                                              description: The header field name\n                                              type: string\n                                            value:\n                                              description: The header field value\n                                              type: string\n                                          required:\n                                          - name\n                                          - value\n                                          type: object\n                                        type: array\n                                      path:\n                                        description: Path to access on the HTTP server.\n                                        type: string\n                                      port:\n                                        anyOf:\n                                        - type: integer\n                                        - type: string\n                                        description: Name or number of the port to\n                                          access on the container. Number must be\n                                          in the range 1 to 65535. Name must be an\n                                          IANA_SVC_NAME.\n                                        x-kubernetes-int-or-string: true\n                                      scheme:\n                                        description: Scheme to use for connecting\n                                          to the host. Defaults to HTTP.\n                                        type: string\n                                    required:\n                                    - port\n                                    type: object\n                                  initialDelaySeconds:\n                                    description: 'Number of seconds after the container\n                                      has started before liveness probes are initiated.\n                                      More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                                    format: int32\n                                    type: integer\n                                  periodSeconds:\n                                    description: How often (in seconds) to perform\n                                      the probe. Default to 10 seconds. Minimum value\n                                      is 1.\n                                    format: int32\n                                    type: integer\n                                  successThreshold:\n                                    description: Minimum consecutive successes for\n                                      the probe to be considered successful after\n                                      having failed. Defaults to 1. Must be 1 for\n                                      liveness and startup. Minimum value is 1.\n                                    format: int32\n                                    type: integer\n                                  tcpSocket:\n                                    description: 'TCPSocket specifies an action involving\n                                      a TCP port. TCP hooks not yet supported TODO:\n                                      implement a realistic TCP lifecycle hook'\n                                    properties:\n                                      host:\n                                        description: 'Optional: Host name to connect\n                                          to, defaults to the pod IP.'\n                                        type: string\n                                      port:\n                                        anyOf:\n                                        - type: integer\n                                        - type: string\n                                        description: Number or name of the port to\n                                          access on the container. Number must be\n                                          in the range 1 to 65535. Name must be an\n                                          IANA_SVC_NAME.\n                                        x-kubernetes-int-or-string: true\n                                    required:\n                                    - port\n                                    type: object\n                                  timeoutSeconds:\n                                    description: 'Number of seconds after which the\n                                      probe times out. Defaults to 1 second. Minimum\n                                      value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                                    format: int32\n                                    type: integer\n                                type: object\n                              resources:\n                                description: Resources are not allowed for ephemeral\n                                  containers. Ephemeral containers use spare resources\n                                  already allocated to the pod.\n                                properties:\n                                  limits:\n                                    additionalProperties:\n                                      anyOf:\n                                      - type: integer\n                                      - type: string\n                                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                      x-kubernetes-int-or-string: true\n                                    description: 'Limits describes the maximum amount\n                                      of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/'\n                                    type: object\n                                  requests:\n                                    additionalProperties:\n                                      anyOf:\n                                      - type: integer\n                                      - type: string\n                                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                      x-kubernetes-int-or-string: true\n                                    description: 'Requests describes the minimum amount\n                                      of compute resources required. If Requests is\n                                      omitted for a container, it defaults to Limits\n                                      if that is explicitly specified, otherwise to\n                                      an implementation-defined value. More info:\n                                      https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/'\n                                    type: object\n                                type: object\n                              securityContext:\n                                description: SecurityContext is not allowed for ephemeral\n                                  containers.\n                                properties:\n                                  allowPrivilegeEscalation:\n                                    description: 'AllowPrivilegeEscalation controls\n                                      whether a process can gain more privileges than\n                                      its parent process. This bool directly controls\n                                      if the no_new_privs flag will be set on the\n                                      container process. AllowPrivilegeEscalation\n                                      is true always when the container is: 1) run\n                                      as Privileged 2) has CAP_SYS_ADMIN'\n                                    type: boolean\n                                  capabilities:\n                                    description: The capabilities to add/drop when\n                                      running containers. Defaults to the default\n                                      set of capabilities granted by the container\n                                      runtime.\n                                    properties:\n                                      add:\n                                        description: Added capabilities\n                                        items:\n                                          description: Capability represent POSIX\n                                            capabilities type\n                                          type: string\n                                        type: array\n                                      drop:\n                                        description: Removed capabilities\n                                        items:\n                                          description: Capability represent POSIX\n                                            capabilities type\n                                          type: string\n                                        type: array\n                                    type: object\n                                  privileged:\n                                    description: Run container in privileged mode.\n                                      Processes in privileged containers are essentially\n                                      equivalent to root on the host. Defaults to\n                                      false.\n                                    type: boolean\n                                  procMount:\n                                    description: procMount denotes the type of proc\n                                      mount to use for the containers. The default\n                                      is DefaultProcMount which uses the container\n                                      runtime defaults for readonly paths and masked\n                                      paths. This requires the ProcMountType feature\n                                      flag to be enabled.\n                                    type: string\n                                  readOnlyRootFilesystem:\n                                    description: Whether this container has a read-only\n                                      root filesystem. Default is false.\n                                    type: boolean\n                                  runAsGroup:\n                                    description: The GID to run the entrypoint of\n                                      the container process. Uses runtime default\n                                      if unset. May also be set in PodSecurityContext.  If\n                                      set in both SecurityContext and PodSecurityContext,\n                                      the value specified in SecurityContext takes\n                                      precedence.\n                                    format: int64\n                                    type: integer\n                                  runAsNonRoot:\n                                    description: Indicates that the container must\n                                      run as a non-root user. If true, the Kubelet\n                                      will validate the image at runtime to ensure\n                                      that it does not run as UID 0 (root) and fail\n                                      to start the container if it does. If unset\n                                      or false, no such validation will be performed.\n                                      May also be set in PodSecurityContext.  If set\n                                      in both SecurityContext and PodSecurityContext,\n                                      the value specified in SecurityContext takes\n                                      precedence.\n                                    type: boolean\n                                  runAsUser:\n                                    description: The UID to run the entrypoint of\n                                      the container process. Defaults to user specified\n                                      in image metadata if unspecified. May also be\n                                      set in PodSecurityContext.  If set in both SecurityContext\n                                      and PodSecurityContext, the value specified\n                                      in SecurityContext takes precedence.\n                                    format: int64\n                                    type: integer\n                                  seLinuxOptions:\n                                    description: The SELinux context to be applied\n                                      to the container. If unspecified, the container\n                                      runtime will allocate a random SELinux context\n                                      for each container.  May also be set in PodSecurityContext.  If\n                                      set in both SecurityContext and PodSecurityContext,\n                                      the value specified in SecurityContext takes\n                                      precedence.\n                                    properties:\n                                      level:\n                                        description: Level is SELinux level label\n                                          that applies to the container.\n                                        type: string\n                                      role:\n                                        description: Role is a SELinux role label\n                                          that applies to the container.\n                                        type: string\n                                      type:\n                                        description: Type is a SELinux type label\n                                          that applies to the container.\n                                        type: string\n                                      user:\n                                        description: User is a SELinux user label\n                                          that applies to the container.\n                                        type: string\n                                    type: object\n                                  windowsOptions:\n                                    description: The Windows specific settings applied\n                                      to all containers. If unspecified, the options\n                                      from the PodSecurityContext will be used. If\n                                      set in both SecurityContext and PodSecurityContext,\n                                      the value specified in SecurityContext takes\n                                      precedence.\n                                    properties:\n                                      gmsaCredentialSpec:\n                                        description: GMSACredentialSpec is where the\n                                          GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa)\n                                          inlines the contents of the GMSA credential\n                                          spec named by the GMSACredentialSpecName\n                                          field.\n                                        type: string\n                                      gmsaCredentialSpecName:\n                                        description: GMSACredentialSpecName is the\n                                          name of the GMSA credential spec to use.\n                                        type: string\n                                      runAsUserName:\n                                        description: The UserName in Windows to run\n                                          the entrypoint of the container process.\n                                          Defaults to the user specified in image\n                                          metadata if unspecified. May also be set\n                                          in PodSecurityContext. If set in both SecurityContext\n                                          and PodSecurityContext, the value specified\n                                          in SecurityContext takes precedence.\n                                        type: string\n                                    type: object\n                                type: object\n                              startupProbe:\n                                description: Probes are not allowed for ephemeral\n                                  containers.\n                                properties:\n                                  exec:\n                                    description: One and only one of the following\n                                      should be specified. Exec specifies the action\n                                      to take.\n                                    properties:\n                                      command:\n                                        description: Command is the command line to\n                                          execute inside the container, the working\n                                          directory for the command  is root ('/')\n                                          in the container's filesystem. The command\n                                          is simply exec'd, it is not run inside a\n                                          shell, so traditional shell instructions\n                                          ('|', etc) won't work. To use a shell, you\n                                          need to explicitly call out to that shell.\n                                          Exit status of 0 is treated as live/healthy\n                                          and non-zero is unhealthy.\n                                        items:\n                                          type: string\n                                        type: array\n                                    type: object\n                                  failureThreshold:\n                                    description: Minimum consecutive failures for\n                                      the probe to be considered failed after having\n                                      succeeded. Defaults to 3. Minimum value is 1.\n                                    format: int32\n                                    type: integer\n                                  httpGet:\n                                    description: HTTPGet specifies the http request\n                                      to perform.\n                                    properties:\n                                      host:\n                                        description: Host name to connect to, defaults\n                                          to the pod IP. You probably want to set\n                                          \"Host\" in httpHeaders instead.\n                                        type: string\n                                      httpHeaders:\n                                        description: Custom headers to set in the\n                                          request. HTTP allows repeated headers.\n                                        items:\n                                          description: HTTPHeader describes a custom\n                                            header to be used in HTTP probes\n                                          properties:\n                                            name:\n                                              description: The header field name\n                                              type: string\n                                            value:\n                                              description: The header field value\n                                              type: string\n                                          required:\n                                          - name\n                                          - value\n                                          type: object\n                                        type: array\n                                      path:\n                                        description: Path to access on the HTTP server.\n                                        type: string\n                                      port:\n                                        anyOf:\n                                        - type: integer\n                                        - type: string\n                                        description: Name or number of the port to\n                                          access on the container. Number must be\n                                          in the range 1 to 65535. Name must be an\n                                          IANA_SVC_NAME.\n                                        x-kubernetes-int-or-string: true\n                                      scheme:\n                                        description: Scheme to use for connecting\n                                          to the host. Defaults to HTTP.\n                                        type: string\n                                    required:\n                                    - port\n                                    type: object\n                                  initialDelaySeconds:\n                                    description: 'Number of seconds after the container\n                                      has started before liveness probes are initiated.\n                                      More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                                    format: int32\n                                    type: integer\n                                  periodSeconds:\n                                    description: How often (in seconds) to perform\n                                      the probe. Default to 10 seconds. Minimum value\n                                      is 1.\n                                    format: int32\n                                    type: integer\n                                  successThreshold:\n                                    description: Minimum consecutive successes for\n                                      the probe to be considered successful after\n                                      having failed. Defaults to 1. Must be 1 for\n                                      liveness and startup. Minimum value is 1.\n                                    format: int32\n                                    type: integer\n                                  tcpSocket:\n                                    description: 'TCPSocket specifies an action involving\n                                      a TCP port. TCP hooks not yet supported TODO:\n                                      implement a realistic TCP lifecycle hook'\n                                    properties:\n                                      host:\n                                        description: 'Optional: Host name to connect\n                                          to, defaults to the pod IP.'\n                                        type: string\n                                      port:\n                                        anyOf:\n                                        - type: integer\n                                        - type: string\n                                        description: Number or name of the port to\n                                          access on the container. Number must be\n                                          in the range 1 to 65535. Name must be an\n                                          IANA_SVC_NAME.\n                                        x-kubernetes-int-or-string: true\n                                    required:\n                                    - port\n                                    type: object\n                                  timeoutSeconds:\n                                    description: 'Number of seconds after which the\n                                      probe times out. Defaults to 1 second. Minimum\n                                      value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                                    format: int32\n                                    type: integer\n                                type: object\n                              stdin:\n                                description: Whether this container should allocate\n                                  a buffer for stdin in the container runtime. If\n                                  this is not set, reads from stdin in the container\n                                  will always result in EOF. Default is false.\n                                type: boolean\n                              stdinOnce:\n                                description: Whether the container runtime should\n                                  close the stdin channel after it has been opened\n                                  by a single attach. When stdin is true the stdin\n                                  stream will remain open across multiple attach sessions.\n                                  If stdinOnce is set to true, stdin is opened on\n                                  container start, is empty until the first client\n                                  attaches to stdin, and then remains open and accepts\n                                  data until the client disconnects, at which time\n                                  stdin is closed and remains closed until the container\n                                  is restarted. If this flag is false, a container\n                                  processes that reads from stdin will never receive\n                                  an EOF. Default is false\n                                type: boolean\n                              targetContainerName:\n                                description: If set, the name of the container from\n                                  PodSpec that this ephemeral container targets. The\n                                  ephemeral container will be run in the namespaces\n                                  (IPC, PID, etc) of this container. If not set then\n                                  the ephemeral container is run in whatever namespaces\n                                  are shared for the pod. Note that the container\n                                  runtime must support this feature.\n                                type: string\n                              terminationMessagePath:\n                                description: 'Optional: Path at which the file to\n                                  which the container''s termination message will\n                                  be written is mounted into the container''s filesystem.\n                                  Message written is intended to be brief final status,\n                                  such as an assertion failure message. Will be truncated\n                                  by the node if greater than 4096 bytes. The total\n                                  message length across all containers will be limited\n                                  to 12kb. Defaults to /dev/termination-log. Cannot\n                                  be updated.'\n                                type: string\n                              terminationMessagePolicy:\n                                description: Indicate how the termination message\n                                  should be populated. File will use the contents\n                                  of terminationMessagePath to populate the container\n                                  status message on both success and failure. FallbackToLogsOnError\n                                  will use the last chunk of container log output\n                                  if the termination message file is empty and the\n                                  container exited with an error. The log output is\n                                  limited to 2048 bytes or 80 lines, whichever is\n                                  smaller. Defaults to File. Cannot be updated.\n                                type: string\n                              tty:\n                                description: Whether this container should allocate\n                                  a TTY for itself, also requires 'stdin' to be true.\n                                  Default is false.\n                                type: boolean\n                              volumeDevices:\n                                description: volumeDevices is the list of block devices\n                                  to be used by the container.\n                                items:\n                                  description: volumeDevice describes a mapping of\n                                    a raw block device within a container.\n                                  properties:\n                                    devicePath:\n                                      description: devicePath is the path inside of\n                                        the container that the device will be mapped\n                                        to.\n                                      type: string\n                                    name:\n                                      description: name must match the name of a persistentVolumeClaim\n                                        in the pod\n                                      type: string\n                                  required:\n                                  - devicePath\n                                  - name\n                                  type: object\n                                type: array\n                              volumeMounts:\n                                description: Pod volumes to mount into the container's\n                                  filesystem. Cannot be updated.\n                                items:\n                                  description: VolumeMount describes a mounting of\n                                    a Volume within a container.\n                                  properties:\n                                    mountPath:\n                                      description: Path within the container at which\n                                        the volume should be mounted.  Must not contain\n                                        ':'.\n                                      type: string\n                                    mountPropagation:\n                                      description: mountPropagation determines how\n                                        mounts are propagated from the host to container\n                                        and the other way around. When not set, MountPropagationNone\n                                        is used. This field is beta in 1.10.\n                                      type: string\n                                    name:\n                                      description: This must match the Name of a Volume.\n                                      type: string\n                                    readOnly:\n                                      description: Mounted read-only if true, read-write\n                                        otherwise (false or unspecified). Defaults\n                                        to false.\n                                      type: boolean\n                                    subPath:\n                                      description: Path within the volume from which\n                                        the container's volume should be mounted.\n                                        Defaults to \"\" (volume's root).\n                                      type: string\n                                    subPathExpr:\n                                      description: Expanded path within the volume\n                                        from which the container's volume should be\n                                        mounted. Behaves similarly to SubPath but\n                                        environment variable references $(VAR_NAME)\n                                        are expanded using the container's environment.\n                                        Defaults to \"\" (volume's root). SubPathExpr\n                                        and SubPath are mutually exclusive.\n                                      type: string\n                                  required:\n                                  - mountPath\n                                  - name\n                                  type: object\n                                type: array\n                              workingDir:\n                                description: Container's working directory. If not\n                                  specified, the container runtime's default will\n                                  be used, which might be configured in the container\n                                  image. Cannot be updated.\n                                type: string\n                            required:\n                            - name\n                            type: object\n                          type: array\n                        hostAliases:\n                          description: HostAliases is an optional list of hosts and\n                            IPs that will be injected into the pod's hosts file if\n                            specified. This is only valid for non-hostNetwork pods.\n                          items:\n                            description: HostAlias holds the mapping between IP and\n                              hostnames that will be injected as an entry in the pod's\n                              hosts file.\n                            properties:\n                              hostnames:\n                                description: Hostnames for the above IP address.\n                                items:\n                                  type: string\n                                type: array\n                              ip:\n                                description: IP address of the host file entry.\n                                type: string\n                            type: object\n                          type: array\n                        hostIPC:\n                          description: 'Use the host''s ipc namespace. Optional: Default\n                            to false.'\n                          type: boolean\n                        hostNetwork:\n                          description: Host networking requested for this pod. Use\n                            the host's network namespace. If this option is set, the\n                            ports that will be used must be specified. Default to\n                            false.\n                          type: boolean\n                        hostPID:\n                          description: 'Use the host''s pid namespace. Optional: Default\n                            to false.'\n                          type: boolean\n                        hostname:\n                          description: Specifies the hostname of the Pod If not specified,\n                            the pod's hostname will be set to a system-defined value.\n                          type: string\n                        imagePullSecrets:\n                          description: 'ImagePullSecrets is an optional list of references\n                            to secrets in the same namespace to use for pulling any\n                            of the images used by this PodSpec. If specified, these\n                            secrets will be passed to individual puller implementations\n                            for them to use. For example, in the case of docker, only\n                            DockerConfig type secrets are honored. More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod'\n                          items:\n                            description: LocalObjectReference contains enough information\n                              to let you locate the referenced object inside the same\n                              namespace.\n                            properties:\n                              name:\n                                description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                  TODO: Add other useful fields. apiVersion, kind,\n                                  uid?'\n                                type: string\n                            type: object\n                          type: array\n                        initContainers:\n                          description: 'List of initialization containers belonging\n                            to the pod. Init containers are executed in order prior\n                            to containers being started. If any init container fails,\n                            the pod is considered to have failed and is handled according\n                            to its restartPolicy. The name for an init container or\n                            normal container must be unique among all containers.\n                            Init containers may not have Lifecycle actions, Readiness\n                            probes, Liveness probes, or Startup probes. The resourceRequirements\n                            of an init container are taken into account during scheduling\n                            by finding the highest request/limit for each resource\n                            type, and then using the max of of that value or the sum\n                            of the normal containers. Limits are applied to init containers\n                            in a similar fashion. Init containers cannot currently\n                            be added or removed. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/'\n                          items:\n                            description: A single application container that you want\n                              to run within a pod.\n                            properties:\n                              args:\n                                description: 'Arguments to the entrypoint. The docker\n                                  image''s CMD is used if this is not provided. Variable\n                                  references $(VAR_NAME) are expanded using the container''s\n                                  environment. If a variable cannot be resolved, the\n                                  reference in the input string will be unchanged.\n                                  The $(VAR_NAME) syntax can be escaped with a double\n                                  $$, ie: $$(VAR_NAME). Escaped references will never\n                                  be expanded, regardless of whether the variable\n                                  exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell'\n                                items:\n                                  type: string\n                                type: array\n                              command:\n                                description: 'Entrypoint array. Not executed within\n                                  a shell. The docker image''s ENTRYPOINT is used\n                                  if this is not provided. Variable references $(VAR_NAME)\n                                  are expanded using the container''s environment.\n                                  If a variable cannot be resolved, the reference\n                                  in the input string will be unchanged. The $(VAR_NAME)\n                                  syntax can be escaped with a double $$, ie: $$(VAR_NAME).\n                                  Escaped references will never be expanded, regardless\n                                  of whether the variable exists or not. Cannot be\n                                  updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell'\n                                items:\n                                  type: string\n                                type: array\n                              env:\n                                description: List of environment variables to set\n                                  in the container. Cannot be updated.\n                                items:\n                                  description: EnvVar represents an environment variable\n                                    present in a Container.\n                                  properties:\n                                    name:\n                                      description: Name of the environment variable.\n                                        Must be a C_IDENTIFIER.\n                                      type: string\n                                    value:\n                                      description: 'Variable references $(VAR_NAME)\n                                        are expanded using the previous defined environment\n                                        variables in the container and any service\n                                        environment variables. If a variable cannot\n                                        be resolved, the reference in the input string\n                                        will be unchanged. The $(VAR_NAME) syntax\n                                        can be escaped with a double $$, ie: $$(VAR_NAME).\n                                        Escaped references will never be expanded,\n                                        regardless of whether the variable exists\n                                        or not. Defaults to \"\".'\n                                      type: string\n                                    valueFrom:\n                                      description: Source for the environment variable's\n                                        value. Cannot be used if value is not empty.\n                                      properties:\n                                        configMapKeyRef:\n                                          description: Selects a key of a ConfigMap.\n                                          properties:\n                                            key:\n                                              description: The key to select.\n                                              type: string\n                                            name:\n                                              description: 'Name of the referent.\n                                                More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                                TODO: Add other useful fields. apiVersion,\n                                                kind, uid?'\n                                              type: string\n                                            optional:\n                                              description: Specify whether the ConfigMap\n                                                or its key must be defined\n                                              type: boolean\n                                          required:\n                                          - key\n                                          type: object\n                                        fieldRef:\n                                          description: 'Selects a field of the pod:\n                                            supports metadata.name, metadata.namespace,\n                                            metadata.labels, metadata.annotations,\n                                            spec.nodeName, spec.serviceAccountName,\n                                            status.hostIP, status.podIP, status.podIPs.'\n                                          properties:\n                                            apiVersion:\n                                              description: Version of the schema the\n                                                FieldPath is written in terms of,\n                                                defaults to \"v1\".\n                                              type: string\n                                            fieldPath:\n                                              description: Path of the field to select\n                                                in the specified API version.\n                                              type: string\n                                          required:\n                                          - fieldPath\n                                          type: object\n                                        resourceFieldRef:\n                                          description: 'Selects a resource of the\n                                            container: only resources limits and requests\n                                            (limits.cpu, limits.memory, limits.ephemeral-storage,\n                                            requests.cpu, requests.memory and requests.ephemeral-storage)\n                                            are currently supported.'\n                                          properties:\n                                            containerName:\n                                              description: 'Container name: required\n                                                for volumes, optional for env vars'\n                                              type: string\n                                            divisor:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              description: Specifies the output format\n                                                of the exposed resources, defaults\n                                                to \"1\"\n                                              pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                              x-kubernetes-int-or-string: true\n                                            resource:\n                                              description: 'Required: resource to\n                                                select'\n                                              type: string\n                                          required:\n                                          - resource\n                                          type: object\n                                        secretKeyRef:\n                                          description: Selects a key of a secret in\n                                            the pod's namespace\n                                          properties:\n                                            key:\n                                              description: The key of the secret to\n                                                select from.  Must be a valid secret\n                                                key.\n                                              type: string\n                                            name:\n                                              description: 'Name of the referent.\n                                                More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                                TODO: Add other useful fields. apiVersion,\n                                                kind, uid?'\n                                              type: string\n                                            optional:\n                                              description: Specify whether the Secret\n                                                or its key must be defined\n                                              type: boolean\n                                          required:\n                                          - key\n                                          type: object\n                                      type: object\n                                  required:\n                                  - name\n                                  type: object\n                                type: array\n                              envFrom:\n                                description: List of sources to populate environment\n                                  variables in the container. The keys defined within\n                                  a source must be a C_IDENTIFIER. All invalid keys\n                                  will be reported as an event when the container\n                                  is starting. When a key exists in multiple sources,\n                                  the value associated with the last source will take\n                                  precedence. Values defined by an Env with a duplicate\n                                  key will take precedence. Cannot be updated.\n                                items:\n                                  description: EnvFromSource represents the source\n                                    of a set of ConfigMaps\n                                  properties:\n                                    configMapRef:\n                                      description: The ConfigMap to select from\n                                      properties:\n                                        name:\n                                          description: 'Name of the referent. More\n                                            info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                            TODO: Add other useful fields. apiVersion,\n                                            kind, uid?'\n                                          type: string\n                                        optional:\n                                          description: Specify whether the ConfigMap\n                                            must be defined\n                                          type: boolean\n                                      type: object\n                                    prefix:\n                                      description: An optional identifier to prepend\n                                        to each key in the ConfigMap. Must be a C_IDENTIFIER.\n                                      type: string\n                                    secretRef:\n                                      description: The Secret to select from\n                                      properties:\n                                        name:\n                                          description: 'Name of the referent. More\n                                            info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                            TODO: Add other useful fields. apiVersion,\n                                            kind, uid?'\n                                          type: string\n                                        optional:\n                                          description: Specify whether the Secret\n                                            must be defined\n                                          type: boolean\n                                      type: object\n                                  type: object\n                                type: array\n                              image:\n                                description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images\n                                  This field is optional to allow higher level config\n                                  management to default or override container images\n                                  in workload controllers like Deployments and StatefulSets.'\n                                type: string\n                              imagePullPolicy:\n                                description: 'Image pull policy. One of Always, Never,\n                                  IfNotPresent. Defaults to Always if :latest tag\n                                  is specified, or IfNotPresent otherwise. Cannot\n                                  be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images'\n                                type: string\n                              lifecycle:\n                                description: Actions that the management system should\n                                  take in response to container lifecycle events.\n                                  Cannot be updated.\n                                properties:\n                                  postStart:\n                                    description: 'PostStart is called immediately\n                                      after a container is created. If the handler\n                                      fails, the container is terminated and restarted\n                                      according to its restart policy. Other management\n                                      of the container blocks until the hook completes.\n                                      More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks'\n                                    properties:\n                                      exec:\n                                        description: One and only one of the following\n                                          should be specified. Exec specifies the\n                                          action to take.\n                                        properties:\n                                          command:\n                                            description: Command is the command line\n                                              to execute inside the container, the\n                                              working directory for the command  is\n                                              root ('/') in the container's filesystem.\n                                              The command is simply exec'd, it is\n                                              not run inside a shell, so traditional\n                                              shell instructions ('|', etc) won't\n                                              work. To use a shell, you need to explicitly\n                                              call out to that shell. Exit status\n                                              of 0 is treated as live/healthy and\n                                              non-zero is unhealthy.\n                                            items:\n                                              type: string\n                                            type: array\n                                        type: object\n                                      httpGet:\n                                        description: HTTPGet specifies the http request\n                                          to perform.\n                                        properties:\n                                          host:\n                                            description: Host name to connect to,\n                                              defaults to the pod IP. You probably\n                                              want to set \"Host\" in httpHeaders instead.\n                                            type: string\n                                          httpHeaders:\n                                            description: Custom headers to set in\n                                              the request. HTTP allows repeated headers.\n                                            items:\n                                              description: HTTPHeader describes a\n                                                custom header to be used in HTTP probes\n                                              properties:\n                                                name:\n                                                  description: The header field name\n                                                  type: string\n                                                value:\n                                                  description: The header field value\n                                                  type: string\n                                              required:\n                                              - name\n                                              - value\n                                              type: object\n                                            type: array\n                                          path:\n                                            description: Path to access on the HTTP\n                                              server.\n                                            type: string\n                                          port:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            description: Name or number of the port\n                                              to access on the container. Number must\n                                              be in the range 1 to 65535. Name must\n                                              be an IANA_SVC_NAME.\n                                            x-kubernetes-int-or-string: true\n                                          scheme:\n                                            description: Scheme to use for connecting\n                                              to the host. Defaults to HTTP.\n                                            type: string\n                                        required:\n                                        - port\n                                        type: object\n                                      tcpSocket:\n                                        description: 'TCPSocket specifies an action\n                                          involving a TCP port. TCP hooks not yet\n                                          supported TODO: implement a realistic TCP\n                                          lifecycle hook'\n                                        properties:\n                                          host:\n                                            description: 'Optional: Host name to connect\n                                              to, defaults to the pod IP.'\n                                            type: string\n                                          port:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            description: Number or name of the port\n                                              to access on the container. Number must\n                                              be in the range 1 to 65535. Name must\n                                              be an IANA_SVC_NAME.\n                                            x-kubernetes-int-or-string: true\n                                        required:\n                                        - port\n                                        type: object\n                                    type: object\n                                  preStop:\n                                    description: 'PreStop is called immediately before\n                                      a container is terminated due to an API request\n                                      or management event such as liveness/startup\n                                      probe failure, preemption, resource contention,\n                                      etc. The handler is not called if the container\n                                      crashes or exits. The reason for termination\n                                      is passed to the handler. The Pod''s termination\n                                      grace period countdown begins before the PreStop\n                                      hooked is executed. Regardless of the outcome\n                                      of the handler, the container will eventually\n                                      terminate within the Pod''s termination grace\n                                      period. Other management of the container blocks\n                                      until the hook completes or until the termination\n                                      grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks'\n                                    properties:\n                                      exec:\n                                        description: One and only one of the following\n                                          should be specified. Exec specifies the\n                                          action to take.\n                                        properties:\n                                          command:\n                                            description: Command is the command line\n                                              to execute inside the container, the\n                                              working directory for the command  is\n                                              root ('/') in the container's filesystem.\n                                              The command is simply exec'd, it is\n                                              not run inside a shell, so traditional\n                                              shell instructions ('|', etc) won't\n                                              work. To use a shell, you need to explicitly\n                                              call out to that shell. Exit status\n                                              of 0 is treated as live/healthy and\n                                              non-zero is unhealthy.\n                                            items:\n                                              type: string\n                                            type: array\n                                        type: object\n                                      httpGet:\n                                        description: HTTPGet specifies the http request\n                                          to perform.\n                                        properties:\n                                          host:\n                                            description: Host name to connect to,\n                                              defaults to the pod IP. You probably\n                                              want to set \"Host\" in httpHeaders instead.\n                                            type: string\n                                          httpHeaders:\n                                            description: Custom headers to set in\n                                              the request. HTTP allows repeated headers.\n                                            items:\n                                              description: HTTPHeader describes a\n                                                custom header to be used in HTTP probes\n                                              properties:\n                                                name:\n                                                  description: The header field name\n                                                  type: string\n                                                value:\n                                                  description: The header field value\n                                                  type: string\n                                              required:\n                                              - name\n                                              - value\n                                              type: object\n                                            type: array\n                                          path:\n                                            description: Path to access on the HTTP\n                                              server.\n                                            type: string\n                                          port:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            description: Name or number of the port\n                                              to access on the container. Number must\n                                              be in the range 1 to 65535. Name must\n                                              be an IANA_SVC_NAME.\n                                            x-kubernetes-int-or-string: true\n                                          scheme:\n                                            description: Scheme to use for connecting\n                                              to the host. Defaults to HTTP.\n                                            type: string\n                                        required:\n                                        - port\n                                        type: object\n                                      tcpSocket:\n                                        description: 'TCPSocket specifies an action\n                                          involving a TCP port. TCP hooks not yet\n                                          supported TODO: implement a realistic TCP\n                                          lifecycle hook'\n                                        properties:\n                                          host:\n                                            description: 'Optional: Host name to connect\n                                              to, defaults to the pod IP.'\n                                            type: string\n                                          port:\n                                            anyOf:\n                                            - type: integer\n                                            - type: string\n                                            description: Number or name of the port\n                                              to access on the container. Number must\n                                              be in the range 1 to 65535. Name must\n                                              be an IANA_SVC_NAME.\n                                            x-kubernetes-int-or-string: true\n                                        required:\n                                        - port\n                                        type: object\n                                    type: object\n                                type: object\n                              livenessProbe:\n                                description: 'Periodic probe of container liveness.\n                                  Container will be restarted if the probe fails.\n                                  Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                                properties:\n                                  exec:\n                                    description: One and only one of the following\n                                      should be specified. Exec specifies the action\n                                      to take.\n                                    properties:\n                                      command:\n                                        description: Command is the command line to\n                                          execute inside the container, the working\n                                          directory for the command  is root ('/')\n                                          in the container's filesystem. The command\n                                          is simply exec'd, it is not run inside a\n                                          shell, so traditional shell instructions\n                                          ('|', etc) won't work. To use a shell, you\n                                          need to explicitly call out to that shell.\n                                          Exit status of 0 is treated as live/healthy\n                                          and non-zero is unhealthy.\n                                        items:\n                                          type: string\n                                        type: array\n                                    type: object\n                                  failureThreshold:\n                                    description: Minimum consecutive failures for\n                                      the probe to be considered failed after having\n                                      succeeded. Defaults to 3. Minimum value is 1.\n                                    format: int32\n                                    type: integer\n                                  httpGet:\n                                    description: HTTPGet specifies the http request\n                                      to perform.\n                                    properties:\n                                      host:\n                                        description: Host name to connect to, defaults\n                                          to the pod IP. You probably want to set\n                                          \"Host\" in httpHeaders instead.\n                                        type: string\n                                      httpHeaders:\n                                        description: Custom headers to set in the\n                                          request. HTTP allows repeated headers.\n                                        items:\n                                          description: HTTPHeader describes a custom\n                                            header to be used in HTTP probes\n                                          properties:\n                                            name:\n                                              description: The header field name\n                                              type: string\n                                            value:\n                                              description: The header field value\n                                              type: string\n                                          required:\n                                          - name\n                                          - value\n                                          type: object\n                                        type: array\n                                      path:\n                                        description: Path to access on the HTTP server.\n                                        type: string\n                                      port:\n                                        anyOf:\n                                        - type: integer\n                                        - type: string\n                                        description: Name or number of the port to\n                                          access on the container. Number must be\n                                          in the range 1 to 65535. Name must be an\n                                          IANA_SVC_NAME.\n                                        x-kubernetes-int-or-string: true\n                                      scheme:\n                                        description: Scheme to use for connecting\n                                          to the host. Defaults to HTTP.\n                                        type: string\n                                    required:\n                                    - port\n                                    type: object\n                                  initialDelaySeconds:\n                                    description: 'Number of seconds after the container\n                                      has started before liveness probes are initiated.\n                                      More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                                    format: int32\n                                    type: integer\n                                  periodSeconds:\n                                    description: How often (in seconds) to perform\n                                      the probe. Default to 10 seconds. Minimum value\n                                      is 1.\n                                    format: int32\n                                    type: integer\n                                  successThreshold:\n                                    description: Minimum consecutive successes for\n                                      the probe to be considered successful after\n                                      having failed. Defaults to 1. Must be 1 for\n                                      liveness and startup. Minimum value is 1.\n                                    format: int32\n                                    type: integer\n                                  tcpSocket:\n                                    description: 'TCPSocket specifies an action involving\n                                      a TCP port. TCP hooks not yet supported TODO:\n                                      implement a realistic TCP lifecycle hook'\n                                    properties:\n                                      host:\n                                        description: 'Optional: Host name to connect\n                                          to, defaults to the pod IP.'\n                                        type: string\n                                      port:\n                                        anyOf:\n                                        - type: integer\n                                        - type: string\n                                        description: Number or name of the port to\n                                          access on the container. Number must be\n                                          in the range 1 to 65535. Name must be an\n                                          IANA_SVC_NAME.\n                                        x-kubernetes-int-or-string: true\n                                    required:\n                                    - port\n                                    type: object\n                                  timeoutSeconds:\n                                    description: 'Number of seconds after which the\n                                      probe times out. Defaults to 1 second. Minimum\n                                      value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                                    format: int32\n                                    type: integer\n                                type: object\n                              name:\n                                description: Name of the container specified as a\n                                  DNS_LABEL. Each container in a pod must have a unique\n                                  name (DNS_LABEL). Cannot be updated.\n                                type: string\n                              ports:\n                                description: List of ports to expose from the container.\n                                  Exposing a port here gives the system additional\n                                  information about the network connections a container\n                                  uses, but is primarily informational. Not specifying\n                                  a port here DOES NOT prevent that port from being\n                                  exposed. Any port which is listening on the default\n                                  \"0.0.0.0\" address inside a container will be accessible\n                                  from the network. Cannot be updated.\n                                items:\n                                  description: ContainerPort represents a network\n                                    port in a single container.\n                                  properties:\n                                    containerPort:\n                                      description: Number of port to expose on the\n                                        pod's IP address. This must be a valid port\n                                        number, 0 < x < 65536.\n                                      format: int32\n                                      type: integer\n                                    hostIP:\n                                      description: What host IP to bind the external\n                                        port to.\n                                      type: string\n                                    hostPort:\n                                      description: Number of port to expose on the\n                                        host. If specified, this must be a valid port\n                                        number, 0 < x < 65536. If HostNetwork is specified,\n                                        this must match ContainerPort. Most containers\n                                        do not need this.\n                                      format: int32\n                                      type: integer\n                                    name:\n                                      description: If specified, this must be an IANA_SVC_NAME\n                                        and unique within the pod. Each named port\n                                        in a pod must have a unique name. Name for\n                                        the port that can be referred to by services.\n                                      type: string\n                                    protocol:\n                                      description: Protocol for port. Must be UDP,\n                                        TCP, or SCTP. Defaults to \"TCP\".\n                                      type: string\n                                  required:\n                                  - containerPort\n                                  - protocol\n                                  type: object\n                                type: array\n                                x-kubernetes-list-map-keys:\n                                - containerPort\n                                - protocol\n                                x-kubernetes-list-type: map\n                              readinessProbe:\n                                description: 'Periodic probe of container service\n                                  readiness. Container will be removed from service\n                                  endpoints if the probe fails. Cannot be updated.\n                                  More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                                properties:\n                                  exec:\n                                    description: One and only one of the following\n                                      should be specified. Exec specifies the action\n                                      to take.\n                                    properties:\n                                      command:\n                                        description: Command is the command line to\n                                          execute inside the container, the working\n                                          directory for the command  is root ('/')\n                                          in the container's filesystem. The command\n                                          is simply exec'd, it is not run inside a\n                                          shell, so traditional shell instructions\n                                          ('|', etc) won't work. To use a shell, you\n                                          need to explicitly call out to that shell.\n                                          Exit status of 0 is treated as live/healthy\n                                          and non-zero is unhealthy.\n                                        items:\n                                          type: string\n                                        type: array\n                                    type: object\n                                  failureThreshold:\n                                    description: Minimum consecutive failures for\n                                      the probe to be considered failed after having\n                                      succeeded. Defaults to 3. Minimum value is 1.\n                                    format: int32\n                                    type: integer\n                                  httpGet:\n                                    description: HTTPGet specifies the http request\n                                      to perform.\n                                    properties:\n                                      host:\n                                        description: Host name to connect to, defaults\n                                          to the pod IP. You probably want to set\n                                          \"Host\" in httpHeaders instead.\n                                        type: string\n                                      httpHeaders:\n                                        description: Custom headers to set in the\n                                          request. HTTP allows repeated headers.\n                                        items:\n                                          description: HTTPHeader describes a custom\n                                            header to be used in HTTP probes\n                                          properties:\n                                            name:\n                                              description: The header field name\n                                              type: string\n                                            value:\n                                              description: The header field value\n                                              type: string\n                                          required:\n                                          - name\n                                          - value\n                                          type: object\n                                        type: array\n                                      path:\n                                        description: Path to access on the HTTP server.\n                                        type: string\n                                      port:\n                                        anyOf:\n                                        - type: integer\n                                        - type: string\n                                        description: Name or number of the port to\n                                          access on the container. Number must be\n                                          in the range 1 to 65535. Name must be an\n                                          IANA_SVC_NAME.\n                                        x-kubernetes-int-or-string: true\n                                      scheme:\n                                        description: Scheme to use for connecting\n                                          to the host. Defaults to HTTP.\n                                        type: string\n                                    required:\n                                    - port\n                                    type: object\n                                  initialDelaySeconds:\n                                    description: 'Number of seconds after the container\n                                      has started before liveness probes are initiated.\n                                      More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                                    format: int32\n                                    type: integer\n                                  periodSeconds:\n                                    description: How often (in seconds) to perform\n                                      the probe. Default to 10 seconds. Minimum value\n                                      is 1.\n                                    format: int32\n                                    type: integer\n                                  successThreshold:\n                                    description: Minimum consecutive successes for\n                                      the probe to be considered successful after\n                                      having failed. Defaults to 1. Must be 1 for\n                                      liveness and startup. Minimum value is 1.\n                                    format: int32\n                                    type: integer\n                                  tcpSocket:\n                                    description: 'TCPSocket specifies an action involving\n                                      a TCP port. TCP hooks not yet supported TODO:\n                                      implement a realistic TCP lifecycle hook'\n                                    properties:\n                                      host:\n                                        description: 'Optional: Host name to connect\n                                          to, defaults to the pod IP.'\n                                        type: string\n                                      port:\n                                        anyOf:\n                                        - type: integer\n                                        - type: string\n                                        description: Number or name of the port to\n                                          access on the container. Number must be\n                                          in the range 1 to 65535. Name must be an\n                                          IANA_SVC_NAME.\n                                        x-kubernetes-int-or-string: true\n                                    required:\n                                    - port\n                                    type: object\n                                  timeoutSeconds:\n                                    description: 'Number of seconds after which the\n                                      probe times out. Defaults to 1 second. Minimum\n                                      value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                                    format: int32\n                                    type: integer\n                                type: object\n                              resources:\n                                description: 'Compute Resources required by this container.\n                                  Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/'\n                                properties:\n                                  limits:\n                                    additionalProperties:\n                                      anyOf:\n                                      - type: integer\n                                      - type: string\n                                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                      x-kubernetes-int-or-string: true\n                                    description: 'Limits describes the maximum amount\n                                      of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/'\n                                    type: object\n                                  requests:\n                                    additionalProperties:\n                                      anyOf:\n                                      - type: integer\n                                      - type: string\n                                      pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                      x-kubernetes-int-or-string: true\n                                    description: 'Requests describes the minimum amount\n                                      of compute resources required. If Requests is\n                                      omitted for a container, it defaults to Limits\n                                      if that is explicitly specified, otherwise to\n                                      an implementation-defined value. More info:\n                                      https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/'\n                                    type: object\n                                type: object\n                              securityContext:\n                                description: 'Security options the pod should run\n                                  with. More info: https://kubernetes.io/docs/concepts/policy/security-context/\n                                  More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/'\n                                properties:\n                                  allowPrivilegeEscalation:\n                                    description: 'AllowPrivilegeEscalation controls\n                                      whether a process can gain more privileges than\n                                      its parent process. This bool directly controls\n                                      if the no_new_privs flag will be set on the\n                                      container process. AllowPrivilegeEscalation\n                                      is true always when the container is: 1) run\n                                      as Privileged 2) has CAP_SYS_ADMIN'\n                                    type: boolean\n                                  capabilities:\n                                    description: The capabilities to add/drop when\n                                      running containers. Defaults to the default\n                                      set of capabilities granted by the container\n                                      runtime.\n                                    properties:\n                                      add:\n                                        description: Added capabilities\n                                        items:\n                                          description: Capability represent POSIX\n                                            capabilities type\n                                          type: string\n                                        type: array\n                                      drop:\n                                        description: Removed capabilities\n                                        items:\n                                          description: Capability represent POSIX\n                                            capabilities type\n                                          type: string\n                                        type: array\n                                    type: object\n                                  privileged:\n                                    description: Run container in privileged mode.\n                                      Processes in privileged containers are essentially\n                                      equivalent to root on the host. Defaults to\n                                      false.\n                                    type: boolean\n                                  procMount:\n                                    description: procMount denotes the type of proc\n                                      mount to use for the containers. The default\n                                      is DefaultProcMount which uses the container\n                                      runtime defaults for readonly paths and masked\n                                      paths. This requires the ProcMountType feature\n                                      flag to be enabled.\n                                    type: string\n                                  readOnlyRootFilesystem:\n                                    description: Whether this container has a read-only\n                                      root filesystem. Default is false.\n                                    type: boolean\n                                  runAsGroup:\n                                    description: The GID to run the entrypoint of\n                                      the container process. Uses runtime default\n                                      if unset. May also be set in PodSecurityContext.  If\n                                      set in both SecurityContext and PodSecurityContext,\n                                      the value specified in SecurityContext takes\n                                      precedence.\n                                    format: int64\n                                    type: integer\n                                  runAsNonRoot:\n                                    description: Indicates that the container must\n                                      run as a non-root user. If true, the Kubelet\n                                      will validate the image at runtime to ensure\n                                      that it does not run as UID 0 (root) and fail\n                                      to start the container if it does. If unset\n                                      or false, no such validation will be performed.\n                                      May also be set in PodSecurityContext.  If set\n                                      in both SecurityContext and PodSecurityContext,\n                                      the value specified in SecurityContext takes\n                                      precedence.\n                                    type: boolean\n                                  runAsUser:\n                                    description: The UID to run the entrypoint of\n                                      the container process. Defaults to user specified\n                                      in image metadata if unspecified. May also be\n                                      set in PodSecurityContext.  If set in both SecurityContext\n                                      and PodSecurityContext, the value specified\n                                      in SecurityContext takes precedence.\n                                    format: int64\n                                    type: integer\n                                  seLinuxOptions:\n                                    description: The SELinux context to be applied\n                                      to the container. If unspecified, the container\n                                      runtime will allocate a random SELinux context\n                                      for each container.  May also be set in PodSecurityContext.  If\n                                      set in both SecurityContext and PodSecurityContext,\n                                      the value specified in SecurityContext takes\n                                      precedence.\n                                    properties:\n                                      level:\n                                        description: Level is SELinux level label\n                                          that applies to the container.\n                                        type: string\n                                      role:\n                                        description: Role is a SELinux role label\n                                          that applies to the container.\n                                        type: string\n                                      type:\n                                        description: Type is a SELinux type label\n                                          that applies to the container.\n                                        type: string\n                                      user:\n                                        description: User is a SELinux user label\n                                          that applies to the container.\n                                        type: string\n                                    type: object\n                                  windowsOptions:\n                                    description: The Windows specific settings applied\n                                      to all containers. If unspecified, the options\n                                      from the PodSecurityContext will be used. If\n                                      set in both SecurityContext and PodSecurityContext,\n                                      the value specified in SecurityContext takes\n                                      precedence.\n                                    properties:\n                                      gmsaCredentialSpec:\n                                        description: GMSACredentialSpec is where the\n                                          GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa)\n                                          inlines the contents of the GMSA credential\n                                          spec named by the GMSACredentialSpecName\n                                          field.\n                                        type: string\n                                      gmsaCredentialSpecName:\n                                        description: GMSACredentialSpecName is the\n                                          name of the GMSA credential spec to use.\n                                        type: string\n                                      runAsUserName:\n                                        description: The UserName in Windows to run\n                                          the entrypoint of the container process.\n                                          Defaults to the user specified in image\n                                          metadata if unspecified. May also be set\n                                          in PodSecurityContext. If set in both SecurityContext\n                                          and PodSecurityContext, the value specified\n                                          in SecurityContext takes precedence.\n                                        type: string\n                                    type: object\n                                type: object\n                              startupProbe:\n                                description: 'StartupProbe indicates that the Pod\n                                  has successfully initialized. If specified, no other\n                                  probes are executed until this completes successfully.\n                                  If this probe fails, the Pod will be restarted,\n                                  just as if the livenessProbe failed. This can be\n                                  used to provide different probe parameters at the\n                                  beginning of a Pod''s lifecycle, when it might take\n                                  a long time to load data or warm a cache, than during\n                                  steady-state operation. This cannot be updated.\n                                  This is a beta feature enabled by the StartupProbe\n                                  feature flag. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                                properties:\n                                  exec:\n                                    description: One and only one of the following\n                                      should be specified. Exec specifies the action\n                                      to take.\n                                    properties:\n                                      command:\n                                        description: Command is the command line to\n                                          execute inside the container, the working\n                                          directory for the command  is root ('/')\n                                          in the container's filesystem. The command\n                                          is simply exec'd, it is not run inside a\n                                          shell, so traditional shell instructions\n                                          ('|', etc) won't work. To use a shell, you\n                                          need to explicitly call out to that shell.\n                                          Exit status of 0 is treated as live/healthy\n                                          and non-zero is unhealthy.\n                                        items:\n                                          type: string\n                                        type: array\n                                    type: object\n                                  failureThreshold:\n                                    description: Minimum consecutive failures for\n                                      the probe to be considered failed after having\n                                      succeeded. Defaults to 3. Minimum value is 1.\n                                    format: int32\n                                    type: integer\n                                  httpGet:\n                                    description: HTTPGet specifies the http request\n                                      to perform.\n                                    properties:\n                                      host:\n                                        description: Host name to connect to, defaults\n                                          to the pod IP. You probably want to set\n                                          \"Host\" in httpHeaders instead.\n                                        type: string\n                                      httpHeaders:\n                                        description: Custom headers to set in the\n                                          request. HTTP allows repeated headers.\n                                        items:\n                                          description: HTTPHeader describes a custom\n                                            header to be used in HTTP probes\n                                          properties:\n                                            name:\n                                              description: The header field name\n                                              type: string\n                                            value:\n                                              description: The header field value\n                                              type: string\n                                          required:\n                                          - name\n                                          - value\n                                          type: object\n                                        type: array\n                                      path:\n                                        description: Path to access on the HTTP server.\n                                        type: string\n                                      port:\n                                        anyOf:\n                                        - type: integer\n                                        - type: string\n                                        description: Name or number of the port to\n                                          access on the container. Number must be\n                                          in the range 1 to 65535. Name must be an\n                                          IANA_SVC_NAME.\n                                        x-kubernetes-int-or-string: true\n                                      scheme:\n                                        description: Scheme to use for connecting\n                                          to the host. Defaults to HTTP.\n                                        type: string\n                                    required:\n                                    - port\n                                    type: object\n                                  initialDelaySeconds:\n                                    description: 'Number of seconds after the container\n                                      has started before liveness probes are initiated.\n                                      More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                                    format: int32\n                                    type: integer\n                                  periodSeconds:\n                                    description: How often (in seconds) to perform\n                                      the probe. Default to 10 seconds. Minimum value\n                                      is 1.\n                                    format: int32\n                                    type: integer\n                                  successThreshold:\n                                    description: Minimum consecutive successes for\n                                      the probe to be considered successful after\n                                      having failed. Defaults to 1. Must be 1 for\n                                      liveness and startup. Minimum value is 1.\n                                    format: int32\n                                    type: integer\n                                  tcpSocket:\n                                    description: 'TCPSocket specifies an action involving\n                                      a TCP port. TCP hooks not yet supported TODO:\n                                      implement a realistic TCP lifecycle hook'\n                                    properties:\n                                      host:\n                                        description: 'Optional: Host name to connect\n                                          to, defaults to the pod IP.'\n                                        type: string\n                                      port:\n                                        anyOf:\n                                        - type: integer\n                                        - type: string\n                                        description: Number or name of the port to\n                                          access on the container. Number must be\n                                          in the range 1 to 65535. Name must be an\n                                          IANA_SVC_NAME.\n                                        x-kubernetes-int-or-string: true\n                                    required:\n                                    - port\n                                    type: object\n                                  timeoutSeconds:\n                                    description: 'Number of seconds after which the\n                                      probe times out. Defaults to 1 second. Minimum\n                                      value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes'\n                                    format: int32\n                                    type: integer\n                                type: object\n                              stdin:\n                                description: Whether this container should allocate\n                                  a buffer for stdin in the container runtime. If\n                                  this is not set, reads from stdin in the container\n                                  will always result in EOF. Default is false.\n                                type: boolean\n                              stdinOnce:\n                                description: Whether the container runtime should\n                                  close the stdin channel after it has been opened\n                                  by a single attach. When stdin is true the stdin\n                                  stream will remain open across multiple attach sessions.\n                                  If stdinOnce is set to true, stdin is opened on\n                                  container start, is empty until the first client\n                                  attaches to stdin, and then remains open and accepts\n                                  data until the client disconnects, at which time\n                                  stdin is closed and remains closed until the container\n                                  is restarted. If this flag is false, a container\n                                  processes that reads from stdin will never receive\n                                  an EOF. Default is false\n                                type: boolean\n                              terminationMessagePath:\n                                description: 'Optional: Path at which the file to\n                                  which the container''s termination message will\n                                  be written is mounted into the container''s filesystem.\n                                  Message written is intended to be brief final status,\n                                  such as an assertion failure message. Will be truncated\n                                  by the node if greater than 4096 bytes. The total\n                                  message length across all containers will be limited\n                                  to 12kb. Defaults to /dev/termination-log. Cannot\n                                  be updated.'\n                                type: string\n                              terminationMessagePolicy:\n                                description: Indicate how the termination message\n                                  should be populated. File will use the contents\n                                  of terminationMessagePath to populate the container\n                                  status message on both success and failure. FallbackToLogsOnError\n                                  will use the last chunk of container log output\n                                  if the termination message file is empty and the\n                                  container exited with an error. The log output is\n                                  limited to 2048 bytes or 80 lines, whichever is\n                                  smaller. Defaults to File. Cannot be updated.\n                                type: string\n                              tty:\n                                description: Whether this container should allocate\n                                  a TTY for itself, also requires 'stdin' to be true.\n                                  Default is false.\n                                type: boolean\n                              volumeDevices:\n                                description: volumeDevices is the list of block devices\n                                  to be used by the container.\n                                items:\n                                  description: volumeDevice describes a mapping of\n                                    a raw block device within a container.\n                                  properties:\n                                    devicePath:\n                                      description: devicePath is the path inside of\n                                        the container that the device will be mapped\n                                        to.\n                                      type: string\n                                    name:\n                                      description: name must match the name of a persistentVolumeClaim\n                                        in the pod\n                                      type: string\n                                  required:\n                                  - devicePath\n                                  - name\n                                  type: object\n                                type: array\n                              volumeMounts:\n                                description: Pod volumes to mount into the container's\n                                  filesystem. Cannot be updated.\n                                items:\n                                  description: VolumeMount describes a mounting of\n                                    a Volume within a container.\n                                  properties:\n                                    mountPath:\n                                      description: Path within the container at which\n                                        the volume should be mounted.  Must not contain\n                                        ':'.\n                                      type: string\n                                    mountPropagation:\n                                      description: mountPropagation determines how\n                                        mounts are propagated from the host to container\n                                        and the other way around. When not set, MountPropagationNone\n                                        is used. This field is beta in 1.10.\n                                      type: string\n                                    name:\n                                      description: This must match the Name of a Volume.\n                                      type: string\n                                    readOnly:\n                                      description: Mounted read-only if true, read-write\n                                        otherwise (false or unspecified). Defaults\n                                        to false.\n                                      type: boolean\n                                    subPath:\n                                      description: Path within the volume from which\n                                        the container's volume should be mounted.\n                                        Defaults to \"\" (volume's root).\n                                      type: string\n                                    subPathExpr:\n                                      description: Expanded path within the volume\n                                        from which the container's volume should be\n                                        mounted. Behaves similarly to SubPath but\n                                        environment variable references $(VAR_NAME)\n                                        are expanded using the container's environment.\n                                        Defaults to \"\" (volume's root). SubPathExpr\n                                        and SubPath are mutually exclusive.\n                                      type: string\n                                  required:\n                                  - mountPath\n                                  - name\n                                  type: object\n                                type: array\n                              workingDir:\n                                description: Container's working directory. If not\n                                  specified, the container runtime's default will\n                                  be used, which might be configured in the container\n                                  image. Cannot be updated.\n                                type: string\n                            required:\n                            - name\n                            type: object\n                          type: array\n                        nodeName:\n                          description: NodeName is a request to schedule this pod\n                            onto a specific node. If it is non-empty, the scheduler\n                            simply schedules this pod onto that node, assuming that\n                            it fits resource requirements.\n                          type: string\n                        nodeSelector:\n                          additionalProperties:\n                            type: string\n                          description: 'NodeSelector is a selector which must be true\n                            for the pod to fit on a node. Selector which must match\n                            a node''s labels for the pod to be scheduled on that node.\n                            More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/'\n                          type: object\n                        overhead:\n                          additionalProperties:\n                            anyOf:\n                            - type: integer\n                            - type: string\n                            pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                            x-kubernetes-int-or-string: true\n                          description: 'Overhead represents the resource overhead\n                            associated with running a pod for a given RuntimeClass.\n                            This field will be autopopulated at admission time by\n                            the RuntimeClass admission controller. If the RuntimeClass\n                            admission controller is enabled, overhead must not be\n                            set in Pod create requests. The RuntimeClass admission\n                            controller will reject Pod create requests which have\n                            the overhead already set. If RuntimeClass is configured\n                            and selected in the PodSpec, Overhead will be set to the\n                            value defined in the corresponding RuntimeClass, otherwise\n                            it will remain unset and treated as zero. More info: https://git.k8s.io/enhancements/keps/sig-node/20190226-pod-overhead.md\n                            This field is alpha-level as of Kubernetes v1.16, and\n                            is only honored by servers that enable the PodOverhead\n                            feature.'\n                          type: object\n                        preemptionPolicy:\n                          description: PreemptionPolicy is the Policy for preempting\n                            pods with lower priority. One of Never, PreemptLowerPriority.\n                            Defaults to PreemptLowerPriority if unset. This field\n                            is alpha-level and is only honored by servers that enable\n                            the NonPreemptingPriority feature.\n                          type: string\n                        priority:\n                          description: The priority value. Various system components\n                            use this field to find the priority of the pod. When Priority\n                            Admission Controller is enabled, it prevents users from\n                            setting this field. The admission controller populates\n                            this field from PriorityClassName. The higher the value,\n                            the higher the priority.\n                          format: int32\n                          type: integer\n                        priorityClassName:\n                          description: If specified, indicates the pod's priority.\n                            \"system-node-critical\" and \"system-cluster-critical\" are\n                            two special keywords which indicate the highest priorities\n                            with the former being the highest priority. Any other\n                            name must be defined by creating a PriorityClass object\n                            with that name. If not specified, the pod priority will\n                            be default or zero if there is no default.\n                          type: string\n                        readinessGates:\n                          description: 'If specified, all readiness gates will be\n                            evaluated for pod readiness. A pod is ready when all its\n                            containers are ready AND all conditions specified in the\n                            readiness gates have status equal to \"True\" More info:\n                            https://git.k8s.io/enhancements/keps/sig-network/0007-pod-ready%2B%2B.md'\n                          items:\n                            description: PodReadinessGate contains the reference to\n                              a pod condition\n                            properties:\n                              conditionType:\n                                description: ConditionType refers to a condition in\n                                  the pod's condition list with matching type.\n                                type: string\n                            required:\n                            - conditionType\n                            type: object\n                          type: array\n                        restartPolicy:\n                          description: 'Restart policy for all containers within the\n                            pod. One of Always, OnFailure, Never. Default to Always.\n                            More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#restart-policy'\n                          type: string\n                        runtimeClassName:\n                          description: 'RuntimeClassName refers to a RuntimeClass\n                            object in the node.k8s.io group, which should be used\n                            to run this pod.  If no RuntimeClass resource matches\n                            the named class, the pod will not be run. If unset or\n                            empty, the \"legacy\" RuntimeClass will be used, which is\n                            an implicit class with an empty definition that uses the\n                            default runtime handler. More info: https://git.k8s.io/enhancements/keps/sig-node/runtime-class.md\n                            This is a beta feature as of Kubernetes v1.14.'\n                          type: string\n                        schedulerName:\n                          description: If specified, the pod will be dispatched by\n                            specified scheduler. If not specified, the pod will be\n                            dispatched by default scheduler.\n                          type: string\n                        securityContext:\n                          description: 'SecurityContext holds pod-level security attributes\n                            and common container settings. Optional: Defaults to empty.  See\n                            type description for default values of each field.'\n                          properties:\n                            fsGroup:\n                              description: \"A special supplemental group that applies\n                                to all containers in a pod. Some volume types allow\n                                the Kubelet to change the ownership of that volume\n                                to be owned by the pod: \\n 1. The owning GID will\n                                be the FSGroup 2. The setgid bit is set (new files\n                                created in the volume will be owned by FSGroup) 3.\n                                The permission bits are OR'd with rw-rw---- \\n If\n                                unset, the Kubelet will not modify the ownership and\n                                permissions of any volume.\"\n                              format: int64\n                              type: integer\n                            fsGroupChangePolicy:\n                              description: 'fsGroupChangePolicy defines behavior of\n                                changing ownership and permission of the volume before\n                                being exposed inside Pod. This field will only apply\n                                to volume types which support fsGroup based ownership(and\n                                permissions). It will have no effect on ephemeral\n                                volume types such as: secret, configmaps and emptydir.\n                                Valid values are \"OnRootMismatch\" and \"Always\". If\n                                not specified defaults to \"Always\".'\n                              type: string\n                            runAsGroup:\n                              description: The GID to run the entrypoint of the container\n                                process. Uses runtime default if unset. May also be\n                                set in SecurityContext.  If set in both SecurityContext\n                                and PodSecurityContext, the value specified in SecurityContext\n                                takes precedence for that container.\n                              format: int64\n                              type: integer\n                            runAsNonRoot:\n                              description: Indicates that the container must run as\n                                a non-root user. If true, the Kubelet will validate\n                                the image at runtime to ensure that it does not run\n                                as UID 0 (root) and fail to start the container if\n                                it does. If unset or false, no such validation will\n                                be performed. May also be set in SecurityContext.  If\n                                set in both SecurityContext and PodSecurityContext,\n                                the value specified in SecurityContext takes precedence.\n                              type: boolean\n                            runAsUser:\n                              description: The UID to run the entrypoint of the container\n                                process. Defaults to user specified in image metadata\n                                if unspecified. May also be set in SecurityContext.  If\n                                set in both SecurityContext and PodSecurityContext,\n                                the value specified in SecurityContext takes precedence\n                                for that container.\n                              format: int64\n                              type: integer\n                            seLinuxOptions:\n                              description: The SELinux context to be applied to all\n                                containers. If unspecified, the container runtime\n                                will allocate a random SELinux context for each container.  May\n                                also be set in SecurityContext.  If set in both SecurityContext\n                                and PodSecurityContext, the value specified in SecurityContext\n                                takes precedence for that container.\n                              properties:\n                                level:\n                                  description: Level is SELinux level label that applies\n                                    to the container.\n                                  type: string\n                                role:\n                                  description: Role is a SELinux role label that applies\n                                    to the container.\n                                  type: string\n                                type:\n                                  description: Type is a SELinux type label that applies\n                                    to the container.\n                                  type: string\n                                user:\n                                  description: User is a SELinux user label that applies\n                                    to the container.\n                                  type: string\n                              type: object\n                            supplementalGroups:\n                              description: A list of groups applied to the first process\n                                run in each container, in addition to the container's\n                                primary GID.  If unspecified, no groups will be added\n                                to any container.\n                              items:\n                                format: int64\n                                type: integer\n                              type: array\n                            sysctls:\n                              description: Sysctls hold a list of namespaced sysctls\n                                used for the pod. Pods with unsupported sysctls (by\n                                the container runtime) might fail to launch.\n                              items:\n                                description: Sysctl defines a kernel parameter to\n                                  be set\n                                properties:\n                                  name:\n                                    description: Name of a property to set\n                                    type: string\n                                  value:\n                                    description: Value of a property to set\n                                    type: string\n                                required:\n                                - name\n                                - value\n                                type: object\n                              type: array\n                            windowsOptions:\n                              description: The Windows specific settings applied to\n                                all containers. If unspecified, the options within\n                                a container's SecurityContext will be used. If set\n                                in both SecurityContext and PodSecurityContext, the\n                                value specified in SecurityContext takes precedence.\n                              properties:\n                                gmsaCredentialSpec:\n                                  description: GMSACredentialSpec is where the GMSA\n                                    admission webhook (https://github.com/kubernetes-sigs/windows-gmsa)\n                                    inlines the contents of the GMSA credential spec\n                                    named by the GMSACredentialSpecName field.\n                                  type: string\n                                gmsaCredentialSpecName:\n                                  description: GMSACredentialSpecName is the name\n                                    of the GMSA credential spec to use.\n                                  type: string\n                                runAsUserName:\n                                  description: The UserName in Windows to run the\n                                    entrypoint of the container process. Defaults\n                                    to the user specified in image metadata if unspecified.\n                                    May also be set in PodSecurityContext. If set\n                                    in both SecurityContext and PodSecurityContext,\n                                    the value specified in SecurityContext takes precedence.\n                                  type: string\n                              type: object\n                          type: object\n                        serviceAccount:\n                          description: 'DeprecatedServiceAccount is a depreciated\n                            alias for ServiceAccountName. Deprecated: Use serviceAccountName\n                            instead.'\n                          type: string\n                        serviceAccountName:\n                          description: 'ServiceAccountName is the name of the ServiceAccount\n                            to use to run this pod. More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/'\n                          type: string\n                        shareProcessNamespace:\n                          description: 'Share a single process namespace between all\n                            of the containers in a pod. When this is set containers\n                            will be able to view and signal processes from other containers\n                            in the same pod, and the first process in each container\n                            will not be assigned PID 1. HostPID and ShareProcessNamespace\n                            cannot both be set. Optional: Default to false.'\n                          type: boolean\n                        subdomain:\n                          description: If specified, the fully qualified Pod hostname\n                            will be \"<hostname>.<subdomain>.<pod namespace>.svc.<cluster\n                            domain>\". If not specified, the pod will not have a domainname\n                            at all.\n                          type: string\n                        terminationGracePeriodSeconds:\n                          description: Optional duration in seconds the pod needs\n                            to terminate gracefully. May be decreased in delete request.\n                            Value must be non-negative integer. The value zero indicates\n                            delete immediately. If this value is nil, the default\n                            grace period will be used instead. The grace period is\n                            the duration in seconds after the processes running in\n                            the pod are sent a termination signal and the time when\n                            the processes are forcibly halted with a kill signal.\n                            Set this value longer than the expected cleanup time for\n                            your process. Defaults to 30 seconds.\n                          format: int64\n                          type: integer\n                        tolerations:\n                          description: If specified, the pod's tolerations.\n                          items:\n                            description: The pod this Toleration is attached to tolerates\n                              any taint that matches the triple <key,value,effect>\n                              using the matching operator <operator>.\n                            properties:\n                              effect:\n                                description: Effect indicates the taint effect to\n                                  match. Empty means match all taint effects. When\n                                  specified, allowed values are NoSchedule, PreferNoSchedule\n                                  and NoExecute.\n                                type: string\n                              key:\n                                description: Key is the taint key that the toleration\n                                  applies to. Empty means match all taint keys. If\n                                  the key is empty, operator must be Exists; this\n                                  combination means to match all values and all keys.\n                                type: string\n                              operator:\n                                description: Operator represents a key's relationship\n                                  to the value. Valid operators are Exists and Equal.\n                                  Defaults to Equal. Exists is equivalent to wildcard\n                                  for value, so that a pod can tolerate all taints\n                                  of a particular category.\n                                type: string\n                              tolerationSeconds:\n                                description: TolerationSeconds represents the period\n                                  of time the toleration (which must be of effect\n                                  NoExecute, otherwise this field is ignored) tolerates\n                                  the taint. By default, it is not set, which means\n                                  tolerate the taint forever (do not evict). Zero\n                                  and negative values will be treated as 0 (evict\n                                  immediately) by the system.\n                                format: int64\n                                type: integer\n                              value:\n                                description: Value is the taint value the toleration\n                                  matches to. If the operator is Exists, the value\n                                  should be empty, otherwise just a regular string.\n                                type: string\n                            type: object\n                          type: array\n                        topologySpreadConstraints:\n                          description: TopologySpreadConstraints describes how a group\n                            of pods ought to spread across topology domains. Scheduler\n                            will schedule pods in a way which abides by the constraints.\n                            This field is only honored by clusters that enable the\n                            EvenPodsSpread feature. All topologySpreadConstraints\n                            are ANDed.\n                          items:\n                            description: TopologySpreadConstraint specifies how to\n                              spread matching pods among the given topology.\n                            properties:\n                              labelSelector:\n                                description: LabelSelector is used to find matching\n                                  pods. Pods that match this label selector are counted\n                                  to determine the number of pods in their corresponding\n                                  topology domain.\n                                properties:\n                                  matchExpressions:\n                                    description: matchExpressions is a list of label\n                                      selector requirements. The requirements are\n                                      ANDed.\n                                    items:\n                                      description: A label selector requirement is\n                                        a selector that contains values, a key, and\n                                        an operator that relates the key and values.\n                                      properties:\n                                        key:\n                                          description: key is the label key that the\n                                            selector applies to.\n                                          type: string\n                                        operator:\n                                          description: operator represents a key's\n                                            relationship to a set of values. Valid\n                                            operators are In, NotIn, Exists and DoesNotExist.\n                                          type: string\n                                        values:\n                                          description: values is an array of string\n                                            values. If the operator is In or NotIn,\n                                            the values array must be non-empty. If\n                                            the operator is Exists or DoesNotExist,\n                                            the values array must be empty. This array\n                                            is replaced during a strategic merge patch.\n                                          items:\n                                            type: string\n                                          type: array\n                                      required:\n                                      - key\n                                      - operator\n                                      type: object\n                                    type: array\n                                  matchLabels:\n                                    additionalProperties:\n                                      type: string\n                                    description: matchLabels is a map of {key,value}\n                                      pairs. A single {key,value} in the matchLabels\n                                      map is equivalent to an element of matchExpressions,\n                                      whose key field is \"key\", the operator is \"In\",\n                                      and the values array contains only \"value\".\n                                      The requirements are ANDed.\n                                    type: object\n                                type: object\n                              maxSkew:\n                                description: 'MaxSkew describes the degree to which\n                                  pods may be unevenly distributed. It''s the maximum\n                                  permitted difference between the number of matching\n                                  pods in any two topology domains of a given topology\n                                  type. For example, in a 3-zone cluster, MaxSkew\n                                  is set to 1, and pods with the same labelSelector\n                                  spread as 1/1/0: | zone1 | zone2 | zone3 | |   P   |   P   |       |\n                                  - if MaxSkew is 1, incoming pod can only be scheduled\n                                  to zone3 to become 1/1/1; scheduling it onto zone1(zone2)\n                                  would make the ActualSkew(2-0) on zone1(zone2) violate\n                                  MaxSkew(1). - if MaxSkew is 2, incoming pod can\n                                  be scheduled onto any zone. It''s a required field.\n                                  Default value is 1 and 0 is not allowed.'\n                                format: int32\n                                type: integer\n                              topologyKey:\n                                description: TopologyKey is the key of node labels.\n                                  Nodes that have a label with this key and identical\n                                  values are considered to be in the same topology.\n                                  We consider each <key, value> as a \"bucket\", and\n                                  try to put balanced number of pods into each bucket.\n                                  It's a required field.\n                                type: string\n                              whenUnsatisfiable:\n                                description: 'WhenUnsatisfiable indicates how to deal\n                                  with a pod if it doesn''t satisfy the spread constraint.\n                                  - DoNotSchedule (default) tells the scheduler not\n                                  to schedule it - ScheduleAnyway tells the scheduler\n                                  to still schedule it It''s considered as \"Unsatisfiable\"\n                                  if and only if placing incoming pod on any topology\n                                  violates \"MaxSkew\". For example, in a 3-zone cluster,\n                                  MaxSkew is set to 1, and pods with the same labelSelector\n                                  spread as 3/1/1: | zone1 | zone2 | zone3 | | P P\n                                  P |   P   |   P   | If WhenUnsatisfiable is set\n                                  to DoNotSchedule, incoming pod can only be scheduled\n                                  to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1)\n                                  on zone2(zone3) satisfies MaxSkew(1). In other words,\n                                  the cluster can still be imbalanced, but scheduler\n                                  won''t make it *more* imbalanced. It''s a required\n                                  field.'\n                                type: string\n                            required:\n                            - maxSkew\n                            - topologyKey\n                            - whenUnsatisfiable\n                            type: object\n                          type: array\n                          x-kubernetes-list-map-keys:\n                          - topologyKey\n                          - whenUnsatisfiable\n                          x-kubernetes-list-type: map\n                        volumes:\n                          description: 'List of volumes that can be mounted by containers\n                            belonging to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes'\n                          items:\n                            description: Volume represents a named volume in a pod\n                              that may be accessed by any container in the pod.\n                            properties:\n                              awsElasticBlockStore:\n                                description: 'AWSElasticBlockStore represents an AWS\n                                  Disk resource that is attached to a kubelet''s host\n                                  machine and then exposed to the pod. More info:\n                                  https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore'\n                                properties:\n                                  fsType:\n                                    description: 'Filesystem type of the volume that\n                                      you want to mount. Tip: Ensure that the filesystem\n                                      type is supported by the host operating system.\n                                      Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly\n                                      inferred to be \"ext4\" if unspecified. More info:\n                                      https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore\n                                      TODO: how do we prevent errors in the filesystem\n                                      from compromising the machine'\n                                    type: string\n                                  partition:\n                                    description: 'The partition in the volume that\n                                      you want to mount. If omitted, the default is\n                                      to mount by volume name. Examples: For volume\n                                      /dev/sda1, you specify the partition as \"1\".\n                                      Similarly, the volume partition for /dev/sda\n                                      is \"0\" (or you can leave the property empty).'\n                                    format: int32\n                                    type: integer\n                                  readOnly:\n                                    description: 'Specify \"true\" to force and set\n                                      the ReadOnly property in VolumeMounts to \"true\".\n                                      If omitted, the default is \"false\". More info:\n                                      https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore'\n                                    type: boolean\n                                  volumeID:\n                                    description: 'Unique ID of the persistent disk\n                                      resource in AWS (Amazon EBS volume). More info:\n                                      https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore'\n                                    type: string\n                                required:\n                                - volumeID\n                                type: object\n                              azureDisk:\n                                description: AzureDisk represents an Azure Data Disk\n                                  mount on the host and bind mount to the pod.\n                                properties:\n                                  cachingMode:\n                                    description: 'Host Caching mode: None, Read Only,\n                                      Read Write.'\n                                    type: string\n                                  diskName:\n                                    description: The Name of the data disk in the\n                                      blob storage\n                                    type: string\n                                  diskURI:\n                                    description: The URI the data disk in the blob\n                                      storage\n                                    type: string\n                                  fsType:\n                                    description: Filesystem type to mount. Must be\n                                      a filesystem type supported by the host operating\n                                      system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly\n                                      inferred to be \"ext4\" if unspecified.\n                                    type: string\n                                  kind:\n                                    description: 'Expected values Shared: multiple\n                                      blob disks per storage account  Dedicated: single\n                                      blob disk per storage account  Managed: azure\n                                      managed data disk (only in managed availability\n                                      set). defaults to shared'\n                                    type: string\n                                  readOnly:\n                                    description: Defaults to false (read/write). ReadOnly\n                                      here will force the ReadOnly setting in VolumeMounts.\n                                    type: boolean\n                                required:\n                                - diskName\n                                - diskURI\n                                type: object\n                              azureFile:\n                                description: AzureFile represents an Azure File Service\n                                  mount on the host and bind mount to the pod.\n                                properties:\n                                  readOnly:\n                                    description: Defaults to false (read/write). ReadOnly\n                                      here will force the ReadOnly setting in VolumeMounts.\n                                    type: boolean\n                                  secretName:\n                                    description: the name of secret that contains\n                                      Azure Storage Account Name and Key\n                                    type: string\n                                  shareName:\n                                    description: Share Name\n                                    type: string\n                                required:\n                                - secretName\n                                - shareName\n                                type: object\n                              cephfs:\n                                description: CephFS represents a Ceph FS mount on\n                                  the host that shares a pod's lifetime\n                                properties:\n                                  monitors:\n                                    description: 'Required: Monitors is a collection\n                                      of Ceph monitors More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it'\n                                    items:\n                                      type: string\n                                    type: array\n                                  path:\n                                    description: 'Optional: Used as the mounted root,\n                                      rather than the full Ceph tree, default is /'\n                                    type: string\n                                  readOnly:\n                                    description: 'Optional: Defaults to false (read/write).\n                                      ReadOnly here will force the ReadOnly setting\n                                      in VolumeMounts. More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it'\n                                    type: boolean\n                                  secretFile:\n                                    description: 'Optional: SecretFile is the path\n                                      to key ring for User, default is /etc/ceph/user.secret\n                                      More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it'\n                                    type: string\n                                  secretRef:\n                                    description: 'Optional: SecretRef is reference\n                                      to the authentication secret for User, default\n                                      is empty. More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it'\n                                    properties:\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                    type: object\n                                  user:\n                                    description: 'Optional: User is the rados user\n                                      name, default is admin More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it'\n                                    type: string\n                                required:\n                                - monitors\n                                type: object\n                              cinder:\n                                description: 'Cinder represents a cinder volume attached\n                                  and mounted on kubelets host machine. More info:\n                                  https://examples.k8s.io/mysql-cinder-pd/README.md'\n                                properties:\n                                  fsType:\n                                    description: 'Filesystem type to mount. Must be\n                                      a filesystem type supported by the host operating\n                                      system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly\n                                      inferred to be \"ext4\" if unspecified. More info:\n                                      https://examples.k8s.io/mysql-cinder-pd/README.md'\n                                    type: string\n                                  readOnly:\n                                    description: 'Optional: Defaults to false (read/write).\n                                      ReadOnly here will force the ReadOnly setting\n                                      in VolumeMounts. More info: https://examples.k8s.io/mysql-cinder-pd/README.md'\n                                    type: boolean\n                                  secretRef:\n                                    description: 'Optional: points to a secret object\n                                      containing parameters used to connect to OpenStack.'\n                                    properties:\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                    type: object\n                                  volumeID:\n                                    description: 'volume id used to identify the volume\n                                      in cinder. More info: https://examples.k8s.io/mysql-cinder-pd/README.md'\n                                    type: string\n                                required:\n                                - volumeID\n                                type: object\n                              configMap:\n                                description: ConfigMap represents a configMap that\n                                  should populate this volume\n                                properties:\n                                  defaultMode:\n                                    description: 'Optional: mode bits to use on created\n                                      files by default. Must be a value between 0\n                                      and 0777. Defaults to 0644. Directories within\n                                      the path are not affected by this setting. This\n                                      might be in conflict with other options that\n                                      affect the file mode, like fsGroup, and the\n                                      result can be other mode bits set.'\n                                    format: int32\n                                    type: integer\n                                  items:\n                                    description: If unspecified, each key-value pair\n                                      in the Data field of the referenced ConfigMap\n                                      will be projected into the volume as a file\n                                      whose name is the key and content is the value.\n                                      If specified, the listed keys will be projected\n                                      into the specified paths, and unlisted keys\n                                      will not be present. If a key is specified which\n                                      is not present in the ConfigMap, the volume\n                                      setup will error unless it is marked optional.\n                                      Paths must be relative and may not contain the\n                                      '..' path or start with '..'.\n                                    items:\n                                      description: Maps a string key to a path within\n                                        a volume.\n                                      properties:\n                                        key:\n                                          description: The key to project.\n                                          type: string\n                                        mode:\n                                          description: 'Optional: mode bits to use\n                                            on this file, must be a value between\n                                            0 and 0777. If not specified, the volume\n                                            defaultMode will be used. This might be\n                                            in conflict with other options that affect\n                                            the file mode, like fsGroup, and the result\n                                            can be other mode bits set.'\n                                          format: int32\n                                          type: integer\n                                        path:\n                                          description: The relative path of the file\n                                            to map the key to. May not be an absolute\n                                            path. May not contain the path element\n                                            '..'. May not start with the string '..'.\n                                          type: string\n                                      required:\n                                      - key\n                                      - path\n                                      type: object\n                                    type: array\n                                  name:\n                                    description: 'Name of the referent. More info:\n                                      https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                      TODO: Add other useful fields. apiVersion, kind,\n                                      uid?'\n                                    type: string\n                                  optional:\n                                    description: Specify whether the ConfigMap or\n                                      its keys must be defined\n                                    type: boolean\n                                type: object\n                              csi:\n                                description: CSI (Container Storage Interface) represents\n                                  storage that is handled by an external CSI driver\n                                  (Alpha feature).\n                                properties:\n                                  driver:\n                                    description: Driver is the name of the CSI driver\n                                      that handles this volume. Consult with your\n                                      admin for the correct name as registered in\n                                      the cluster.\n                                    type: string\n                                  fsType:\n                                    description: Filesystem type to mount. Ex. \"ext4\",\n                                      \"xfs\", \"ntfs\". If not provided, the empty value\n                                      is passed to the associated CSI driver which\n                                      will determine the default filesystem to apply.\n                                    type: string\n                                  nodePublishSecretRef:\n                                    description: NodePublishSecretRef is a reference\n                                      to the secret object containing sensitive information\n                                      to pass to the CSI driver to complete the CSI\n                                      NodePublishVolume and NodeUnpublishVolume calls.\n                                      This field is optional, and  may be empty if\n                                      no secret is required. If the secret object\n                                      contains more than one secret, all secret references\n                                      are passed.\n                                    properties:\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                    type: object\n                                  readOnly:\n                                    description: Specifies a read-only configuration\n                                      for the volume. Defaults to false (read/write).\n                                    type: boolean\n                                  volumeAttributes:\n                                    additionalProperties:\n                                      type: string\n                                    description: VolumeAttributes stores driver-specific\n                                      properties that are passed to the CSI driver.\n                                      Consult your driver's documentation for supported\n                                      values.\n                                    type: object\n                                required:\n                                - driver\n                                type: object\n                              downwardAPI:\n                                description: DownwardAPI represents downward API about\n                                  the pod that should populate this volume\n                                properties:\n                                  defaultMode:\n                                    description: 'Optional: mode bits to use on created\n                                      files by default. Must be a value between 0\n                                      and 0777. Defaults to 0644. Directories within\n                                      the path are not affected by this setting. This\n                                      might be in conflict with other options that\n                                      affect the file mode, like fsGroup, and the\n                                      result can be other mode bits set.'\n                                    format: int32\n                                    type: integer\n                                  items:\n                                    description: Items is a list of downward API volume\n                                      file\n                                    items:\n                                      description: DownwardAPIVolumeFile represents\n                                        information to create the file containing\n                                        the pod field\n                                      properties:\n                                        fieldRef:\n                                          description: 'Required: Selects a field\n                                            of the pod: only annotations, labels,\n                                            name and namespace are supported.'\n                                          properties:\n                                            apiVersion:\n                                              description: Version of the schema the\n                                                FieldPath is written in terms of,\n                                                defaults to \"v1\".\n                                              type: string\n                                            fieldPath:\n                                              description: Path of the field to select\n                                                in the specified API version.\n                                              type: string\n                                          required:\n                                          - fieldPath\n                                          type: object\n                                        mode:\n                                          description: 'Optional: mode bits to use\n                                            on this file, must be a value between\n                                            0 and 0777. If not specified, the volume\n                                            defaultMode will be used. This might be\n                                            in conflict with other options that affect\n                                            the file mode, like fsGroup, and the result\n                                            can be other mode bits set.'\n                                          format: int32\n                                          type: integer\n                                        path:\n                                          description: 'Required: Path is  the relative\n                                            path name of the file to be created. Must\n                                            not be absolute or contain the ''..''\n                                            path. Must be utf-8 encoded. The first\n                                            item of the relative path must not start\n                                            with ''..'''\n                                          type: string\n                                        resourceFieldRef:\n                                          description: 'Selects a resource of the\n                                            container: only resources limits and requests\n                                            (limits.cpu, limits.memory, requests.cpu\n                                            and requests.memory) are currently supported.'\n                                          properties:\n                                            containerName:\n                                              description: 'Container name: required\n                                                for volumes, optional for env vars'\n                                              type: string\n                                            divisor:\n                                              anyOf:\n                                              - type: integer\n                                              - type: string\n                                              description: Specifies the output format\n                                                of the exposed resources, defaults\n                                                to \"1\"\n                                              pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                              x-kubernetes-int-or-string: true\n                                            resource:\n                                              description: 'Required: resource to\n                                                select'\n                                              type: string\n                                          required:\n                                          - resource\n                                          type: object\n                                      required:\n                                      - path\n                                      type: object\n                                    type: array\n                                type: object\n                              emptyDir:\n                                description: 'EmptyDir represents a temporary directory\n                                  that shares a pod''s lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir'\n                                properties:\n                                  medium:\n                                    description: 'What type of storage medium should\n                                      back this directory. The default is \"\" which\n                                      means to use the node''s default medium. Must\n                                      be an empty string (default) or Memory. More\n                                      info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir'\n                                    type: string\n                                  sizeLimit:\n                                    anyOf:\n                                    - type: integer\n                                    - type: string\n                                    description: 'Total amount of local storage required\n                                      for this EmptyDir volume. The size limit is\n                                      also applicable for memory medium. The maximum\n                                      usage on memory medium EmptyDir would be the\n                                      minimum value between the SizeLimit specified\n                                      here and the sum of memory limits of all containers\n                                      in a pod. The default is nil which means that\n                                      the limit is undefined. More info: http://kubernetes.io/docs/user-guide/volumes#emptydir'\n                                    pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                    x-kubernetes-int-or-string: true\n                                type: object\n                              fc:\n                                description: FC represents a Fibre Channel resource\n                                  that is attached to a kubelet's host machine and\n                                  then exposed to the pod.\n                                properties:\n                                  fsType:\n                                    description: 'Filesystem type to mount. Must be\n                                      a filesystem type supported by the host operating\n                                      system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly\n                                      inferred to be \"ext4\" if unspecified. TODO:\n                                      how do we prevent errors in the filesystem from\n                                      compromising the machine'\n                                    type: string\n                                  lun:\n                                    description: 'Optional: FC target lun number'\n                                    format: int32\n                                    type: integer\n                                  readOnly:\n                                    description: 'Optional: Defaults to false (read/write).\n                                      ReadOnly here will force the ReadOnly setting\n                                      in VolumeMounts.'\n                                    type: boolean\n                                  targetWWNs:\n                                    description: 'Optional: FC target worldwide names\n                                      (WWNs)'\n                                    items:\n                                      type: string\n                                    type: array\n                                  wwids:\n                                    description: 'Optional: FC volume world wide identifiers\n                                      (wwids) Either wwids or combination of targetWWNs\n                                      and lun must be set, but not both simultaneously.'\n                                    items:\n                                      type: string\n                                    type: array\n                                type: object\n                              flexVolume:\n                                description: FlexVolume represents a generic volume\n                                  resource that is provisioned/attached using an exec\n                                  based plugin.\n                                properties:\n                                  driver:\n                                    description: Driver is the name of the driver\n                                      to use for this volume.\n                                    type: string\n                                  fsType:\n                                    description: Filesystem type to mount. Must be\n                                      a filesystem type supported by the host operating\n                                      system. Ex. \"ext4\", \"xfs\", \"ntfs\". The default\n                                      filesystem depends on FlexVolume script.\n                                    type: string\n                                  options:\n                                    additionalProperties:\n                                      type: string\n                                    description: 'Optional: Extra command options\n                                      if any.'\n                                    type: object\n                                  readOnly:\n                                    description: 'Optional: Defaults to false (read/write).\n                                      ReadOnly here will force the ReadOnly setting\n                                      in VolumeMounts.'\n                                    type: boolean\n                                  secretRef:\n                                    description: 'Optional: SecretRef is reference\n                                      to the secret object containing sensitive information\n                                      to pass to the plugin scripts. This may be empty\n                                      if no secret object is specified. If the secret\n                                      object contains more than one secret, all secrets\n                                      are passed to the plugin scripts.'\n                                    properties:\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                    type: object\n                                required:\n                                - driver\n                                type: object\n                              flocker:\n                                description: Flocker represents a Flocker volume attached\n                                  to a kubelet's host machine. This depends on the\n                                  Flocker control service being running\n                                properties:\n                                  datasetName:\n                                    description: Name of the dataset stored as metadata\n                                      -> name on the dataset for Flocker should be\n                                      considered as deprecated\n                                    type: string\n                                  datasetUUID:\n                                    description: UUID of the dataset. This is unique\n                                      identifier of a Flocker dataset\n                                    type: string\n                                type: object\n                              gcePersistentDisk:\n                                description: 'GCEPersistentDisk represents a GCE Disk\n                                  resource that is attached to a kubelet''s host machine\n                                  and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk'\n                                properties:\n                                  fsType:\n                                    description: 'Filesystem type of the volume that\n                                      you want to mount. Tip: Ensure that the filesystem\n                                      type is supported by the host operating system.\n                                      Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly\n                                      inferred to be \"ext4\" if unspecified. More info:\n                                      https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk\n                                      TODO: how do we prevent errors in the filesystem\n                                      from compromising the machine'\n                                    type: string\n                                  partition:\n                                    description: 'The partition in the volume that\n                                      you want to mount. If omitted, the default is\n                                      to mount by volume name. Examples: For volume\n                                      /dev/sda1, you specify the partition as \"1\".\n                                      Similarly, the volume partition for /dev/sda\n                                      is \"0\" (or you can leave the property empty).\n                                      More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk'\n                                    format: int32\n                                    type: integer\n                                  pdName:\n                                    description: 'Unique name of the PD resource in\n                                      GCE. Used to identify the disk in GCE. More\n                                      info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk'\n                                    type: string\n                                  readOnly:\n                                    description: 'ReadOnly here will force the ReadOnly\n                                      setting in VolumeMounts. Defaults to false.\n                                      More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk'\n                                    type: boolean\n                                required:\n                                - pdName\n                                type: object\n                              gitRepo:\n                                description: 'GitRepo represents a git repository\n                                  at a particular revision. DEPRECATED: GitRepo is\n                                  deprecated. To provision a container with a git\n                                  repo, mount an EmptyDir into an InitContainer that\n                                  clones the repo using git, then mount the EmptyDir\n                                  into the Pod''s container.'\n                                properties:\n                                  directory:\n                                    description: Target directory name. Must not contain\n                                      or start with '..'.  If '.' is supplied, the\n                                      volume directory will be the git repository.  Otherwise,\n                                      if specified, the volume will contain the git\n                                      repository in the subdirectory with the given\n                                      name.\n                                    type: string\n                                  repository:\n                                    description: Repository URL\n                                    type: string\n                                  revision:\n                                    description: Commit hash for the specified revision.\n                                    type: string\n                                required:\n                                - repository\n                                type: object\n                              glusterfs:\n                                description: 'Glusterfs represents a Glusterfs mount\n                                  on the host that shares a pod''s lifetime. More\n                                  info: https://examples.k8s.io/volumes/glusterfs/README.md'\n                                properties:\n                                  endpoints:\n                                    description: 'EndpointsName is the endpoint name\n                                      that details Glusterfs topology. More info:\n                                      https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod'\n                                    type: string\n                                  path:\n                                    description: 'Path is the Glusterfs volume path.\n                                      More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod'\n                                    type: string\n                                  readOnly:\n                                    description: 'ReadOnly here will force the Glusterfs\n                                      volume to be mounted with read-only permissions.\n                                      Defaults to false. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod'\n                                    type: boolean\n                                required:\n                                - endpoints\n                                - path\n                                type: object\n                              hostPath:\n                                description: 'HostPath represents a pre-existing file\n                                  or directory on the host machine that is directly\n                                  exposed to the container. This is generally used\n                                  for system agents or other privileged things that\n                                  are allowed to see the host machine. Most containers\n                                  will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath\n                                  --- TODO(jonesdl) We need to restrict who can use\n                                  host directory mounts and who can/can not mount\n                                  host directories as read/write.'\n                                properties:\n                                  path:\n                                    description: 'Path of the directory on the host.\n                                      If the path is a symlink, it will follow the\n                                      link to the real path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath'\n                                    type: string\n                                  type:\n                                    description: 'Type for HostPath Volume Defaults\n                                      to \"\" More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath'\n                                    type: string\n                                required:\n                                - path\n                                type: object\n                              iscsi:\n                                description: 'ISCSI represents an ISCSI Disk resource\n                                  that is attached to a kubelet''s host machine and\n                                  then exposed to the pod. More info: https://examples.k8s.io/volumes/iscsi/README.md'\n                                properties:\n                                  chapAuthDiscovery:\n                                    description: whether support iSCSI Discovery CHAP\n                                      authentication\n                                    type: boolean\n                                  chapAuthSession:\n                                    description: whether support iSCSI Session CHAP\n                                      authentication\n                                    type: boolean\n                                  fsType:\n                                    description: 'Filesystem type of the volume that\n                                      you want to mount. Tip: Ensure that the filesystem\n                                      type is supported by the host operating system.\n                                      Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly\n                                      inferred to be \"ext4\" if unspecified. More info:\n                                      https://kubernetes.io/docs/concepts/storage/volumes#iscsi\n                                      TODO: how do we prevent errors in the filesystem\n                                      from compromising the machine'\n                                    type: string\n                                  initiatorName:\n                                    description: Custom iSCSI Initiator Name. If initiatorName\n                                      is specified with iscsiInterface simultaneously,\n                                      new iSCSI interface <target portal>:<volume\n                                      name> will be created for the connection.\n                                    type: string\n                                  iqn:\n                                    description: Target iSCSI Qualified Name.\n                                    type: string\n                                  iscsiInterface:\n                                    description: iSCSI Interface Name that uses an\n                                      iSCSI transport. Defaults to 'default' (tcp).\n                                    type: string\n                                  lun:\n                                    description: iSCSI Target Lun number.\n                                    format: int32\n                                    type: integer\n                                  portals:\n                                    description: iSCSI Target Portal List. The portal\n                                      is either an IP or ip_addr:port if the port\n                                      is other than default (typically TCP ports 860\n                                      and 3260).\n                                    items:\n                                      type: string\n                                    type: array\n                                  readOnly:\n                                    description: ReadOnly here will force the ReadOnly\n                                      setting in VolumeMounts. Defaults to false.\n                                    type: boolean\n                                  secretRef:\n                                    description: CHAP Secret for iSCSI target and\n                                      initiator authentication\n                                    properties:\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                    type: object\n                                  targetPortal:\n                                    description: iSCSI Target Portal. The Portal is\n                                      either an IP or ip_addr:port if the port is\n                                      other than default (typically TCP ports 860\n                                      and 3260).\n                                    type: string\n                                required:\n                                - iqn\n                                - lun\n                                - targetPortal\n                                type: object\n                              name:\n                                description: 'Volume''s name. Must be a DNS_LABEL\n                                  and unique within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'\n                                type: string\n                              nfs:\n                                description: 'NFS represents an NFS mount on the host\n                                  that shares a pod''s lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs'\n                                properties:\n                                  path:\n                                    description: 'Path that is exported by the NFS\n                                      server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs'\n                                    type: string\n                                  readOnly:\n                                    description: 'ReadOnly here will force the NFS\n                                      export to be mounted with read-only permissions.\n                                      Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs'\n                                    type: boolean\n                                  server:\n                                    description: 'Server is the hostname or IP address\n                                      of the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs'\n                                    type: string\n                                required:\n                                - path\n                                - server\n                                type: object\n                              persistentVolumeClaim:\n                                description: 'PersistentVolumeClaimVolumeSource represents\n                                  a reference to a PersistentVolumeClaim in the same\n                                  namespace. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims'\n                                properties:\n                                  claimName:\n                                    description: 'ClaimName is the name of a PersistentVolumeClaim\n                                      in the same namespace as the pod using this\n                                      volume. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims'\n                                    type: string\n                                  readOnly:\n                                    description: Will force the ReadOnly setting in\n                                      VolumeMounts. Default false.\n                                    type: boolean\n                                required:\n                                - claimName\n                                type: object\n                              photonPersistentDisk:\n                                description: PhotonPersistentDisk represents a PhotonController\n                                  persistent disk attached and mounted on kubelets\n                                  host machine\n                                properties:\n                                  fsType:\n                                    description: Filesystem type to mount. Must be\n                                      a filesystem type supported by the host operating\n                                      system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly\n                                      inferred to be \"ext4\" if unspecified.\n                                    type: string\n                                  pdID:\n                                    description: ID that identifies Photon Controller\n                                      persistent disk\n                                    type: string\n                                required:\n                                - pdID\n                                type: object\n                              portworxVolume:\n                                description: PortworxVolume represents a portworx\n                                  volume attached and mounted on kubelets host machine\n                                properties:\n                                  fsType:\n                                    description: FSType represents the filesystem\n                                      type to mount Must be a filesystem type supported\n                                      by the host operating system. Ex. \"ext4\", \"xfs\".\n                                      Implicitly inferred to be \"ext4\" if unspecified.\n                                    type: string\n                                  readOnly:\n                                    description: Defaults to false (read/write). ReadOnly\n                                      here will force the ReadOnly setting in VolumeMounts.\n                                    type: boolean\n                                  volumeID:\n                                    description: VolumeID uniquely identifies a Portworx\n                                      volume\n                                    type: string\n                                required:\n                                - volumeID\n                                type: object\n                              projected:\n                                description: Items for all in one resources secrets,\n                                  configmaps, and downward API\n                                properties:\n                                  defaultMode:\n                                    description: Mode bits to use on created files\n                                      by default. Must be a value between 0 and 0777.\n                                      Directories within the path are not affected\n                                      by this setting. This might be in conflict with\n                                      other options that affect the file mode, like\n                                      fsGroup, and the result can be other mode bits\n                                      set.\n                                    format: int32\n                                    type: integer\n                                  sources:\n                                    description: list of volume projections\n                                    items:\n                                      description: Projection that may be projected\n                                        along with other supported volume types\n                                      properties:\n                                        configMap:\n                                          description: information about the configMap\n                                            data to project\n                                          properties:\n                                            items:\n                                              description: If unspecified, each key-value\n                                                pair in the Data field of the referenced\n                                                ConfigMap will be projected into the\n                                                volume as a file whose name is the\n                                                key and content is the value. If specified,\n                                                the listed keys will be projected\n                                                into the specified paths, and unlisted\n                                                keys will not be present. If a key\n                                                is specified which is not present\n                                                in the ConfigMap, the volume setup\n                                                will error unless it is marked optional.\n                                                Paths must be relative and may not\n                                                contain the '..' path or start with\n                                                '..'.\n                                              items:\n                                                description: Maps a string key to\n                                                  a path within a volume.\n                                                properties:\n                                                  key:\n                                                    description: The key to project.\n                                                    type: string\n                                                  mode:\n                                                    description: 'Optional: mode bits\n                                                      to use on this file, must be\n                                                      a value between 0 and 0777.\n                                                      If not specified, the volume\n                                                      defaultMode will be used. This\n                                                      might be in conflict with other\n                                                      options that affect the file\n                                                      mode, like fsGroup, and the\n                                                      result can be other mode bits\n                                                      set.'\n                                                    format: int32\n                                                    type: integer\n                                                  path:\n                                                    description: The relative path\n                                                      of the file to map the key to.\n                                                      May not be an absolute path.\n                                                      May not contain the path element\n                                                      '..'. May not start with the\n                                                      string '..'.\n                                                    type: string\n                                                required:\n                                                - key\n                                                - path\n                                                type: object\n                                              type: array\n                                            name:\n                                              description: 'Name of the referent.\n                                                More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                                TODO: Add other useful fields. apiVersion,\n                                                kind, uid?'\n                                              type: string\n                                            optional:\n                                              description: Specify whether the ConfigMap\n                                                or its keys must be defined\n                                              type: boolean\n                                          type: object\n                                        downwardAPI:\n                                          description: information about the downwardAPI\n                                            data to project\n                                          properties:\n                                            items:\n                                              description: Items is a list of DownwardAPIVolume\n                                                file\n                                              items:\n                                                description: DownwardAPIVolumeFile\n                                                  represents information to create\n                                                  the file containing the pod field\n                                                properties:\n                                                  fieldRef:\n                                                    description: 'Required: Selects\n                                                      a field of the pod: only annotations,\n                                                      labels, name and namespace are\n                                                      supported.'\n                                                    properties:\n                                                      apiVersion:\n                                                        description: Version of the\n                                                          schema the FieldPath is\n                                                          written in terms of, defaults\n                                                          to \"v1\".\n                                                        type: string\n                                                      fieldPath:\n                                                        description: Path of the field\n                                                          to select in the specified\n                                                          API version.\n                                                        type: string\n                                                    required:\n                                                    - fieldPath\n                                                    type: object\n                                                  mode:\n                                                    description: 'Optional: mode bits\n                                                      to use on this file, must be\n                                                      a value between 0 and 0777.\n                                                      If not specified, the volume\n                                                      defaultMode will be used. This\n                                                      might be in conflict with other\n                                                      options that affect the file\n                                                      mode, like fsGroup, and the\n                                                      result can be other mode bits\n                                                      set.'\n                                                    format: int32\n                                                    type: integer\n                                                  path:\n                                                    description: 'Required: Path is  the\n                                                      relative path name of the file\n                                                      to be created. Must not be absolute\n                                                      or contain the ''..'' path.\n                                                      Must be utf-8 encoded. The first\n                                                      item of the relative path must\n                                                      not start with ''..'''\n                                                    type: string\n                                                  resourceFieldRef:\n                                                    description: 'Selects a resource\n                                                      of the container: only resources\n                                                      limits and requests (limits.cpu,\n                                                      limits.memory, requests.cpu\n                                                      and requests.memory) are currently\n                                                      supported.'\n                                                    properties:\n                                                      containerName:\n                                                        description: 'Container name:\n                                                          required for volumes, optional\n                                                          for env vars'\n                                                        type: string\n                                                      divisor:\n                                                        anyOf:\n                                                        - type: integer\n                                                        - type: string\n                                                        description: Specifies the\n                                                          output format of the exposed\n                                                          resources, defaults to \"1\"\n                                                        pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                                        x-kubernetes-int-or-string: true\n                                                      resource:\n                                                        description: 'Required: resource\n                                                          to select'\n                                                        type: string\n                                                    required:\n                                                    - resource\n                                                    type: object\n                                                required:\n                                                - path\n                                                type: object\n                                              type: array\n                                          type: object\n                                        secret:\n                                          description: information about the secret\n                                            data to project\n                                          properties:\n                                            items:\n                                              description: If unspecified, each key-value\n                                                pair in the Data field of the referenced\n                                                Secret will be projected into the\n                                                volume as a file whose name is the\n                                                key and content is the value. If specified,\n                                                the listed keys will be projected\n                                                into the specified paths, and unlisted\n                                                keys will not be present. If a key\n                                                is specified which is not present\n                                                in the Secret, the volume setup will\n                                                error unless it is marked optional.\n                                                Paths must be relative and may not\n                                                contain the '..' path or start with\n                                                '..'.\n                                              items:\n                                                description: Maps a string key to\n                                                  a path within a volume.\n                                                properties:\n                                                  key:\n                                                    description: The key to project.\n                                                    type: string\n                                                  mode:\n                                                    description: 'Optional: mode bits\n                                                      to use on this file, must be\n                                                      a value between 0 and 0777.\n                                                      If not specified, the volume\n                                                      defaultMode will be used. This\n                                                      might be in conflict with other\n                                                      options that affect the file\n                                                      mode, like fsGroup, and the\n                                                      result can be other mode bits\n                                                      set.'\n                                                    format: int32\n                                                    type: integer\n                                                  path:\n                                                    description: The relative path\n                                                      of the file to map the key to.\n                                                      May not be an absolute path.\n                                                      May not contain the path element\n                                                      '..'. May not start with the\n                                                      string '..'.\n                                                    type: string\n                                                required:\n                                                - key\n                                                - path\n                                                type: object\n                                              type: array\n                                            name:\n                                              description: 'Name of the referent.\n                                                More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                                TODO: Add other useful fields. apiVersion,\n                                                kind, uid?'\n                                              type: string\n                                            optional:\n                                              description: Specify whether the Secret\n                                                or its key must be defined\n                                              type: boolean\n                                          type: object\n                                        serviceAccountToken:\n                                          description: information about the serviceAccountToken\n                                            data to project\n                                          properties:\n                                            audience:\n                                              description: Audience is the intended\n                                                audience of the token. A recipient\n                                                of a token must identify itself with\n                                                an identifier specified in the audience\n                                                of the token, and otherwise should\n                                                reject the token. The audience defaults\n                                                to the identifier of the apiserver.\n                                              type: string\n                                            expirationSeconds:\n                                              description: ExpirationSeconds is the\n                                                requested duration of validity of\n                                                the service account token. As the\n                                                token approaches expiration, the kubelet\n                                                volume plugin will proactively rotate\n                                                the service account token. The kubelet\n                                                will start trying to rotate the token\n                                                if the token is older than 80 percent\n                                                of its time to live or if the token\n                                                is older than 24 hours.Defaults to\n                                                1 hour and must be at least 10 minutes.\n                                              format: int64\n                                              type: integer\n                                            path:\n                                              description: Path is the path relative\n                                                to the mount point of the file to\n                                                project the token into.\n                                              type: string\n                                          required:\n                                          - path\n                                          type: object\n                                      type: object\n                                    type: array\n                                required:\n                                - sources\n                                type: object\n                              quobyte:\n                                description: Quobyte represents a Quobyte mount on\n                                  the host that shares a pod's lifetime\n                                properties:\n                                  group:\n                                    description: Group to map volume access to Default\n                                      is no group\n                                    type: string\n                                  readOnly:\n                                    description: ReadOnly here will force the Quobyte\n                                      volume to be mounted with read-only permissions.\n                                      Defaults to false.\n                                    type: boolean\n                                  registry:\n                                    description: Registry represents a single or multiple\n                                      Quobyte Registry services specified as a string\n                                      as host:port pair (multiple entries are separated\n                                      with commas) which acts as the central registry\n                                      for volumes\n                                    type: string\n                                  tenant:\n                                    description: Tenant owning the given Quobyte volume\n                                      in the Backend Used with dynamically provisioned\n                                      Quobyte volumes, value is set by the plugin\n                                    type: string\n                                  user:\n                                    description: User to map volume access to Defaults\n                                      to serivceaccount user\n                                    type: string\n                                  volume:\n                                    description: Volume is a string that references\n                                      an already created Quobyte volume by name.\n                                    type: string\n                                required:\n                                - registry\n                                - volume\n                                type: object\n                              rbd:\n                                description: 'RBD represents a Rados Block Device\n                                  mount on the host that shares a pod''s lifetime.\n                                  More info: https://examples.k8s.io/volumes/rbd/README.md'\n                                properties:\n                                  fsType:\n                                    description: 'Filesystem type of the volume that\n                                      you want to mount. Tip: Ensure that the filesystem\n                                      type is supported by the host operating system.\n                                      Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly\n                                      inferred to be \"ext4\" if unspecified. More info:\n                                      https://kubernetes.io/docs/concepts/storage/volumes#rbd\n                                      TODO: how do we prevent errors in the filesystem\n                                      from compromising the machine'\n                                    type: string\n                                  image:\n                                    description: 'The rados image name. More info:\n                                      https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it'\n                                    type: string\n                                  keyring:\n                                    description: 'Keyring is the path to key ring\n                                      for RBDUser. Default is /etc/ceph/keyring. More\n                                      info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it'\n                                    type: string\n                                  monitors:\n                                    description: 'A collection of Ceph monitors. More\n                                      info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it'\n                                    items:\n                                      type: string\n                                    type: array\n                                  pool:\n                                    description: 'The rados pool name. Default is\n                                      rbd. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it'\n                                    type: string\n                                  readOnly:\n                                    description: 'ReadOnly here will force the ReadOnly\n                                      setting in VolumeMounts. Defaults to false.\n                                      More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it'\n                                    type: boolean\n                                  secretRef:\n                                    description: 'SecretRef is name of the authentication\n                                      secret for RBDUser. If provided overrides keyring.\n                                      Default is nil. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it'\n                                    properties:\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                    type: object\n                                  user:\n                                    description: 'The rados user name. Default is\n                                      admin. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it'\n                                    type: string\n                                required:\n                                - image\n                                - monitors\n                                type: object\n                              scaleIO:\n                                description: ScaleIO represents a ScaleIO persistent\n                                  volume attached and mounted on Kubernetes nodes.\n                                properties:\n                                  fsType:\n                                    description: Filesystem type to mount. Must be\n                                      a filesystem type supported by the host operating\n                                      system. Ex. \"ext4\", \"xfs\", \"ntfs\". Default is\n                                      \"xfs\".\n                                    type: string\n                                  gateway:\n                                    description: The host address of the ScaleIO API\n                                      Gateway.\n                                    type: string\n                                  protectionDomain:\n                                    description: The name of the ScaleIO Protection\n                                      Domain for the configured storage.\n                                    type: string\n                                  readOnly:\n                                    description: Defaults to false (read/write). ReadOnly\n                                      here will force the ReadOnly setting in VolumeMounts.\n                                    type: boolean\n                                  secretRef:\n                                    description: SecretRef references to the secret\n                                      for ScaleIO user and other sensitive information.\n                                      If this is not provided, Login operation will\n                                      fail.\n                                    properties:\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                    type: object\n                                  sslEnabled:\n                                    description: Flag to enable/disable SSL communication\n                                      with Gateway, default false\n                                    type: boolean\n                                  storageMode:\n                                    description: Indicates whether the storage for\n                                      a volume should be ThickProvisioned or ThinProvisioned.\n                                      Default is ThinProvisioned.\n                                    type: string\n                                  storagePool:\n                                    description: The ScaleIO Storage Pool associated\n                                      with the protection domain.\n                                    type: string\n                                  system:\n                                    description: The name of the storage system as\n                                      configured in ScaleIO.\n                                    type: string\n                                  volumeName:\n                                    description: The name of a volume already created\n                                      in the ScaleIO system that is associated with\n                                      this volume source.\n                                    type: string\n                                required:\n                                - gateway\n                                - secretRef\n                                - system\n                                type: object\n                              secret:\n                                description: 'Secret represents a secret that should\n                                  populate this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret'\n                                properties:\n                                  defaultMode:\n                                    description: 'Optional: mode bits to use on created\n                                      files by default. Must be a value between 0\n                                      and 0777. Defaults to 0644. Directories within\n                                      the path are not affected by this setting. This\n                                      might be in conflict with other options that\n                                      affect the file mode, like fsGroup, and the\n                                      result can be other mode bits set.'\n                                    format: int32\n                                    type: integer\n                                  items:\n                                    description: If unspecified, each key-value pair\n                                      in the Data field of the referenced Secret will\n                                      be projected into the volume as a file whose\n                                      name is the key and content is the value. If\n                                      specified, the listed keys will be projected\n                                      into the specified paths, and unlisted keys\n                                      will not be present. If a key is specified which\n                                      is not present in the Secret, the volume setup\n                                      will error unless it is marked optional. Paths\n                                      must be relative and may not contain the '..'\n                                      path or start with '..'.\n                                    items:\n                                      description: Maps a string key to a path within\n                                        a volume.\n                                      properties:\n                                        key:\n                                          description: The key to project.\n                                          type: string\n                                        mode:\n                                          description: 'Optional: mode bits to use\n                                            on this file, must be a value between\n                                            0 and 0777. If not specified, the volume\n                                            defaultMode will be used. This might be\n                                            in conflict with other options that affect\n                                            the file mode, like fsGroup, and the result\n                                            can be other mode bits set.'\n                                          format: int32\n                                          type: integer\n                                        path:\n                                          description: The relative path of the file\n                                            to map the key to. May not be an absolute\n                                            path. May not contain the path element\n                                            '..'. May not start with the string '..'.\n                                          type: string\n                                      required:\n                                      - key\n                                      - path\n                                      type: object\n                                    type: array\n                                  optional:\n                                    description: Specify whether the Secret or its\n                                      keys must be defined\n                                    type: boolean\n                                  secretName:\n                                    description: 'Name of the secret in the pod''s\n                                      namespace to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret'\n                                    type: string\n                                type: object\n                              storageos:\n                                description: StorageOS represents a StorageOS volume\n                                  attached and mounted on Kubernetes nodes.\n                                properties:\n                                  fsType:\n                                    description: Filesystem type to mount. Must be\n                                      a filesystem type supported by the host operating\n                                      system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly\n                                      inferred to be \"ext4\" if unspecified.\n                                    type: string\n                                  readOnly:\n                                    description: Defaults to false (read/write). ReadOnly\n                                      here will force the ReadOnly setting in VolumeMounts.\n                                    type: boolean\n                                  secretRef:\n                                    description: SecretRef specifies the secret to\n                                      use for obtaining the StorageOS API credentials.  If\n                                      not specified, default values will be attempted.\n                                    properties:\n                                      name:\n                                        description: 'Name of the referent. More info:\n                                          https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n                                          TODO: Add other useful fields. apiVersion,\n                                          kind, uid?'\n                                        type: string\n                                    type: object\n                                  volumeName:\n                                    description: VolumeName is the human-readable\n                                      name of the StorageOS volume.  Volume names\n                                      are only unique within a namespace.\n                                    type: string\n                                  volumeNamespace:\n                                    description: VolumeNamespace specifies the scope\n                                      of the volume within StorageOS.  If no namespace\n                                      is specified then the Pod's namespace will be\n                                      used.  This allows the Kubernetes name scoping\n                                      to be mirrored within StorageOS for tighter\n                                      integration. Set VolumeName to any name to override\n                                      the default behaviour. Set to \"default\" if you\n                                      are not using namespaces within StorageOS. Namespaces\n                                      that do not pre-exist within StorageOS will\n                                      be created.\n                                    type: string\n                                type: object\n                              vsphereVolume:\n                                description: VsphereVolume represents a vSphere volume\n                                  attached and mounted on kubelets host machine\n                                properties:\n                                  fsType:\n                                    description: Filesystem type to mount. Must be\n                                      a filesystem type supported by the host operating\n                                      system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly\n                                      inferred to be \"ext4\" if unspecified.\n                                    type: string\n                                  storagePolicyID:\n                                    description: Storage Policy Based Management (SPBM)\n                                      profile ID associated with the StoragePolicyName.\n                                    type: string\n                                  storagePolicyName:\n                                    description: Storage Policy Based Management (SPBM)\n                                      profile name.\n                                    type: string\n                                  volumePath:\n                                    description: Path that identifies vSphere volume\n                                      vmdk\n                                    type: string\n                                required:\n                                - volumePath\n                                type: object\n                            required:\n                            - name\n                            type: object\n                          type: array\n                      required:\n                      - containers\n                      type: object\n                  type: object\n                ttlSecondsAfterFinished:\n                  description: ttlSecondsAfterFinished limits the lifetime of a Job\n                    that has finished execution (either Complete or Failed). If this\n                    field is set, ttlSecondsAfterFinished after the Job finishes,\n                    it is eligible to be automatically deleted. When the Job is being\n                    deleted, its lifecycle guarantees (e.g. finalizers) will be honored.\n                    If this field is unset, the Job won't be automatically deleted.\n                    If this field is set to zero, the Job becomes eligible to be deleted\n                    immediately after it finishes. This field is alpha-level and is\n                    only honored by servers that enable the TTLAfterFinished feature.\n                  format: int32\n                  type: integer\n              required:\n              - template\n              type: object\n            maxReplicaCount:\n              format: int32\n              type: integer\n            pollingInterval:\n              format: int32\n              type: integer\n            successfulJobsHistoryLimit:\n              format: int32\n              type: integer\n            triggers:\n              items:\n                description: ScaleTriggers reference the scaler that will be used\n                properties:\n                  authenticationRef:\n                    description: ScaledObjectAuthRef points to the TriggerAuthentication\n                      object that is used to authenticate the scaler with the environment\n                    properties:\n                      name:\n                        type: string\n                    required:\n                    - name\n                    type: object\n                  metadata:\n                    additionalProperties:\n                      type: string\n                    type: object\n                  name:\n                    type: string\n                  type:\n                    type: string\n                required:\n                - metadata\n                - type\n                type: object\n              type: array\n          required:\n          - jobTargetRef\n          - triggers\n          type: object\n        status:\n          description: ScaledJobStatus defines the observed state of ScaledJob\n          properties:\n            conditions:\n              description: Conditions an array representation to store multiple Conditions\n              items:\n                description: Condition to store the condition state\n                properties:\n                  message:\n                    description: A human readable message indicating details about\n                      the transition.\n                    type: string\n                  reason:\n                    description: The reason for the condition's last transition.\n                    type: string\n                  status:\n                    description: Status of the condition, one of True, False, Unknown.\n                    type: string\n                  type:\n                    description: Type of condition\n                    type: string\n                required:\n                - status\n                - type\n                type: object\n              type: array\n            lastActiveTime:\n              format: date-time\n              type: string\n          type: object\n      type: object\n  version: v1alpha1\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: true\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n---\napiVersion: apiextensions.k8s.io/v1beta1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.3.0\n  creationTimestamp: null\n  labels:\n    app.kubernetes.io/part-of: keda-operator\n    app.kubernetes.io/version: 2.0.0-beta\n  name: scaledobjects.keda.sh\nspec:\n  additionalPrinterColumns:\n  - JSONPath: .status.scaleTargetKind\n    name: ScaleTargetKind\n    type: string\n  - JSONPath: .spec.scaleTargetRef.name\n    name: ScaleTargetName\n    type: string\n  - JSONPath: .spec.triggers[*].type\n    name: Triggers\n    type: string\n  - JSONPath: .spec.triggers[*].authenticationRef.name\n    name: Authentication\n    type: string\n  - JSONPath: .status.conditions[?(@.type==\"Ready\")].status\n    name: Ready\n    type: string\n  - JSONPath: .status.conditions[?(@.type==\"Active\")].status\n    name: Active\n    type: string\n  - JSONPath: .metadata.creationTimestamp\n    name: Age\n    type: date\n  group: keda.sh\n  names:\n    kind: ScaledObject\n    listKind: ScaledObjectList\n    plural: scaledobjects\n    shortNames:\n    - so\n    singular: scaledobject\n  scope: Namespaced\n  subresources:\n    status: {}\n  validation:\n    openAPIV3Schema:\n      description: ScaledObject is a specification for a ScaledObject resource\n      properties:\n        apiVersion:\n          description: 'APIVersion defines the versioned schema of this representation\n            of an object. Servers should convert recognized schemas to the latest\n            internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n          type: string\n        kind:\n          description: 'Kind is a string value representing the REST resource this\n            object represents. Servers may infer this from the endpoint the client\n            submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n          type: string\n        metadata:\n          type: object\n        spec:\n          description: ScaledObjectSpec is the spec for a ScaledObject resource\n          properties:\n            advanced:\n              description: AdvancedConfig specifies advance scaling options\n              properties:\n                horizontalPodAutoscalerConfig:\n                  description: HorizontalPodAutoscalerConfig specifies horizontal\n                    scale config\n                  properties:\n                    behavior:\n                      description: HorizontalPodAutoscalerBehavior configures the\n                        scaling behavior of the target in both Up and Down directions\n                        (scaleUp and scaleDown fields respectively).\n                      properties:\n                        scaleDown:\n                          description: scaleDown is scaling policy for scaling Down.\n                            If not set, the default value is to allow to scale down\n                            to minReplicas pods, with a 300 second stabilization window\n                            (i.e., the highest recommendation for the last 300sec\n                            is used).\n                          properties:\n                            policies:\n                              description: policies is a list of potential scaling\n                                polices which can be used during scaling. At least\n                                one policy must be specified, otherwise the HPAScalingRules\n                                will be discarded as invalid\n                              items:\n                                description: HPAScalingPolicy is a single policy which\n                                  must hold true for a specified past interval.\n                                properties:\n                                  periodSeconds:\n                                    description: PeriodSeconds specifies the window\n                                      of time for which the policy should hold true.\n                                      PeriodSeconds must be greater than zero and\n                                      less than or equal to 1800 (30 min).\n                                    format: int32\n                                    type: integer\n                                  type:\n                                    description: Type is used to specify the scaling\n                                      policy.\n                                    type: string\n                                  value:\n                                    description: Value contains the amount of change\n                                      which is permitted by the policy. It must be\n                                      greater than zero\n                                    format: int32\n                                    type: integer\n                                required:\n                                - periodSeconds\n                                - type\n                                - value\n                                type: object\n                              type: array\n                            selectPolicy:\n                              description: selectPolicy is used to specify which policy\n                                should be used. If not set, the default value MaxPolicySelect\n                                is used.\n                              type: string\n                            stabilizationWindowSeconds:\n                              description: 'StabilizationWindowSeconds is the number\n                                of seconds for which past recommendations should be\n                                considered while scaling up or scaling down. StabilizationWindowSeconds\n                                must be greater than or equal to zero and less than\n                                or equal to 3600 (one hour). If not set, use the default\n                                values: - For scale up: 0 (i.e. no stabilization is\n                                done). - For scale down: 300 (i.e. the stabilization\n                                window is 300 seconds long).'\n                              format: int32\n                              type: integer\n                          type: object\n                        scaleUp:\n                          description: 'scaleUp is scaling policy for scaling Up.\n                            If not set, the default value is the higher of:   * increase\n                            no more than 4 pods per 60 seconds   * double the number\n                            of pods per 60 seconds No stabilization is used.'\n                          properties:\n                            policies:\n                              description: policies is a list of potential scaling\n                                polices which can be used during scaling. At least\n                                one policy must be specified, otherwise the HPAScalingRules\n                                will be discarded as invalid\n                              items:\n                                description: HPAScalingPolicy is a single policy which\n                                  must hold true for a specified past interval.\n                                properties:\n                                  periodSeconds:\n                                    description: PeriodSeconds specifies the window\n                                      of time for which the policy should hold true.\n                                      PeriodSeconds must be greater than zero and\n                                      less than or equal to 1800 (30 min).\n                                    format: int32\n                                    type: integer\n                                  type:\n                                    description: Type is used to specify the scaling\n                                      policy.\n                                    type: string\n                                  value:\n                                    description: Value contains the amount of change\n                                      which is permitted by the policy. It must be\n                                      greater than zero\n                                    format: int32\n                                    type: integer\n                                required:\n                                - periodSeconds\n                                - type\n                                - value\n                                type: object\n                              type: array\n                            selectPolicy:\n                              description: selectPolicy is used to specify which policy\n                                should be used. If not set, the default value MaxPolicySelect\n                                is used.\n                              type: string\n                            stabilizationWindowSeconds:\n                              description: 'StabilizationWindowSeconds is the number\n                                of seconds for which past recommendations should be\n                                considered while scaling up or scaling down. StabilizationWindowSeconds\n                                must be greater than or equal to zero and less than\n                                or equal to 3600 (one hour). If not set, use the default\n                                values: - For scale up: 0 (i.e. no stabilization is\n                                done). - For scale down: 300 (i.e. the stabilization\n                                window is 300 seconds long).'\n                              format: int32\n                              type: integer\n                          type: object\n                      type: object\n                    resourceMetrics:\n                      items:\n                        description: ResourceMetricSource indicates how to scale on\n                          a resource metric known to Kubernetes, as specified in requests\n                          and limits, describing each pod in the current scale target\n                          (e.g. CPU or memory).  The values will be averaged together\n                          before being compared to the target.  Such metrics are built\n                          in to Kubernetes, and have special scaling options on top\n                          of those available to normal per-pod metrics using the \"pods\"\n                          source.  Only one \"target\" type should be set.\n                        properties:\n                          name:\n                            description: name is the name of the resource in question.\n                            type: string\n                          target:\n                            description: target specifies the target value for the\n                              given metric\n                            properties:\n                              averageUtilization:\n                                description: averageUtilization is the target value\n                                  of the average of the resource metric across all\n                                  relevant pods, represented as a percentage of the\n                                  requested value of the resource for the pods. Currently\n                                  only valid for Resource metric source type\n                                format: int32\n                                type: integer\n                              averageValue:\n                                anyOf:\n                                - type: integer\n                                - type: string\n                                description: averageValue is the target value of the\n                                  average of the metric across all relevant pods (as\n                                  a quantity)\n                                pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                x-kubernetes-int-or-string: true\n                              type:\n                                description: type represents whether the metric type\n                                  is Utilization, Value, or AverageValue\n                                type: string\n                              value:\n                                anyOf:\n                                - type: integer\n                                - type: string\n                                description: value is the target value of the metric\n                                  (as a quantity).\n                                pattern: ^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$\n                                x-kubernetes-int-or-string: true\n                            required:\n                            - type\n                            type: object\n                        required:\n                        - name\n                        - target\n                        type: object\n                      type: array\n                  type: object\n                restoreToOriginalReplicaCount:\n                  type: boolean\n              type: object\n            cooldownPeriod:\n              format: int32\n              type: integer\n            maxReplicaCount:\n              format: int32\n              type: integer\n            minReplicaCount:\n              format: int32\n              type: integer\n            pollingInterval:\n              format: int32\n              type: integer\n            scaleTargetRef:\n              description: ScaleTarget holds the a reference to the scale target Object\n              properties:\n                apiVersion:\n                  type: string\n                envSourceContainerName:\n                  type: string\n                kind:\n                  type: string\n                name:\n                  type: string\n              required:\n              - name\n              type: object\n            triggers:\n              items:\n                description: ScaleTriggers reference the scaler that will be used\n                properties:\n                  authenticationRef:\n                    description: ScaledObjectAuthRef points to the TriggerAuthentication\n                      object that is used to authenticate the scaler with the environment\n                    properties:\n                      name:\n                        type: string\n                    required:\n                    - name\n                    type: object\n                  metadata:\n                    additionalProperties:\n                      type: string\n                    type: object\n                  name:\n                    type: string\n                  type:\n                    type: string\n                required:\n                - metadata\n                - type\n                type: object\n              type: array\n          required:\n          - scaleTargetRef\n          - triggers\n          type: object\n        status:\n          description: ScaledObjectStatus is the status for a ScaledObject resource\n          properties:\n            conditions:\n              description: Conditions an array representation to store multiple Conditions\n              items:\n                description: Condition to store the condition state\n                properties:\n                  message:\n                    description: A human readable message indicating details about\n                      the transition.\n                    type: string\n                  reason:\n                    description: The reason for the condition's last transition.\n                    type: string\n                  status:\n                    description: Status of the condition, one of True, False, Unknown.\n                    type: string\n                  type:\n                    description: Type of condition\n                    type: string\n                required:\n                - status\n                - type\n                type: object\n              type: array\n            externalMetricNames:\n              items:\n                type: string\n              type: array\n            lastActiveTime:\n              format: date-time\n              type: string\n            originalReplicaCount:\n              format: int32\n              type: integer\n            scaleTargetGVKR:\n              description: GroupVersionKindResource provides unified structure for\n                schema.GroupVersionKind and Resource\n              properties:\n                group:\n                  type: string\n                kind:\n                  type: string\n                resource:\n                  type: string\n                version:\n                  type: string\n              required:\n              - group\n              - kind\n              - resource\n              - version\n              type: object\n            scaleTargetKind:\n              type: string\n          type: object\n      required:\n      - spec\n      type: object\n  version: v1alpha1\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: true\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n---\napiVersion: apiextensions.k8s.io/v1beta1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.3.0\n  creationTimestamp: null\n  labels:\n    app.kubernetes.io/part-of: keda-operator\n    app.kubernetes.io/version: 2.0.0-beta\n  name: triggerauthentications.keda.sh\nspec:\n  additionalPrinterColumns:\n  - JSONPath: .spec.podIdentity.provider\n    name: PodIdentity\n    type: string\n  - JSONPath: .spec.secretTargetRef[*].name\n    name: Secret\n    type: string\n  - JSONPath: .spec.env[*].name\n    name: Env\n    type: string\n  group: keda.sh\n  names:\n    kind: TriggerAuthentication\n    listKind: TriggerAuthenticationList\n    plural: triggerauthentications\n    shortNames:\n    - ta\n    - triggerauth\n    singular: triggerauthentication\n  scope: Namespaced\n  subresources: {}\n  validation:\n    openAPIV3Schema:\n      description: TriggerAuthentication defines how a trigger can authenticate\n      properties:\n        apiVersion:\n          description: 'APIVersion defines the versioned schema of this representation\n            of an object. Servers should convert recognized schemas to the latest\n            internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n          type: string\n        kind:\n          description: 'Kind is a string value representing the REST resource this\n            object represents. Servers may infer this from the endpoint the client\n            submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n          type: string\n        metadata:\n          type: object\n        spec:\n          description: TriggerAuthenticationSpec defines the various ways to authenticate\n          properties:\n            env:\n              items:\n                description: AuthEnvironment is used to authenticate using environment\n                  variables in the destination ScaleTarget spec\n                properties:\n                  containerName:\n                    type: string\n                  name:\n                    type: string\n                  parameter:\n                    type: string\n                required:\n                - name\n                - parameter\n                type: object\n              type: array\n            hashiCorpVault:\n              description: HashiCorpVault is used to authenticate using Hashicorp\n                Vault\n              properties:\n                address:\n                  type: string\n                authentication:\n                  description: VaultAuthentication contains the list of Hashicorp\n                    Vault authentication methods\n                  type: string\n                credential:\n                  description: Credential defines the Hashicorp Vault credentials\n                    depending on the authentication method\n                  properties:\n                    serviceAccount:\n                      type: string\n                    token:\n                      type: string\n                  type: object\n                mount:\n                  type: string\n                role:\n                  type: string\n                secrets:\n                  items:\n                    description: VaultSecret defines the mapping between the path\n                      of the secret in Vault to the parameter\n                    properties:\n                      key:\n                        type: string\n                      parameter:\n                        type: string\n                      path:\n                        type: string\n                    required:\n                    - key\n                    - parameter\n                    - path\n                    type: object\n                  type: array\n              required:\n              - address\n              - authentication\n              - secrets\n              type: object\n            podIdentity:\n              description: AuthPodIdentity allows users to select the platform native\n                identity mechanism\n              properties:\n                provider:\n                  description: PodIdentityProvider contains the list of providers\n                  type: string\n              required:\n              - provider\n              type: object\n            secretTargetRef:\n              items:\n                description: AuthSecretTargetRef is used to authenticate using a reference\n                  to a secret\n                properties:\n                  key:\n                    type: string\n                  name:\n                    type: string\n                  parameter:\n                    type: string\n                required:\n                - key\n                - name\n                - parameter\n                type: object\n              type: array\n          type: object\n      required:\n      - spec\n      type: object\n  version: v1alpha1\n  versions:\n  - name: v1alpha1\n    served: true\n    storage: true\nstatus:\n  acceptedNames:\n    kind: \"\"\n    plural: \"\"\n  conditions: []\n  storedVersions: []\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    app.kubernetes.io/name: keda-operator\n    app.kubernetes.io/part-of: keda-operator\n    app.kubernetes.io/version: 2.0.0-beta\n  name: keda-operator\n  namespace: keda\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app.kubernetes.io/name: keda-external-metrics-reader\n    app.kubernetes.io/part-of: keda-operator\n    app.kubernetes.io/version: 2.0.0-beta\n  name: keda-external-metrics-reader\nrules:\n- apiGroups:\n  - external.metrics.k8s.io\n  resources:\n  - '*'\n  verbs:\n  - '*'\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  creationTimestamp: null\n  labels:\n    app.kubernetes.io/name: keda-operator\n    app.kubernetes.io/part-of: keda-operator\n    app.kubernetes.io/version: 2.0.0-beta\n  name: keda-operator\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  - configmaps/status\n  - events\n  verbs:\n  - '*'\n- apiGroups:\n  - \"\"\n  resources:\n  - external\n  - pods\n  - secrets\n  - services\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - '*'\n  resources:\n  - '*'\n  verbs:\n  - get\n- apiGroups:\n  - '*'\n  resources:\n  - '*/scale'\n  verbs:\n  - '*'\n- apiGroups:\n  - autoscaling\n  resources:\n  - horizontalpodautoscalers\n  verbs:\n  - '*'\n- apiGroups:\n  - batch\n  resources:\n  - jobs\n  verbs:\n  - '*'\n- apiGroups:\n  - keda.sh\n  resources:\n  - scaledjobs\n  - scaledjobs/status\n  verbs:\n  - '*'\n- apiGroups:\n  - keda.sh\n  resources:\n  - scaledobjects\n  - scaledobjects/finalizers\n  - scaledobjects/status\n  verbs:\n  - '*'\n- apiGroups:\n  - keda.sh\n  resources:\n  - triggerauthentications\n  - triggerauthentications/status\n  verbs:\n  - '*'\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/name: keda-auth-reader\n    app.kubernetes.io/part-of: keda-operator\n    app.kubernetes.io/version: 2.0.0-beta\n  name: keda-auth-reader\n  namespace: keda\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: extension-apiserver-authentication-reader\nsubjects:\n- kind: ServiceAccount\n  name: keda-operator\n  namespace: keda\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/name: keda-hpa-controller-external-metrics\n    app.kubernetes.io/part-of: keda-operator\n    app.kubernetes.io/version: 2.0.0-beta\n  name: keda-hpa-controller-external-metrics\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: keda-external-metrics-reader\nsubjects:\n- kind: ServiceAccount\n  name: horizontal-pod-autoscaler\n  namespace: kube-system\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/name: keda-operator\n    app.kubernetes.io/part-of: keda-operator\n    app.kubernetes.io/version: 2.0.0-beta\n  name: keda-operator\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: keda-operator\nsubjects:\n- kind: ServiceAccount\n  name: keda-operator\n  namespace: keda\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  labels:\n    app.kubernetes.io/name: keda-system-auth-delegator\n    app.kubernetes.io/part-of: keda-operator\n    app.kubernetes.io/version: 2.0.0-beta\n  name: keda:system:auth-delegator\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: system:auth-delegator\nsubjects:\n- kind: ServiceAccount\n  name: keda-operator\n  namespace: keda\n---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    app.kubernetes.io/name: keda-metrics-apiserver\n    app.kubernetes.io/part-of: keda-operator\n    app.kubernetes.io/version: 2.0.0-beta\n  name: keda-metrics-apiserver\n  namespace: keda\nspec:\n  ports:\n  - name: https\n    port: 443\n    targetPort: 6443\n  - name: http\n    port: 80\n    targetPort: 8080\n  selector:\n    app: keda-metrics-apiserver\n    app.kubernetes.io/part-of: keda-operator\n    app.kubernetes.io/version: 2.0.0-beta\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app: keda-metrics-apiserver\n    app.kubernetes.io/name: keda-metrics-apiserver\n    app.kubernetes.io/part-of: keda-operator\n    app.kubernetes.io/version: 2.0.0-beta\n  name: keda-metrics-apiserver\n  namespace: keda\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: keda-metrics-apiserver\n      app.kubernetes.io/part-of: keda-operator\n      app.kubernetes.io/version: 2.0.0-beta\n  template:\n    metadata:\n      labels:\n        app: keda-metrics-apiserver\n        app.kubernetes.io/part-of: keda-operator\n        app.kubernetes.io/version: 2.0.0-beta\n      name: keda-metrics-apiserver\n    spec:\n      containers:\n      - args:\n        - /usr/local/bin/keda-adapter\n        - --secure-port=6443\n        - --logtostderr=true\n        - --v=4\n        env:\n        - name: WATCH_NAMESPACE\n          value: \"\"\n        image: docker.io/kedacore/keda-metrics-apiserver:2.0.0-beta\n        imagePullPolicy: Always\n        livenessProbe:\n          httpGet:\n            path: /healthz\n            port: 6443\n            scheme: HTTPS\n          initialDelaySeconds: 5\n        name: keda-metrics-apiserver\n        ports:\n        - containerPort: 6443\n          name: https\n        - containerPort: 8080\n          name: http\n        readinessProbe:\n          httpGet:\n            path: /readyz\n            port: 6443\n            scheme: HTTPS\n          initialDelaySeconds: 5\n        resources:\n          limits:\n            cpu: 1000m\n            memory: 1000Mi\n          requests:\n            cpu: 100m\n            memory: 100Mi\n        volumeMounts:\n        - mountPath: /tmp\n          name: temp-vol\n      nodeSelector:\n        beta.kubernetes.io/os: linux\n      serviceAccountName: keda-operator\n      volumes:\n      - emptyDir: {}\n        name: temp-vol\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app: keda-operator\n    app.kubernetes.io/component: operator\n    app.kubernetes.io/name: keda-operator\n    app.kubernetes.io/part-of: keda-operator\n    app.kubernetes.io/version: 2.0.0-beta\n  name: keda-operator\n  namespace: keda\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: keda-operator\n      app.kubernetes.io/part-of: keda-operator\n      app.kubernetes.io/version: 2.0.0-beta\n  template:\n    metadata:\n      labels:\n        app: keda-operator\n        app.kubernetes.io/part-of: keda-operator\n        app.kubernetes.io/version: 2.0.0-beta\n        name: keda-operator\n      name: keda-operator\n    spec:\n      containers:\n      - args:\n        - --enable-leader-election\n        - --zap-log-level=debug\n        - --zap-encoder=console\n        command:\n        - /keda\n        env:\n        - name: WATCH_NAMESPACE\n          value: \"\"\n        image: docker.io/kedacore/keda:2.0.0-beta\n        imagePullPolicy: Always\n        livenessProbe:\n          httpGet:\n            path: /healthz\n            port: 8081\n          initialDelaySeconds: 25\n        name: keda-operator\n        readinessProbe:\n          httpGet:\n            path: /readyz\n            port: 8081\n          initialDelaySeconds: 20\n        resources:\n          limits:\n            cpu: 1000m\n            memory: 1000Mi\n          requests:\n            cpu: 100m\n            memory: 100Mi\n      nodeSelector:\n        beta.kubernetes.io/os: linux\n      serviceAccountName: keda-operator\n      terminationGracePeriodSeconds: 10\n---\napiVersion: apiregistration.k8s.io/v1\nkind: APIService\nmetadata:\n  labels:\n    app.kubernetes.io/name: v1beta1.external.metrics.k8s.io\n    app.kubernetes.io/part-of: keda-operator\n    app.kubernetes.io/version: 2.0.0-beta\n  name: v1beta1.external.metrics.k8s.io\nspec:\n  group: external.metrics.k8s.io\n  groupPriorityMinimum: 100\n  insecureSkipTLSVerify: true\n  service:\n    name: keda-metrics-apiserver\n    namespace: keda\n  version: v1beta1\n  versionPriority: 100\n"
  },
  {
    "path": "autoscaling-on-queue/deployment/producer.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: autoscaling-producer\n  labels:\n    app: autoscaling-producer\nspec:\n  selector:\n    matchLabels:\n      app: autoscaling-producer\n  template:\n    metadata:\n      labels:\n        app: autoscaling-producer\n      annotations:\n        dapr.io/enabled: \"true\"\n        dapr.io/app-id: \"autoscaling-producer\"\n        dapr.io/app-protocol: \"grpc\"\n        dapr.io/app-port: \"60034\"\n        dapr.io/log-level: \"debug\"\n        dapr.io/log-as-json: \"true\"\n    spec:\n      containers:\n      - name: service\n        image: mchmarny/autoscaling-producer:v0.11.1\n        ports:\n        - containerPort: 60034\n        env:\n        - name: PUBSUB_NAME\n          value: autoscaling-pubsub\n        - name: TOPIC_NAME\n          value: metric\n        - name: NUMBER_OF_PUBLISHERS\n          value: \"2\"\n        - name: PUBLISHERS_FREQ\n          value: \"100ms\"\n        - name: PUBLISHERS_DELAY\n          value: \"10s\"\n        - name: LOG_FREQ\n          value: \"3s\""
  },
  {
    "path": "autoscaling-on-queue/deployment/subscriber-scaler.yaml",
    "content": "apiVersion: keda.sh/v1alpha1\nkind: ScaledObject\nmetadata:\n  name: subscriber-scaler\nspec:\n  scaleTargetRef:\n    name: autoscaling-subscriber\n  pollingInterval: 15\n  minReplicaCount: 0\n  maxReplicaCount: 10\n  cooldownPeriod: 30\n  triggers:\n  - type: kafka\n    metadata:\n      topic: metric\n      bootstrapServers: kafka-cp-kafka.kafka.svc.cluster.local:9092\n      consumerGroup: autoscaling-subscriber\n      lagThreshold: \"3\""
  },
  {
    "path": "autoscaling-on-queue/deployment/subscriber.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: autoscaling-subscriber\n  labels:\n    app: autoscaling-subscriber\nspec:\n  selector:\n    matchLabels:\n      app: autoscaling-subscriber\n  template:\n    metadata:\n      labels:\n        app: autoscaling-subscriber\n      annotations:\n        dapr.io/enabled: \"true\"\n        dapr.io/app-id: \"autoscaling-subscriber\"\n        dapr.io/app-protocol: \"grpc\"\n        dapr.io/app-port: \"60033\"\n        dapr.io/log-level: \"debug\"\n        dapr.io/log-as-json: \"true\"\n    spec:\n      containers:\n      - name: service\n        image: mchmarny/autoscaling-subscriber:v0.11.1\n        ports:\n        - containerPort: 60033\n        env:\n        - name: PUBSUB_NAME\n          value: autoscaling-pubsub\n        - name: TOPIC_NAME\n          value: metric\n        - name: PROCESS_DURATION\n          value: \"300ms\""
  },
  {
    "path": "autoscaling-on-queue/producer/Dockerfile",
    "content": "FROM golang:1.15.0 as builder\n\nWORKDIR /src/\nCOPY . /src/\n\nENV GO111MODULE=on\n\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \\\n    go build -a -tags netgo -mod vendor -o ./producer .\n\nFROM gcr.io/distroless/static:nonroot\nCOPY --from=builder /src/producer .\n\nENTRYPOINT [\"./producer\"]\n"
  },
  {
    "path": "autoscaling-on-queue/producer/Makefile",
    "content": "RELEASE_VERSION  =v0.11.1\nSERVICE_NAME    ?=autoscaling-producer\nDOCKER_USERNAME ?=$(DOCKER_USER)\n\n.PHONY: all\nall: help\n\n.PHONY: tidy\ntidy: ## Updates the go modules and vendors all dependencies \n\tgo mod tidy\n\tgo mod vendor\n\n.PHONY: run\nrun: tidy ## Runs uncompiled code\n\tNUMBER_OF_PUBLISHERS=3 PUBLISH_TO_CONSOLE=true go run main.go\n\n.PHONY: image\nimage: tidy ## Builds and publishes docker image \n\tdocker build -t \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\" .\n\tdocker push \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\"\n\n.PHONY: lint\nlint: ## Lints the entire project \n\tgolangci-lint run --timeout=3m\n\n.PHONY: tag\ntag: ## Creates release tag \n\tgit tag $(RELEASE_VERSION)\n\tgit push origin $(RELEASE_VERSION)\n\t\t\n.PHONY: clean\nclean: ## Cleans up generated files \n\tgo clean\n\trm -fr ./bin\n\trm -fr ./vendor\n\n.PHONY: help\nhelp: ## Display available commands\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \\\n\t\t'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n"
  },
  {
    "path": "autoscaling-on-queue/producer/go.mod",
    "content": "module github.com/mchmarny/dapr-demos/autoscaling-on-queue/producer\n\ngo 1.15\n\nrequire (\n\tgithub.com/dapr/go-sdk v0.11.0\n\tgithub.com/google/uuid v1.1.2\n\tgolang.org/x/net v0.0.0-20200930145003-4acb6c075d10 // indirect\n\tgolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f // indirect\n\tgoogle.golang.org/genproto v0.0.0-20201002142447-3860012362da // indirect\n)\n"
  },
  {
    "path": "autoscaling-on-queue/producer/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo=\ngithub.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200930145003-4acb6c075d10 h1:YfxMZzv3PjGonQYNUaeU2+DhAdqOxerQ30JFB6WgAXo=\ngolang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201002142447-3860012362da h1:DTQYk4u7nICKkkVZsBv0/0po0ChISxAJ5CTAfUhO0PQ=\ngoogle.golang.org/genproto v0.0.0-20201002142447-3860012362da/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=\ngoogle.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "autoscaling-on-queue/producer/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log\"\n\t\"math/rand\"\n\t\"os\"\n\t\"os/signal\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"syscall\"\n\t\"time\"\n\n\tdapr \"github.com/dapr/go-sdk/client\"\n\tdaprd \"github.com/dapr/go-sdk/service/grpc\"\n\t\"github.com/google/uuid\"\n)\n\nconst (\n\tchars = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\"\n)\n\nvar (\n\tlogger           = log.New(os.Stdout, \"\", 0)\n\tserviceAddress   = getEnvVar(\"ADDRESS\", \":60034\")\n\tpubSubName       = getEnvVar(\"PUBSUB_NAME\", \"autoscaling-pubsub\")\n\ttopicName        = getEnvVar(\"TOPIC_NAME\", \"metrics\")\n\tnumOfPublishers  = getEnvIntOrFail(\"NUMBER_OF_PUBLISHERS\", \"1\")\n\tpublishFrequency = getEnvDurationOrFail(\"PUBLISHERS_FREQ\", \"1s\")\n\tpublishDelay     = getEnvDurationOrFail(\"PUBLISHERS_DELAY\", \"10s\")\n\tlogFrequency     = getEnvDurationOrFail(\"LOG_FREQ\", \"3s\")\n\tpublishToConsole = getEnvBoolOrFail(\"PUBLISH_TO_CONSOLE\", \"false\")\n\n\tclient dapr.Client\n)\n\nfunc main() {\n\tif numOfPublishers < 1 {\n\t\tnumOfPublishers = 1\n\t}\n\tlogger.Printf(\"subscription name: %s\", pubSubName)\n\tlogger.Printf(\"topic name: %s\", topicName)\n\tlogger.Printf(\"number of publishers: %d\", numOfPublishers)\n\tlogger.Printf(\"publish frequency: %v\", publishFrequency)\n\tlogger.Printf(\"log frequency: %v\", logFrequency)\n\tlogger.Printf(\"publish delay: %v\", publishDelay)\n\n\t// create Dapr service\n\ts, err := daprd.NewService(serviceAddress)\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to start the server: %v\", err)\n\t}\n\n\tc, err := dapr.NewClient()\n\tif err != nil {\n\t\tlog.Fatalf(\"error creating Dapr client: %v\", err)\n\t}\n\tclient = c\n\tdefer client.Close()\n\n\t// handle signals\n\tstop := make(chan os.Signal)\n\tsignal.Notify(stop, syscall.SIGTERM, syscall.SIGINT)\n\n\tresultCh := make(chan bool, 100)\n\tstopCh := make(chan struct{})\n\n\tgo func() {\n\t\t<-stop\n\t\tclose(stopCh)\n\t}()\n\n\t// print results\n\tgo monitor(resultCh, stopCh)\n\n\t// start producing\n\tfor i := 1; i <= numOfPublishers; i++ {\n\t\tgo publish(i, resultCh, stopCh)\n\t}\n\n\t// start the server to handle incoming events\n\tif err := s.Start(); err != nil {\n\t\tlog.Fatalf(\"server error: %v\", err)\n\t}\n\n}\n\nfunc monitor(resultCh <-chan bool, stopCh <-chan struct{}) {\n\tvar mux sync.Mutex\n\tvar successCounter int64 = 0\n\tvar errorCounter int64 = 0\n\tstartTime := time.Now()\n\ttickerCh := time.NewTicker(logFrequency).C\n\tfor {\n\t\tselect {\n\t\tcase r := <-resultCh:\n\t\t\tmux.Lock()\n\t\t\tif r {\n\t\t\t\tsuccessCounter++\n\t\t\t} else {\n\t\t\t\terrorCounter++\n\t\t\t}\n\t\t\tmux.Unlock()\n\t\tcase <-tickerCh:\n\t\t\tvar avg float64 = 0\n\t\t\tif successCounter > 0 {\n\t\t\t\tavg = float64(successCounter) / time.Since(startTime).Seconds()\n\t\t\t}\n\t\t\tlogger.Printf(\"%10d published, %3.0f/sec, %3d errors\", successCounter, avg, errorCounter)\n\t\tcase <-stopCh:\n\t\t\tos.Exit(0)\n\t\t}\n\t}\n}\n\nfunc publish(index int, resultCh chan<- bool, stopCh <-chan struct{}) {\n\tdelayCh := time.NewTicker(publishDelay).C\n\t<-delayCh\n\n\ttickerCh := time.NewTicker(publishFrequency).C\n\tfor {\n\t\tselect {\n\t\tcase <-stopCh:\n\t\t\treturn\n\t\tcase <-tickerCh:\n\t\t\td := getEventData(index)\n\t\t\tif publishToConsole {\n\t\t\t\tlogger.Printf(\"%s\", d)\n\t\t\t\tresultCh <- true\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tresultCh <- client.PublishEvent(context.Background(), pubSubName, topicName, d) == nil\n\t\t}\n\t}\n}\n\nfunc getEventData(index int) []byte {\n\tr := requestContent{\n\t\tID:   fmt.Sprintf(\"p%d-%s\", index, uuid.New().String()),\n\t\tData: []byte(getData(256)),\n\t\tTime: time.Now().UTC().Unix(),\n\t}\n\n\t// hash the entire message\n\tinSha := sha256.Sum256(r.Data)\n\tr.Sha = string(inSha[:])\n\n\tb, err := json.Marshal(r)\n\tif err != nil {\n\t\tlogger.Fatalf(\"error generating request: %v\", err)\n\t}\n\treturn b\n}\n\nfunc getData(length int) string {\n\tseededRand := rand.New(rand.NewSource(time.Now().UnixNano()))\n\tb := make([]byte, length)\n\tfor i := range b {\n\t\tb[i] = chars[seededRand.Intn(len(chars))]\n\t}\n\treturn string(b)\n}\n\ntype requestContent struct {\n\tID   string `json:\"id\"`\n\tData []byte `json:\"data\"`\n\tSha  string `json:\"sha\"`\n\tTime int64  `json:\"time\"`\n}\n\nfunc getEnvVar(key, fallbackValue string) string {\n\tif val, ok := os.LookupEnv(key); ok {\n\t\treturn strings.TrimSpace(val)\n\t}\n\treturn fallbackValue\n}\n\nfunc getEnvIntOrFail(key, fallbackValue string) int {\n\ts := getEnvVar(key, fallbackValue)\n\tv, err := strconv.Atoi(s)\n\tif err != nil {\n\t\tlogger.Fatalf(\"invalid number variable: %s - %v\", s, err)\n\t}\n\treturn v\n}\n\nfunc getEnvDurationOrFail(key, fallbackValue string) time.Duration {\n\ts := getEnvVar(key, fallbackValue)\n\tv, err := time.ParseDuration(s)\n\tif err != nil {\n\t\tlogger.Fatalf(\"invalid duration variable: %s - %v\", s, err)\n\t}\n\treturn v\n}\n\nfunc getEnvBoolOrFail(key, fallbackValue string) bool {\n\ts := getEnvVar(key, fallbackValue)\n\tv, err := strconv.ParseBool(s)\n\tif err != nil {\n\t\tlogger.Fatalf(\"invalid bool variable: %s - %v\", s, err)\n\t}\n\treturn v\n}\n"
  },
  {
    "path": "autoscaling-on-queue/subscriber/Dockerfile",
    "content": "FROM golang:1.15.0 as builder\n\nWORKDIR /src/\nCOPY . /src/\n\nENV GO111MODULE=on\n\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \\\n    go build -a -tags netgo -mod vendor -o ./service .\n\nFROM gcr.io/distroless/static:nonroot\nCOPY --from=builder /src/service .\n\nENTRYPOINT [\"./service\"]\n"
  },
  {
    "path": "autoscaling-on-queue/subscriber/Makefile",
    "content": "RELEASE_VERSION  =v0.11.1\nSERVICE_NAME    ?=autoscaling-subscriber\nDOCKER_USERNAME ?=$(DOCKER_USER)\n\n.PHONY: all\nall: help\n\n.PHONY: tidy\ntidy: ## Updates the go modules and vendors all dependencies \n\tgo mod tidy\n\tgo mod vendor\n\n.PHONY: run\nrun: tidy ## Runs uncompiled code in Dapr\n\tdapr run \\\n      --app-id $(SERVICE_NAME) \\\n      --app-port 60022 \\\n      --app-protocol grpc \\\n      --components-path ./config \\\n      --log-level debug \\\n      go run main.go\t\t\n\n.PHONY: image\nimage: tidy ## Builds and publishes docker image \n\tdocker build -t \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\" .\n\tdocker push \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\"\n\n.PHONY: deploy\ndeploy: ## Deploys prebuild image to k8s using currently selected context \n\tkubectl apply -f k8s/binding.yaml\n\tkubectl apply -f k8s/deployment.yaml\n\tkubectl rollout restart deployment/$(SERVICE_NAME)\n\tkubectl rollout status deployment/$(SERVICE_NAME)\n\n.PHONY: autoscale\nautoscale: ## Applies Keda to the deployed service \n\tkubectl apply -f k8s/keda.yaml\n\n.PHONY: scale\nscale: ## Scales the subscriber manually \n\t# kubectl autoscale deployment autoscaling-subscriber --min=1 --max=10  \n\tkubectl scale deployment/$(SERVICE_NAME) --replicas=10\n\n.PHONY: kafka-port\nkafka-port: ## Forwards cluster Kafka port locally\n\tkubectl port-forward svc/kafka -n kafka 9092 &\n\t@echo use 'pkill kubectl -9' to stop \n\n.PHONY: kafka-portless\nkafka-portless: ## Stops forwarding cluster Kafka port locally\n\t@echo use 'pkill kubectl -9' to stop \n\n.PHONY: lint\nlint: ## Lints the entire project \n\tgolangci-lint run --timeout=3m\n\n.PHONY: tag\ntag: ## Creates release tag \n\tgit tag $(RELEASE_VERSION)\n\tgit push origin $(RELEASE_VERSION)\n\n.PHONY: clean\nclean: ## Cleans up generated files \n\tgo clean\n\trm -fr ./bin\n\trm -fr ./vendor\n\n.PHONY: help\nhelp: ## Display available commands\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \\\n\t\t'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n"
  },
  {
    "path": "autoscaling-on-queue/subscriber/go.mod",
    "content": "module github.com/mchmarny/dapr-demos/autoscaling-on-queue/subscriber\n\ngo 1.15\n\nrequire (\n\tgithub.com/dapr/go-sdk v0.11.0\n\tgithub.com/pkg/errors v0.9.1\n\tgolang.org/x/net v0.0.0-20200930145003-4acb6c075d10 // indirect\n\tgolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f // indirect\n\tgoogle.golang.org/genproto v0.0.0-20201002142447-3860012362da // indirect\n)\n"
  },
  {
    "path": "autoscaling-on-queue/subscriber/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo=\ngithub.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=\ngolang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200930145003-4acb6c075d10 h1:YfxMZzv3PjGonQYNUaeU2+DhAdqOxerQ30JFB6WgAXo=\ngolang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 h1:4X356008q5SA3YXu8PiRap39KFmy4Lf6sGlceJKZQsU=\ngolang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0 h1:uslsjIdqvZYANxSBQjTI47vZfwMaTN3mLELkMnMIY/A=\ngoogle.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201002142447-3860012362da h1:DTQYk4u7nICKkkVZsBv0/0po0ChISxAJ5CTAfUhO0PQ=\ngoogle.golang.org/genproto v0.0.0-20201002142447-3860012362da/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=\ngoogle.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "autoscaling-on-queue/subscriber/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"os/signal\"\n\t\"sync\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/dapr/go-sdk/service/common\"\n\tdaprd \"github.com/dapr/go-sdk/service/grpc\"\n\t\"github.com/pkg/errors\"\n)\n\nconst (\n\tprimeStateKey = \"high-prime\"\n)\n\nvar (\n\tlogger     = log.New(os.Stdout, \"\", 0)\n\treqProcDur time.Duration\n\n\taddress         = getEnvVar(\"ADDRESS\", \":60033\")\n\tprocessDuration = getEnvVar(\"PROCESS_DURATION\", \"500ms\")\n\tpubSubName      = getEnvVar(\"PUBSUB_NAME\", \"autoscaling-pubsub\")\n\ttopicName       = getEnvVar(\"TOPIC_NAME\", \"metrics\")\n)\n\nfunc main() {\n\t// Dapr service\n\ts, err := daprd.NewService(address)\n\tif err != nil {\n\t\tlogger.Fatalf(\"failed to start the service: %v\", err)\n\t}\n\n\td, err := time.ParseDuration(processDuration)\n\tif err != nil {\n\t\tlogger.Fatalf(\"invalid parameter (PROCESS_DURATION) must be a duration): %s - %v\", processDuration, err)\n\t}\n\treqProcDur = d\n\n\tvar mux sync.Mutex\n\tvar successCount int64 = 1\n\tvar errorCount int64 = 0\n\n\tresultCh := make(chan bool)\n\tstartTime := time.Now()\n\n\tgo func() {\n\t\ttickerCh := time.NewTicker(5 * time.Second).C\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase r := <-resultCh:\n\t\t\t\tmux.Lock()\n\t\t\t\tif r {\n\t\t\t\t\tsuccessCount++\n\t\t\t\t} else {\n\t\t\t\t\terrorCount++\n\t\t\t\t}\n\t\t\t\tmux.Unlock()\n\t\t\tcase <-tickerCh:\n\t\t\t\tvar avg float64 = 0\n\t\t\t\tif successCount > 0 {\n\t\t\t\t\tavg = float64(successCount) / time.Since(startTime).Seconds()\n\t\t\t\t}\n\t\t\t\tlogger.Printf(\"received: %10d, %3d errors - avg %3.0f/sec\", successCount, errorCount, avg)\n\t\t\t}\n\t\t}\n\t}()\n\n\t// define subscription\n\tsubscription := &common.Subscription{\n\t\tPubsubName: pubSubName,\n\t\tTopic:      topicName,\n\t}\n\n\t// subscribe\n\tif err := s.AddTopicEventHandler(subscription, func(ctx context.Context, e *common.TopicEvent) (retry bool, err error) {\n\t\tif err := processRequest(ctx, e.Data); err != nil {\n\t\t\tlogger.Printf(\"error processing request: %v\", err)\n\t\t\tresultCh <- false\n\t\t\treturn true, errors.Wrap(err, \"error processing request\")\n\t\t}\n\t\tresultCh <- true\n\t\treturn false, nil\n\t}); err != nil {\n\t\tlogger.Fatalf(\"error adding topic subscription: %v\", err)\n\t}\n\n\t// handle signals\n\tdone := make(chan os.Signal, 1)\n\tsignal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)\n\n\t// Start\n\tgo func() {\n\t\tif err := s.Start(); err != nil && err != http.ErrServerClosed {\n\t\t\tlogger.Fatalf(\"error starting service: %v\", err)\n\t\t}\n\t}()\n\n\t// Finish\n\t<-done\n}\n\n// does some computing to keep the process busy organically\nfunc processRequest(ctx context.Context, in interface{}) error {\n\ttickerCh := time.NewTicker(reqProcDur).C\n\t<-tickerCh\n\treturn nil\n}\n\nfunc getEnvVar(key, fallbackValue string) string {\n\tif val, ok := os.LookupEnv(key); ok {\n\t\treturn strings.TrimSpace(val)\n\t}\n\treturn fallbackValue\n}\n"
  },
  {
    "path": "component-api/README.md",
    "content": "# Use of Dapr as a component API Server\n\nThis demo users Dapr instance with API token authentication to show the use of Dapr as a API server for any of its 70+ components. To illustrate, this demo will show two use-cases:\n\n* Simple note management using Redis state store\n* Sending email using Sendgrid output binding\n* Querying tweets using Twitter bi-directional binding\n\n## Setup \n\n### State Component \n\nCreate a `mongo-secret`\n\n```shell\nkubectl create secret generic redis-secret --from-literal=password=\"\"\n```\n\nDeploy component and [restart gateway](#ingress-gateway)\n\n```shell\nkubectl apply -f config/state.yaml\n```\n\n### Email Component \n\nCreate a `email-secret`\n\n```shell\nkubectl create secret generic email-secret --from-literal=apiKey=\"\"\n```\n\nDeploy component and [restart gateway](#ingress-gateway)\n\n```shell\nkubectl apply -f config/email.yaml\n```\n\n### Twitter Component\n\nCreate a `twitter-secret`\n\n```shell\nkubectl create secret generic twitter-secret \\\n  --from-literal=consumerKey=\"\" \\\n  --from-literal=consumerSecret=\"\" \\\n  --from-literal=accessToken=\"\" \\\n  --from-literal=accessSecret=\"\"\n```\n\nDeploy component and [restart gateway](#ingress-gateway)\n\n```shell\nkubectl apply -f config/twitter.yaml\n```\n\n### Ingress Gateway\n\nEnsure all the gateway instances are aware of new components\n\n```shell\nkubectl rollout restart deployment/nginx-ingress-nginx-controller\nkubectl rollout status deployment/nginx-ingress-nginx-controller\n```\n\n## Usage\n\nTo use any of the components you will need the Dapr API token: \n\n```shell\nexport API_TOKEN=$(kubectl get secret dapr-api-token -o jsonpath=\"{.data.token}\" | base64 --decode)\n```\n\n### State \n\nAnd POST it to the Dapr API to save your note:\n\n```shell\ncurl -X POST \\\n     -d '[{ \"key\": \"1\", \"value\": \"This is my first note\" }]' \\\n     -H \"Content-Type: application/json\" \\\n     -H \"dapr-api-token: ${API_TOKEN}\" \\\n     https://api.cloudylabs.dev/v1.0/state/note-store\n```\n\nRetrieve the saved note:\n\n```shell\ncurl -X GET \\\n     -H \"Content-Type: application/json\" \\\n     -H \"dapr-api-token: ${API_TOKEN}\" \\\n     https://api.cloudylabs.dev/v1.0/state/note-store/1\n```\n\nAnd now delete the note:\n\n```shell\ncurl -X DELETE \\\n     -H \"Content-Type: application/json\" \\\n     -H \"dapr-api-token: ${API_TOKEN}\" \\\n     https://api.cloudylabs.dev/v1.0/state/note-store/1\n```\n\n> For brevity of the example this demo shows only the save, get, delete commands but the Dapr API also includes transactional operations for save and bulk operations for get as well. \n\n\n### Email \n\nTo send email, first edit the [sample email](./sample/email.json) file: \n\n```json\n{\n    \"operation\": \"create\",\n    \"metadata\": {\n        \"emailTo\": \"daprdemo@chmarny.com\",\n        \"subject\": \"Dapr Demo\"\n    },\n    \"data\": \"<h1>Greetings</h1><p>Hi</p>\"\n}\n```\n\nAnd POST it to the Dapr API:\n\n```shell\ncurl -d @./sample/email.json \\\n     -H \"Content-Type: application/json\" \\\n     -H \"dapr-api-token: ${API_TOKEN}\" \\\n     \"https://api.cloudylabs.dev/v1.0/bindings/send-email\"\n```\n\n### Twitter \n\nTo query the last 100 tweets for particular query, first edit the [sample query](./sample/twitter.json) file:\n\n```json\n{\n    \"operation\": \"get\",\n    \"metadata\": {\n        \"query\": \"dapr AND serverless\",\n        \"lang\": \"en\",\n        \"result\": \"recent\"        \n    }\n}\n```\n\nMetadata parameters:\n\n* `query` - can be any valid Twitter query (supports `AND`, `OR` `BUT NOT`, `FROM`, `TO`, `#`, `@`...)\n* `lang` - (optional) is the [ISO 639-1](https://meta.wikimedia.org/wiki/Template:List_of_language_names_ordered_by_code) language code\n* `result` - (optional) is one of:\n  * `mixed` - include both popular and real time results in the response\n  * `recent` - return only the most recent results in the response\n  * `popular` - return only the most popular results in the response\n* `since_id` - (optional) the not inclusive tweet ID query should start from \n\nAnd POST it to the Dapr API:\n\n```shell\ncurl -d @./sample/twitter.json \\\n     -H \"Content-Type: application/json\" \\\n     -H \"dapr-api-token: ${API_TOKEN}\" \\\n     \"https://api.cloudylabs.dev/v1.0/bindings/query-twitter\"\n```\n\nAnd if you have the command-line JSON processor [jq](https://shapeshed.com/jq-json/),  you can format the API results. For example, this will display only the ID, Author, and Text of each tweet as a new JSON object:\n\n```shell\ncurl -d @./sample/twitter.json \\\n     -H \"Content-Type: application/json\" \\\n     -H \"dapr-api-token: ${API_TOKEN}\" \\\n     \"https://api.cloudylabs.dev/v1.0/bindings/query-twitter\" \\\n     | jq \".[] | { id: .id_str, user: .user.screen_name, text: .text}\"\n```\n\nThe result\n\n```shell\n{\n  \"id\": \"1298546227211055109\",\n  \"user\": \"markgossa\",\n  \"text\": \"What a blast! @AzureFunctions Live of August was fully packed with news (new extension bundle, Dapr extension)\"\n}\n{\n  \"id\": \"1298181483547357184\",\n  \"user\": \"ysakashita3\",\n  \"text\": \"I submitted a blog post to https://t.co/DXGTgtC4Xc. 'Serverless plugin': #KEDA for scaling down your containers\"\n}\n```\n\n## Disclaimer\n\nThis is my personal project and it does not represent my employer. While I do my best to ensure that everything works, I take no responsibility for issues caused by this code.\n\n## License\n\nThis software is released under the [MIT](../LICENSE)\n"
  },
  {
    "path": "component-api/config/email.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: send-email\nspec:\n  type: bindings.twilio.sendgrid\n  metadata:\n  - name: emailFrom\n    value: \"demo@thingz.io\"\n  - name: apiKey\n    secretKeyRef:\n      name: email-secret\n      key: apiKey\nscopes:\n- nginx-ingress"
  },
  {
    "path": "component-api/config/state.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: note-store\nspec:\n  type: state.redis\n  metadata:\n  - name: redisHost\n    value: redis-master.redis.svc.cluster.local:6379\n  - name: redisPassword\n    secretKeyRef:\n      name: redis-secret\n      key: password\nscopes:\n- nginx-ingress"
  },
  {
    "path": "component-api/config/twitter.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: query-twitter\nspec:\n  type: bindings.twitter\n  metadata:\n  - name: consumerKey\n    secretKeyRef:\n      name: twitter-secret\n      key: consumerKey\n  - name: consumerSecret\n    secretKeyRef:\n      name: twitter-secret\n      key: consumerSecret\n  - name: accessToken\n    secretKeyRef:\n      name: twitter-secret\n      key: accessToken\n  - name: accessSecret\n    secretKeyRef:\n      name: twitter-secret\n      key: accessSecret\nscopes:\n- nginx-ingress"
  },
  {
    "path": "component-api/sample/email.json",
    "content": "{\n    \"operation\": \"create\",\n    \"metadata\": {\n        \"emailTo\": \"daprdemo@chmarny.com\",\n        \"subject\": \"Dapr Demo\"\n    },\n    \"data\": \"<h1>Greetings:</h1><p>Hi</p>\"\n}"
  },
  {
    "path": "component-api/sample/twitter.json",
    "content": "{\n    \"operation\": \"get\",\n    \"metadata\": {\n        \"query\": \"dapr AND serverless\",\n        \"lang\": \"en\",\n        \"result\": \"recent\"        \n    }\n}"
  },
  {
    "path": "cron-binding/Dockerfile",
    "content": "FROM golang:1.15.0 as builder\n\nWORKDIR /src/\nCOPY . /src/\n\nENV GO111MODULE=on\n\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \\\n    go build -a -tags netgo -mod vendor -o ./service .\n\nFROM gcr.io/distroless/static:nonroot\nCOPY --from=builder /src/service .\n\nENTRYPOINT [\"./service\"]\n"
  },
  {
    "path": "cron-binding/Makefile",
    "content": "RELEASE_VERSION  =v0.11.1\nSERVICE_NAME    ?=cron-binding-demo\nDOCKER_USERNAME ?=$(DOCKER_USER)\n\n.PHONY: help tidy build run image lint tag clean\nall: help\n\ntidy: ## Updates the go modules and vendors all dependencies \n\tgo mod tidy\n\tgo mod vendor\n\ntest: tidy ## Tests the entire project \n\tgo test -count=1 -race ./...\n\nbuild: tidy ## Builds local release binary\n\tCGO_ENABLED=0 go build -a -tags netgo -mod vendor -o bin/$(SERVICE_NAME) .\n\ndebug: ## Runs uncompiled code it in Dapr in debug mode\n\tdapr run \\\n\t\t--app-id $(SERVICE_NAME) \\\n        --app-port 8080 \\\n        --app-protocol http \\\n        --components-path ./config \\\n        go run main.go\n\nrun: build ## Builds binary and runs it in Dapr\n\tdapr run \\\n\t\t--app-id $(SERVICE_NAME) \\\n\t    --app-port 8080 \\\n        --app-protocol http \\\n\t    --components-path ./config \\\n        bin/$(SERVICE_NAME) \n\nimage: tidy ## Builds and publish docker image \n\tdocker build -t \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\" .\n\tdocker push \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\"\n\ndeploy: ## Deploys prebuild image to k8s using currently selected context \n\tkubectl apply -f k8s/component.yaml\n\tkubectl apply -f k8s/deployment.yaml\n\tkubectl rollout restart deployment/cron-binding-demo\n\tkubectl rollout status deployment/cron-binding-demo\n\nlint: ## Lints the entire project \n\tgolangci-lint run --timeout=3m\n\ntag: ## Creates release tag \n\tgit tag $(RELEASE_VERSION)\n\tgit push origin $(RELEASE_VERSION)\n\nclean: ## Cleans up generated files \n\tgo clean\n\trm -fr ./bin\n\trm -fr ./vendor\n\nhelp: ## Display available commands\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \\\n\t\t'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n"
  },
  {
    "path": "cron-binding/README.md",
    "content": "# cron-binding\n\n## Binding\n\n```yaml\napiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: run\nspec:\n  type: bindings.cron\n  metadata:\n  - name: schedule\n    value: \"@every 3s\"\n```\n\nFor more information about this binding see the [Dapr docs](https://github.com/dapr/docs/blob/master/reference/specs/bindings/cron.md)\n\n## Run \n\nDapr cron binding demo in `go`. To use run it, first start the service\n\n```shell\ndapr run --app-id cron-binding-demo \\\n\t    --protocol http \\\n\t    --app-port 8080 \\\n\t    --components-path ./config \\\n\t    go run *.go\n```\n\n\n## Deploy\n\nDeploy and wait for the pod to be ready \n\n```shell\nkubectl apply -f k8s/component.yaml\nkubectl apply -f k8s/deployment.yaml\nkubectl rollout status deployment/cron-binding-demo\n```\n\nIf you have changed an existing component, make sure to reload the deployment and wait until the new version is ready\n\n```shell\nkubectl rollout restart deployment/cron-binding-demo\nkubectl rollout status deployment/cron-binding-demo\n```\n\nFollow logs to view schedule firing \n\n```shell\nkubectl logs -l app=cron-binding-demo -c daprd -f\n```\n\nDepending on the frequency you used there may not be an entry right away but you should see something similar to this\n\n```json\n{\n  \"app_id\":\"cron-binding-demo\",\n  \"instance\":\"cron-binding-demo-6c88dbb467-j54br\",\n  \"level\":\"debug\",\n  \"msg\":\"next run: 59m59.629538771s\",\n  \"scope\":\"dapr.contrib\",\n  \"time\":\"2020-08-31T13:08:34.37049343Z\",\n  \"type\":\"log\",\n  \"ver\":\"0.10.0\"\n}\n```\n\n## Disclaimer\n\nThis is my personal project and it does not represent my employer. While I do my best to ensure that everything works, I take no responsibility for issues caused by this code.\n\n## License\n\nThis software is released under the [MIT](../LICENSE)\n\n\n\n"
  },
  {
    "path": "cron-binding/config/cron.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: run\nspec:\n  type: bindings.cron\n  metadata:\n  - name: schedule\n    value: \"@every 3s\""
  },
  {
    "path": "cron-binding/go.mod",
    "content": "module github.com/mchmarny/dapr-demos/cron-binding\n\ngo 1.15\n\nrequire github.com/dapr/go-sdk v0.11.0\n"
  },
  {
    "path": "cron-binding/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo=\ngithub.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "cron-binding/k8s/component.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: schedule\nspec:\n  type: bindings.cron\n  metadata:\n  - name: schedule\n    value: \"@every 1h\"\nscopes:\n- cron-binding-demo"
  },
  {
    "path": "cron-binding/k8s/deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: cron-binding-demo\n  labels:\n    app: cron-binding-demo\n    demo: cron\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: cron-binding-demo\n  template:\n    metadata:\n      labels:\n        app: cron-binding-demo\n        demo: cron\n      annotations:\n        dapr.io/enabled: \"true\"\n        dapr.io/app-id: \"cron-binding-demo\"\n        dapr.io/app-protocol: \"http\"\n        dapr.io/app-port: \"8080\"\n        dapr.io/config: \"tracing\"\n        dapr.io/log-as-json: \"true\"\n        dapr.io/log-level: \"debug\"\n    spec:\n      containers:\n      - name: service\n        image: mchmarny/cron-binding-demo:v0.11.1\n        ports:\n        - containerPort: 8080\n        env:\n        - name: ADDRESS\n          value: \":8080\""
  },
  {
    "path": "cron-binding/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"log\"\n\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/dapr/go-sdk/service/common\"\n\tdaprd \"github.com/dapr/go-sdk/service/http\"\n)\n\nvar (\n\tlogger  = log.New(os.Stdout, \"\", 0)\n\taddress = getEnvVar(\"ADDRESS\", \":8080\")\n)\n\nfunc main() {\n\t// create a Dapr service\n\ts := daprd.NewService(address)\n\n\t// add some input binding handler\n\tif err := s.AddBindingInvocationHandler(\"schedule\", scheduleHandler); err != nil {\n\t\tlogger.Fatalf(\"error adding binding handler: %v\", err)\n\t}\n\n\t// start the service\n\tif err := s.Start(); err != nil && err != http.ErrServerClosed {\n\t\tlogger.Fatalf(\"error starting service: %v\", err)\n\t}\n}\n\nfunc scheduleHandler(ctx context.Context, in *common.BindingEvent) (out []byte, err error) {\n\tlogger.Printf(\"Schedule - Metadata:%v, Data:%v\", in.Metadata, in.Data)\n\n\t// TODO: do something with the cloud event data\n\n\treturn nil, nil\n}\n\nfunc getEnvVar(key, fallbackValue string) string {\n\tif val, ok := os.LookupEnv(key); ok {\n\t\treturn strings.TrimSpace(val)\n\t}\n\treturn fallbackValue\n}\n"
  },
  {
    "path": "dapr-aci/README.md",
    "content": "# Dapr sidecar in ACI\n\nDemo of Dapr sidecar in Azure Container Instances (ACI)\n\n## Setup \n\nThis demo illustrates simple service subscription to pub/sub topic and persistence of event payload into state. To deploy Dapr into ACI however you will need to first setup a SMB volume which will be used to store the Dapr components and mounted in the `daprd` container The storage account name needs to be globally unique so set `SNAME` to something 3-24 chars long, containing alphanumerics only, and make sure it's all in lower case.\n\n```shell\nexport SNAME=\"dapraci\"\n```\n\n> assumes your resource group and location defaults are already set. If not, set them now:\n\n```shell\naz account set --subscription <id or name>\naz configure --defaults location=<preferred location> group=<preferred resource group>\n```\n\n\nCreate a storage account\n\n```shell\naz storage account create --name $SNAME --sku Standard_LRS\n```\n\nCreate a storage share\n\n> For demo purposes share and storage user names are the same \n\n```shell\naz storage share create --name $SNAME --account-name $SNAME\n```\n\nCapture storage key \n\n```shell\nexport ACCOUNT_KEY=$(az storage account keys list --account-name $SNAME \\\n                                                  --query \"[0].value\" \\\n                                                  --output tsv)\necho $ACCOUNT_KEY\n```\n\nNow update `volumes[components].azureFile.storageAccountKey` in `configuration/app.yaml` file so that ACI can mount it.\n\nUpload the Dapr component files\n\n```shell\naz storage file upload --account-key $ACCOUNT_KEY \\\n                       --account-name $SNAME \\\n                       --share-name $SNAME \\\n                       --source components/state.yaml\n\naz storage file upload --account-key $ACCOUNT_KEY \\\n                       --account-name $SNAME \\\n                       --share-name $SNAME \\\n                       --source components/pubsub.yaml\n```\n\nList files to make sure they are all there\n\n```shell\naz storage file list \\\n    --account-key $ACCOUNT_KEY \\\n    --share-name $SNAME \\\n    --account-name $SNAME  \\\n    --output tsv\n```\n\n## Deployment \n\nOnce the storage is set up, you can deploy\n\n```shell\naz container create -f deployment/app.yaml\n```\n\nWhen you list the containers:\n\n```shell\naz container list -o table\n```\n\nThe result should look something like this:\n\n```shell\nName      ResourceGroup    Status     Image                                                IP:ports               Network    CPU/Memory       OsType    Location\n--------  ---------------  ---------  ---------------------------------------------------  ---------------------  ---------  ---------------  --------  ----------\ndapraci   mchmarny         Succeeded  daprio/daprd:0.11.3,ghcr.io/mchmarny/aci-app:v0.2.2  40.xx.xx.xx:3500       Public     1.0 core/1.5 gb  Linux     westus2\n```\n\n## Demo\n\nFirst, capture the IP for ease of access:\n\n```shell\nexport APP_IP=$(az container show -n dapraci --query \"ipAddress.ip\" -o tsv)\n```\n\nNext, invoke the ping method thru Dapr API:\n\n```shell\ncurl -i -d '{\"message\":\"ping\"}' \\\n     -H \"Content-type: application/json\" \\\n     \"http://${APP_IP}:3500/v1.0/invoke/dapraci/method/ping\"\n```\n\nResponse should look something like this:\n\n```json\n{ \"on\": 1604003460965972895, \"greeting\": \"pong\" }\n```\n\nYou can also invoke the PubSub API on Dapr to publish:\n\n```shell\ncurl -i -d '{\"message\":\"hello\"}' \\\n     -H \"Content-type: application/json\" \\\n     \"http://${APP_IP}:3500/v1.0/publish/pubsub/messages\"\n```\n\nResponse from post has no body but you should see the headers:\n\n```shell\nHTTP/1.1 200 OK\nServer: fasthttp\nDate: Thu, 29 Oct 2020 21:05:43 GMT\nContent-Length: 0\nTraceparent: 00-bd0f6f745de1b2cc8b5463f8abaa8656-d2f1d6be6d202273-00\n```\n\nThis demo also exposes the user container directly. You can disable it but commenting out `- port: 8080` in `ports` section of [configuration/app.yaml](configuration/app.yaml). To invoke the `/ping` route on the deployed app: \n\n```shell\ncurl -i -d '{\"message\":\"ping\"}' \\\n     -H \"Content-type: application/json\" \\\n     \"http://${APP_IP}:8082/ping\"\n```\n\n### Logs\n\nTo view logs from the Dapr container:\n\n```shell\naz container logs --name dapraci --container-name daprd\n```\n\n> Note, `daprd` is set to log in JSON so you can use `jq` or similar to query the logs and parse out only the messages\n\n```shell\naz container logs --name dapraci --container-name daprd | jq \".msg\"\n```\n\nTo query the app container:\n\n```shell\naz container logs --name dapraci --container-name app\n```\n\nThat's it, I hope you found it helpful. \n\n## Todo\n\n* Dapr API token auth\n* Configuration to show ACT\n* Secrets to enable Azure Vault \n\n## Disclaimer\n\nThis is my personal project and it does not represent my employer. While I do my best to ensure that everything works, I take no responsibility for issues caused by this code.\n\n## License\n\nThis software is released under the [MIT](../LICENSE)\n"
  },
  {
    "path": "dapr-aci/components/pubsub.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: pubsub\nspec:\n  type: pubsub.azure.servicebus\n  metadata:\n  - name: connectionString\n    value: \"Endpoint=sb://<TOPIC>.servicebus.windows.net/;SharedAccessKeyName=dapracidemo;SharedAccessKey=<KEY>;EntityPath=messages\"\n"
  },
  {
    "path": "dapr-aci/components/state.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: store\nspec:\n  type: state.azure.tablestorage\n  metadata:\n  - name: accountName\n    value: dapracidemo\n  - name: accountKey\n    value: <KEY>\n  - name: tableName\n    value: dapracidemodata\n"
  },
  {
    "path": "dapr-aci/deployment/app.yaml",
    "content": "apiVersion: 2018-06-01\nlocation: westus\nname: dapraci\nproperties:\n  containers:\n  - name: app\n    properties:\n      image: ghcr.io/mchmarny/aci-app:v0.2.2\n      resources:\n        requests:\n          cpu: 1.0\n          memoryInGB: 1.5\n      environmentVariables:\n        - name: ADDRESS\n          value: \":8082\"\n        - name: PUBSUB_NAME\n          value: pubsub\n        - name: TOPIC_NAME\n          value: messages\n        - name: STORE_NAME\n          value: store\n      ports:\n      - port: 8082\n  - name: daprd\n    properties:\n      image: daprio/daprd:0.11.3\n      volumeMounts:\n      - name: dapr-logs\n        mountPath: /var/log/pods\n      - name: component-store\n        mountPath: /components\n      command:\n      - /daprd\n      - --app-id\n      - dapraci\n      - --app-port\n      - 8082\n      - --components-path\n      - /components\n      - --log-as-json\n      - --log-level\n      - debug\n      resources:\n        requests:\n          cpu: 1.0\n          memoryInGB: 1.5\n      ports:\n        - port: 3500\n  osType: Linux\n  restartPolicy: Always\n  ipAddress:\n    ports:\n    # comment the ports you do not want to expose \n    - port: 8082\n      protocol: TCP\n    - port: 3500\n      protocol: TCP\n    type: Public\n  volumes:\n  - name: dapr-logs\n    emptyDir: {}\n  - name: component-store\n    azureFile:\n      shareName: dapraci\n      storageAccountName: dapraci\n      storageAccountKey: <KEY>\ntype: Microsoft.ContainerInstance/containerGroups"
  },
  {
    "path": "dapr-aci/src/Dockerfile",
    "content": "FROM golang:1.15.3 as builder\n\nWORKDIR /src/\nCOPY . /src/\n\nENV GO111MODULE=on\n\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \\\n    go build -a -tags netgo -mod vendor -o ./app .\n\nFROM gcr.io/distroless/static:nonroot\nCOPY --from=builder /src/app .\n\nENTRYPOINT [\"./app\"]\n"
  },
  {
    "path": "dapr-aci/src/Makefile",
    "content": "IMAGE_NAME  ?=aci-app\nIMAGE_TAG   ?=v0.2.2\nIMAGE_OWNER ?=$(shell git config --get user.username)\nAPP_ID      ?=aci-app\nAPP_PORT    ?=8082\n\nall: help\n\n.PHONY: tidy\ntidy: ## Updates the go modules and vendors all dependencies \n\tgo mod tidy\n\tgo mod vendor\n\n.PHONY: test\ntest: tidy ## Tests the entire project \n\tgo test -count=1 -race ./...\n\n.PHONY: run\nrun: tidy ## Runs uncompiled code in Dapr\n\tdapr run \\\n      --app-id $(APP_ID) \\\n      --app-port $(APP_PORT) \\\n      --app-protocol http \\\n\t  --dapr-http-port 3500 \\\n      --components-path ../components \\\n      --log-level debug \\\n      go run main.go\n\n.PHONY: event\nevent: ## Posts simple event to the messages topic \n\tcurl -i -d '{\"message\":\"hello\"}' \\\n\t\t -H \"Content-type: application/json\" \\\n\t\t http://localhost:3500/v1.0/publish/pubsub/messages\n\n.PHONY: ping\nping: ## Invokes app method \n\tcurl -i -d '{\"message\":\"ping\"}' \\\n\t\t -H \"Content-type: application/json\" \\\n\t\t http://localhost:3500/v1.0/invoke/$(APP_ID)/method/ping\n\n.PHONY: image\nimage: tidy ## Builds and publishes image \n\tdocker build -t \"ghcr.io/$(IMAGE_OWNER)/$(IMAGE_NAME):$(IMAGE_TAG)\" .\n\tdocker push \"ghcr.io/$(IMAGE_OWNER)/$(IMAGE_NAME):$(IMAGE_TAG)\"\n\n.PHONY: lint\nlint: ## Lints the entire project \n\tgolangci-lint run --timeout=3m\n\n.PHONY: clean\nclean: ## Cleans up generated files \n\tgo clean\n\trm -fr ./bin\n\trm -fr ./vendor\n\n.PHONY: help\nhelp: ## Display available commands\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \\\n\t\t'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n"
  },
  {
    "path": "dapr-aci/src/go.mod",
    "content": "module github.com/mchmarny/dapr-demos/dapr-aci/src\n\ngo 1.15\n\nrequire (\n\tgithub.com/dapr/go-sdk v0.11.1\n\tgithub.com/pkg/errors v0.9.1\n\tgolang.org/x/net v0.0.0-20201029055024-942e2f445f3c // indirect\n\tgolang.org/x/sys v0.0.0-20201029080932-201ba4db2418 // indirect\n\tgolang.org/x/text v0.3.4 // indirect\n\tgoogle.golang.org/genproto v0.0.0-20201028140639-c77dae4b0522 // indirect\n)\n"
  },
  {
    "path": "dapr-aci/src/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/dapr/go-sdk v0.11.1 h1:83lLYLrOKK1dAI1XhuamY9SkQoo2t5Fd8zlNlDMtwWA=\ngithub.com/dapr/go-sdk v0.11.1/go.mod h1:l9aJ4Zqsfzi9adwheYgFND6ZxhPDbb34IF/NecuPYJo=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20201022231255-08b38378de70 h1:Z6x4N9mAi4oF0TbHweCsH618MO6OI6UFgV0FP5n0wBY=\ngolang.org/x/net v0.0.0-20201022231255-08b38378de70/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201029055024-942e2f445f3c h1:rpcgRPA7OvNEOdprt2Wx8/Re2cBTd8NPo/lvo3AyMqk=\ngolang.org/x/net v0.0.0-20201029055024-942e2f445f3c/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd h1:WgqgiQvkiZWz7XLhphjt2GI2GcGCTIZs9jqXMWmH+oc=\ngolang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201029080932-201ba4db2418 h1:HlFl4V6pEMziuLXyRkm5BIYq1y1GAbb02pRlWvI54OM=\ngolang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=\ngolang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20201022181438-0ff5f38871d5 h1:YejJbGvoWsTXHab4OKNrzk27Dr7s4lPLnewbHue1+gM=\ngoogle.golang.org/genproto v0.0.0-20201022181438-0ff5f38871d5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201028140639-c77dae4b0522 h1:7RoRaOmOAXwqnurgQ5g5/d0yCi9ha2UxuTZULXudK7A=\ngoogle.golang.org/genproto v0.0.0-20201028140639-c77dae4b0522/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.33.1 h1:DGeFlSan2f+WEtCERJ4J9GJWk15TxUi8QGagfI87Xyc=\ngoogle.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "dapr-aci/src/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log\"\n\t\"time\"\n\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\n\tdapr \"github.com/dapr/go-sdk/client\"\n\t\"github.com/dapr/go-sdk/service/common\"\n\tdaprd \"github.com/dapr/go-sdk/service/http\"\n\t\"github.com/pkg/errors\"\n)\n\nconst (\n\taddInvokeHandlerError = \"error adding invocation handler\"\n\tstartingServiceError  = \"error starting service\"\n\tclientCreateError     = \"error creating Dapr client\"\n\taddSubscriptionError  = \"error subscribing to a topic\"\n\taddInvocationError    = \"error creating invcation handler\"\n\tmethodName            = \"ping\"\n)\n\nvar (\n\tlogger  = log.New(os.Stdout, \"\", 0)\n\taddress = getEnvVar(\"ADDRESS\", \":8082\")\n\n\tpubSubName = getEnvVar(\"PUBSUB_NAME\", \"pubsub\")\n\ttopicName  = getEnvVar(\"TOPIC_NAME\", \"messages\")\n\n\tstoreName = getEnvVar(\"STORE_NAME\", \"store\")\n\n\tclient dapr.Client\n)\n\nfunc main() {\n\ts := daprd.NewService(address)\n\n\tvar clientErr error\n\tif client, clientErr = dapr.NewClient(); clientErr != nil {\n\t\tlogger.Fatalf(\"%s: %v\", clientCreateError, clientErr)\n\t}\n\tdefer client.Close()\n\n\tif err := s.AddServiceInvocationHandler(methodName, invokeHandler); err != nil {\n\t\tlogger.Fatalf(\"%s: %v\", addInvocationError, err)\n\t}\n\n\tsubscription := &common.Subscription{\n\t\tPubsubName: pubSubName,\n\t\tTopic:      topicName,\n\t\tRoute:      fmt.Sprintf(\"/%s\", topicName),\n\t}\n\n\tif err := s.AddTopicEventHandler(subscription, eventHandler); err != nil {\n\t\tlogger.Fatalf(\"%s: %v\", addSubscriptionError, err)\n\t}\n\n\tif err := s.Start(); err != nil && err != http.ErrServerClosed {\n\t\tlogger.Fatalf(\"%s: %v\", startingServiceError, err)\n\t}\n}\n\nfunc invokeHandler(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) {\n\tlogger.Printf(\"Method %s invoked  (ContentType:%s, Verb:%s, QueryString:%s, Data:%s)\",\n\t\tmethodName, in.ContentType, in.Verb, in.QueryString, in.Data)\n\tj := []byte(fmt.Sprintf(`{\"on\": %d, \"greeting\": \"pong\"}`, time.Now().UTC().UnixNano()))\n\tout = &common.Content{ContentType: in.ContentType, Data: j}\n\treturn\n}\n\nfunc eventHandler(ctx context.Context, e *common.TopicEvent) (retry bool, err error) {\n\tlogger.Printf(\"Event received (PubsubName:%s, Topic:%s, Data: %v\", e.PubsubName, e.Topic, e.Data)\n\n\tdata, ok := e.Data.([]byte)\n\tif !ok {\n\t\tdata, err = json.Marshal(e.Data)\n\t\tif err != nil {\n\t\t\treturn false, errors.Wrapf(err, \"invalid data format: %T\", e.Data)\n\t\t}\n\t}\n\n\tif err := client.SaveState(ctx, storeName, e.ID, data); err != nil {\n\t\treturn false, errors.Wrapf(err, \"error saving data to %s (%s)\", storeName, data)\n\t}\n\n\treturn false, nil\n}\n\nfunc getEnvVar(key, fallbackValue string) string {\n\tif val, ok := os.LookupEnv(key); ok {\n\t\treturn strings.TrimSpace(val)\n\t}\n\treturn fallbackValue\n}\n"
  },
  {
    "path": "dapr-api-on-aci/README.md",
    "content": "# Dapr API in ACI\n\n* Purpose-configured instance of Dapr deployed into Azure Container Instances (ACI) with API token authentication using single command \n* Use of Dapr output binding + Dapr as a microservice (in this case email sending)\n\n\n## Setup \n\nThe storage account name needs to be globally unique. Set `SNAME` to something 3-24 chars long, containing alphanumerics only, and make sure it's all in lower case.\n\n```shell\nexport SNAME=\"demodapr\"\n```\n\n> assumes your resource group and location defaults are already set. If not, set them now:\n\n```shell\naz account set --subscription <id or name>\naz configure --defaults location=<preferred location> group=<preferred resource group>\n```\n\nCreate a storage account\n\n```shell\naz storage account create --name $SNAME --sku Standard_LRS\n```\n\nCreate a storage share for config\n\n> For demo purposes share and storage user names are the same \n\n```shell\naz storage share create --name $SNAME --account-name $SNAME\n```\n\nCapture storage key \n\n```shell\nexport SKEY=$(az storage account keys list --account-name $SNAME --query \"[0].value\" --output tsv)\n```\n\nCreate a storage directory for config files  \n\n```shell\naz storage directory create --account-name $SNAME --name $SNAME --share-name $SNAME\n```\n\nUpload the Dapr component files\n\n> TODO: Make sure you set the Sendgrid API key in the email.yaml\n\n```shell\naz storage file upload --account-name $SNAME --share-name $SNAME --source email.yaml\n```\n\n## Deployment \n\nOnce the storage is set up, you can deploy. Start by exporting Dapr API Authentication token\n\n```shell\nexport DTOKEN=$(openssl rand -base64 36)\n```\n\n> Note, make sure to save the value exported into `$DTOKEN` variable to ensure you can use it in other terminal sessions. That value will not be recoverable from the ACI service. \n\nAnd launch the Dapr container\n\n```shell\naz container create \\\n    --name $SNAME \\\n    --ports 3500 \\\n    --protocol TCP \\\n    --dns-name-label $SNAME \\\n    --image docker.io/daprio/daprd:0.11.0 \\\n    --command-line \"/daprd --components-path /components --app-protocol http\" \\\n    --secure-environment-variables \"DAPR_API_TOKEN=${DTOKEN}\" \\\n    --azure-file-volume-share-name $SNAME \\\n    --azure-file-volume-account-name $SNAME \\\n    --azure-file-volume-account-key $SKEY \\\n    --azure-file-volume-mount-path /components\n```\n\nThen check on the status of the deployment \n\n```shell\naz container list -o table\n```\n\nThe result should look something like this \n\n```shell\nName      ResourceGroup  Status     Image                          IP:ports           Network  CPU/Memory       OsType    Location\n--------  -------------  ---------  -----------------------------  -----------------  -------  ---------------  --------  --------\ndemodapr  mchmarny       Succeeded  docker.io/daprio/daprd:0.11.0  51.143.49.0:3500   Public   1.0 core/1.5 gb  Linux     westus2\n```\n\nIf everything went OK, you should be able post to the email output binding below\n\nTo restart the service after update of environment variables \n\n```shell\naz container restart --name $SNAME\n```\n\n## Use\n\nTo use the above deployed instance of Dapr configured with SendGrid output binding, POST to the Dapr API following message using `curl`.\n\n> Note, the from, to, and email subject are configured server side so all you have to submit is a valid output binding message with the `operation` and `data` properties, with the body of the email sent to the user.\n\n```shell\nexport SREGION=$(az container list --query \"[?contains(name, '${SNAME}')].location\" --output tsv)\n```\n\n\n```shell\ncurl -v -X POST -H \"Content-Type: application/json\" \\\n    -H \"dapr-api-token: ${DTOKEN}\" \\\n    \"http://${SNAME}.${SREGION}.azurecontainer.io:3500/v1.0/bindings/email\" \\\n    -d '{ \"operation\": \"create\", \"data\": \"<h1>Test Headline</h1><p>Test message</p>\"}'\n```\n\n## Disclaimer\n\nThis is my personal project and it does not represent my employer. While I do my best to ensure that everything works, I take no responsibility for issues caused by this code.\n\n## License\n\nThis software is released under the [MIT](../LICENSE)\n"
  },
  {
    "path": "dapr-api-on-aci/email.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: email\nspec:\n  type: bindings.twilio.sendgrid\n  metadata:\n  - name: emailFrom\n    value: \"demo@thingz.io\"\n  - name: subject\n    value: \"Demo Email Subject\"\n  - name: apiKey\n    value: <YOUR_SENDGRID_API_KEY>"
  },
  {
    "path": "daprized-ingress/README.md",
    "content": "# Dapr API on Cluster Ingress Controller \n\nThis how to will walk through the process of configuring Dapr api on your cluster ingress. The instructions are pretty much the same regardless of the type of ingress you are using. In this how to I'll be using [NGINX](https://nginx.org/en/).\n\n![](img/diagram.png)\n\n> This how-to assumes you already have Dapr installed in your cluster. If not, consider the opinionated install [here](../setup) or the fully documented instruction in [Dapr docs](https://docs.dapr.io/operations/hosting/kubernetes/).\n\n## Setup\n\nTo make this how-to more reproducible, start by defining the namespace where your NGINX ingress is/will be located:\n\n```shell\nexport INGRESS_NAMESPACE=\"default\"\n```\n\nIf you are not using `default` namespace, apply also the necessary roles to that namespace:\n\n```shell\nkubectl apply -f config/namespace.yml -n $INGRESS_NAMESPACE\n```\n\nNext, create a secret to hold the Dapr API token:\n\n```shell\nexport API_TOKEN=$(openssl rand -base64 32)\nkubectl create secret generic dapr-api-token --from-literal=token=\"${API_TOKEN}\" -n $INGRESS_NAMESPACE\n```\n\n## Deployment\n\nNext, apply the Dapr config for your ingress (this holds the tracing configuration as well as ensures that the ingress is able to access only the `dapr-api-token` secret we specified above):\n\n```shell\nkubectl apply -f config/ingress-config.yaml -n $INGRESS_NAMESPACE\n```\n\nAdd the Helm repo: \n\n```shell\nhelm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx\nhelm repo update\n```\n\nInstall NGINX:\n\n> Note, the following command creates 2 replicas to prevent ingress issues during subsequent changes\n\n```shell\nhelm install nginx ingress-nginx/ingress-nginx \\\n     --set controller.replicaCount=2 \\\n     -f config/ingress-annotations.yaml \\\n     -n $INGRESS_NAMESPACE\n```\n\nAnd wait for the deployment to finish:\n\n```shell\nkubectl rollout status deployment/nginx-ingress-nginx-controller -n $INGRESS_NAMESPACE\n```\n\nNotice we are adding additional configuration which appends all the necessary pod annotations to dapr'ize the ingress controller. Here is the complete list of annotations that were used in above command:\n\n```yaml\ncontroller:\n  podAnnotations:\n    dapr.io/enabled: \"true\" \n    dapr.io/app-id: \"nginx-ingress\" \n    dapr.io/app-protocol: \"http\"\n    dapr.io/app-port: \"80\"\n    dapr.io/api-token-secret: \"dapr-api-token\" \n    dapr.io/config: \"ingress-config\"\n    dapr.io/log-as-json: \"true\"\n```\n\nMost of these are self explanatory, including references to both the API token (secret) and ingress config we've created above. You can learn more about all of the annotations supported in Dapr [here](https://docs.dapr.io/operations/hosting/kubernetes/kubernetes-annotations/):\n\n\n## Configure\n\nFinally, to expose the Dapr API, first change the host name (`api.thingz.io`) in [config/ingress.yaml](config/ingress.yaml) file to the domain name you would like to use:\n\n> Note, you will have to create an A entry in your DNS server in the following step so use a domain you can actually control. `thingz.io` is mine ;) \n\n```yaml\nspec:\n  rules:\n    - host: api.thingz.io\n      http:\n        paths:\n          - path: /\n            backend:\n              serviceName: nginx-ingress-dapr\n              servicePort: 80\n```\n\nWhen done, apply the ingress to the cluster:\n\n```shell\nkubectl apply -f config/ingress.yaml -n $INGRESS_NAMESPACE\n```\n\n> See [setup](../setup) for instructions hot to create TLS certificates and configure SSL for your domain.\n\n## DNS\n\nNow that everything is set up, the only thing that's left is to create the DNS entry for your cluster ingress controller. To do that, start by obtaining the public IP address on your ingress:\n\n```shell\nkubectl get svc nginx-ingress-nginx-controller \\\n     -o jsonpath='{.status.loadBalancer.ingress[0].ip}' \\\n     -n $INGRESS_NAMESPACE\n```\n\nNext go to your DNS service dashboard and create an `A` entry for that IP. For example, if your domain you entered in the above Configuration step was `api.thingz.io` and your IP address printed above was `1.2.3.4`, you would create a following entry:\n\n```shell\nName:     Type:      TTL:      Data:\napi       A          60s       1.2.3.4\n```\n\nThe `name` (or `api` in my example above) is basically whatever you want as long as it is the same name you used in the Configuration step above. Also, the `TTL` setting is key as you don't want to wait for long time to have this entry propagate. Some DNS services will have different duration formats, so just aim for the lowest number possible like `1m`.\n\nWhen done, you can test if the DNS is ready by running `dig` with your domain name (e.g. `api.thingz.io`): \n\n```shell\ndig <your-full-domain-name> \n```\n\nWhen the A record for your domain name in reflected in the ANSWER SECTION you are ready to test.\n\n## Test \n\nIn case you lost the terminal session when we first defined the `API_TOKEN` variable, capture it again:\n\n\n```shell\nexport API_TOKEN=$(kubectl get secret dapr-api-token -o jsonpath=\"{.data.token}\" -n ${INGRESS_NAMESPACE} | base64 --decode)\n```\n\nThen curl the Dapr health API to ensure ensure everything works. Just make sure to replace the `<your-full-domain-name>` with your actual domain. \n\n```shell\ncurl -i \\\n     -H \"Content-type: application/json\" \\\n     -H \"dapr-api-token: ${API_TOKEN}\" \\\n     \"http://<your-full-domain-name>/v1.0/healthz\"\n```\n\nIf the response from the Dapr health API is `HTTP/2 200` you are good to go. Check the [hardened demo](../hardened) for example how you can use this API to access services in other namespaces, how to control access to all your Dapr services in the cluster, and how you can get an end-to-end traceability for all invocations including the ingress. \n\n## Troubleshooting\n\nThere are a few places to check if you experience any issues:\n\n* Does the domain name you used resolve to the cluster ingress IP? \n* Has Dapr injected its sidecar into the ingress pod? (Container count on `get pods` should be 2)\n\n\n## Disclaimer\n\nThis is my personal project and it does not represent my employer. While I do my best to ensure that everything works, I take no responsibility for issues caused by this code.\n\n## License\n\nThis software is released under the [MIT](../LICENSE)\n"
  },
  {
    "path": "daprized-ingress/config/ingress-annotations.yaml",
    "content": "controller:\n  podAnnotations:\n    dapr.io/enabled: \"true\"\n    dapr.io/app-id: \"nginx-ingress\"\n    dapr.io/app-protocol: \"http\"\n    dapr.io/app-port: \"80\"\n    dapr.io/api-token-secret: \"dapr-api-token\"\n    dapr.io/config: \"ingress-config\"\n    dapr.io/log-as-json: \"true\""
  },
  {
    "path": "daprized-ingress/config/ingress-config.yaml",
    "content": "---\napiVersion: dapr.io/v1alpha1\nkind: Configuration\nmetadata:\n  name: ingress-config\nspec:\n  tracing:\n    samplingRate: \"1\"\n  secrets:\n    scopes:\n      - storeName: kubernetes\n        defaultAccess: deny\n        allowedSecrets: [\"dapr-api-token\"]\n---\napiVersion: dapr.io/v1alpha1\nkind: Configuration\nmetadata:\n  name: tracing\nspec:\n  tracing:\n    samplingRate: \"1\""
  },
  {
    "path": "daprized-ingress/config/ingress.yaml",
    "content": "apiVersion: extensions/v1beta1\nkind: Ingress\nmetadata:\n  name: ingress-rules\n  annotations:\n    kubernetes.io/ingress.class: nginx\n    nginx.ingress.kubernetes.io/rewrite-target: /\nspec:\n  rules:\n    - host: api.thingz.io\n      http:\n        paths:\n          - path: /\n            backend:\n              serviceName: nginx-ingress-dapr\n              servicePort: 80\n      "
  },
  {
    "path": "daprized-ingress/config/namespace.yml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: secret-reader\nrules:\n- apiGroups: [\"\"]\n  resources: [\"secrets\"]\n  verbs: [\"get\"]\n---\nkind: RoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: dapr-secret-reader\nsubjects:\n- kind: ServiceAccount\n  name: default\nroleRef:\n  kind: Role\n  name: secret-reader\n  apiGroup: rbac.authorization.k8s.io"
  },
  {
    "path": "fan-out/README.md",
    "content": "# fan-out demo \n\n`Fan-out` is a messaging pattern where single message source is \"broadcasted\" to multiple targets. The common use-case for this may be situation where multiple teams or systems need to receive events from the same source. This is sometimes made even more complicated by the differences in expected formats and protocols by each one of the target systems. \n\nThis demo will illustrate how to use Dapr's plugable component mechanism to `fan-out` events from one Pub/Sub configured with Redis to:\n\n* Kafka topic in CSV format \n* REST endpoint in JSON format \n* gRPC service in XML format \n\n![](./img/fan-out-in-dapr.png)\n\nThis allows for incremental modifications with ability to customize each stream according to its unique configuration needs (e.g. throughout, authentication, format, retry strategy, or even error tolerance). For more information about Dapr's pub/sub see these [docs](https://github.com/dapr/docs/tree/master/concepts/publish-subscribe-messaging)\n\nThis demo requires Dapr `v0.11` as well as go `1.14+` and docker-compose `v1.26+`\n\n> Note, the version of the demo with Event Hub binding has been moved to [this branch](https://github.com/mchmarny/dapr-demos/tree/fanout-eventhubs/fan-out)\n\n## App 2: Pub/Sub to Pub/Sub Publisher\n\nTo run these demos you will need access to Redis and Kafka servers. For Redis, you can use the one installed during local Dapr setup. For Kafka, you can use the included Docker Compose file. First, navigate to the `queue-format-converter` and start Kafka:\n\n```shell\ndocker-compose -f ./config/kafka.yaml up -d\n```\n\nThe result should look something like this:\n\n```shell\nCreating network \"config_default\" with the default driver\nCreating config_kafka_1     ... done\nCreating config_zookeeper_1 ... done\n```\n\nNow, start `App 2` which will receive events from Redis, convert them to XML, and publish them onto the Kafka topic:\n\n```shell\ndapr run \\\n    --app-id app2 \\\n    --app-port 60010 \\\n    --app-protocol grpc \\\n    --components-path ./config \\\n    go run main.go\n```\n\n> The source and targets Pub/Sub components are defined in the [./config](./queue-format-converter/config) directory in their respected files so the use code is free of SDK and libraries which allows for easy re-configuration at run-time. \n\nLeave the application running, we will come back to it after configuring `App 1`\n\n## App 1: Pub/Sub Event Producer\n\nTo demo the above `App 2` we will need events. To produce events, in another terminal session navigate to `queue-event-producer` directory, start the `App 1`: \n\n```shell\ndapr run \\\n    --app-id app1 \\\n    --app-port 60013 \\\n    --app-protocol grpc \\\n    --components-path ./config \\\n    go run main.go\n```\n\nThe app will now publish one event every `3s`. To change the frequency just define the desired duration using `THREAD_PUB_FREQ` variable and restart the app. The results should look something like this:\n\n```shell\n== APP == published: {\"id\":\"df50a6c7-b5bb-45ce-b3a8-ad428bbbd5fe\",\"temperature\":60.46998219508215,\"humidity\":94.05150371362079,\"time\":1598960035}\n```\n\nNow in the `App 1`, the log output for each event should look something like this: \n\n```shell\n== APP == Event - PubsubName:fanout-source-pubsub, Topic:events, ID:5ffa4502-8bf1-4bbf-927e-8b62e1949166\n== APP == Target (csv): \"45ecf820-705b-47c2-a2e4-7dbb3eecb728\",66.459360,43.777042,\"2020-09-01T04:33:58-07:00\"\n```\n\n## App 3: Pub/Sub to External REST Endpoint Publisher\n\nTo add another publisher which converts the received events into JSON and publishes them to the component defined REST endpoint, first navigate to the `http-format-converter` directory and start `App 3`:\n\n```shell\ndapr run \\\n    --app-id app3 \\\n    --app-port 60011 \\\n    --app-protocol grpc \\\n    --components-path ./config \\\n    go run main.go\n```\n\n> This app is configured with an HTTP binding that defines the target and verb of the invocation. This external configuration allows for easy change of the target binding to something like email using SendGrid or AWS S3 without changing the use-code. To learn more about Dapr bindings, see these [docs](https://github.com/dapr/docs/tree/master/concepts/bindings#supported-bindings-and-specs)\n\n```yaml\napiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: fanout-http-target-post-binding\nspec:\n  type: bindings.http\n  metadata:\n  - name: url\n    value: https://postman-echo.com/post\n  - name: method\n    value: POST\n```\n\nIf the `App 1` is still running, you should see entries in the log similar to this:\n\n```shell\n== APP == Event - PubsubName:fanout-source-pubsub, Topic:events, ID:d5bbcc9e-f0d3-46df-8d8e-f7dadb3f304c\n== APP == Target (json): {\"temperature\":20.326656,\"humidity\":36.093533,\"time\":1598961599,\"id\":\"b2fa85cf-1be6-4489-9aec-613cf969675e\"}\n```\n\n## App 4: Pub/Sub to another Dapr Service Publisher \n\nTo add the final publisher, which will convert events into XML format and publish them to another Dapr service over gRPC, first navigate to the `grpc-echo-service` directory and start target service. For demo purposes, we will use the included echo service which simply returns whatever message it receives.\n\n```shell\ndapr run \\\n    --app-id grpc-echo-service \\\n    --app-port 60015 \\\n    --app-protocol grpc \\\n    go run main.go\n```\n\nThen, in yet another terminal session, navigate to the `service-format-converter` directory and start the `App 4` that will invoke the `grpc-echo-service`:\n\n```shell\ndapr run \\\n    --app-id app4 \\\n    --app-port 60012 \\\n    --app-protocol grpc \\\n    --components-path ./config \\\n    go run main.go\n```\n\nIf everything went well, you should see something similar in the `App 4` logs: \n\n```shell\n== APP == Target: &{Data:[60 84 111 112 105 99 69 118...] ContentType:application/xml}\n== APP == Response: <TopicEvent><ID>df450605-4927-407e-a2b2-61233939ee58</ID><SpecVersion>1.0</SpecVersion><Type>com.dapr.event.sent</Type><Source>app1</Source><DataContentType>application/json</DataContentType><Data>{&#34;time&#34;:1598962950,&#34;id&#34;:&#34;745e3ebe-990b-4292-9347-f84ff81f41e6&#34;,&#34;temperature&#34;:31.812637,&#34;humidity&#34;:46.894296}</Data><Subject></Subject><Topic>events</Topic><PubsubName>fanout-source-pubsub</PubsubName></TopicEvent>\n```\n\n> If you left all the apps running, you can go to each terminal session and see the different formats which are generated and published by each application.\n\n\n## Disclaimer\n\nThis is my personal project and it does not represent my employer. While I do my best to ensure that everything works, I take no responsibility for issues caused by this code.\n\n## License\n\nThis software is released under the [MIT](../LICENSE)\n"
  },
  {
    "path": "fan-out/grpc-echo-service/Dockerfile",
    "content": "FROM golang:1.15.0 as builder\n\nWORKDIR /src/\nCOPY . /src/\n\nENV GO111MODULE=on\n\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \\\n    go build -a -tags netgo -mod vendor -o ./service .\n\nFROM gcr.io/distroless/static:nonroot\nCOPY --from=builder /src/service .\n\nENTRYPOINT [\"./service\"]\n"
  },
  {
    "path": "fan-out/grpc-echo-service/Makefile",
    "content": "RELEASE_VERSION  =v0.11.1\nSERVICE_NAME    ?=grpc-echo-service\nDOCKER_USERNAME ?=$(DOCKER_USER)\n\n.PHONY: all\nall: help\n\n.PHONY: tidy\ntidy: ## Updates the go modules and vendors all dependencies \n\tgo mod tidy\n\tgo mod vendor\n\n.PHONY: test\ntest: tidy ## Tests the entire project \n\tgo test -count=1 -race ./...\n\n.PHONY: run\nrun: tidy ## Runs uncompiled code in Dapr\n\tdapr run \\\n\t\t--app-id $(SERVICE_NAME) \\\n\t\t--app-port 60002 \\\n\t\t--app-protocol grpc \\\n\t\t--dapr-http-port 3500 \\\n        go run main.go\n\n.PHONY: invoke\ninvoke: ## Invokes service through Dapr API \n\tcurl -d '{ \"message\": \"ping\" }' \\\n     -H \"Content-type: application/json\" \\\n     \"http://localhost:3500/v1.0/invoke/$(SERVICE_NAME)/method/echo\"\n\n.PHONY: image\nimage: tidy ## Builds and publish docker image \n\tdocker build -t \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\" .\n\tdocker push \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\"\n\n.PHONY: deploy\ndeploy: ## Deploys prebuild image to k8s using currently selected context \n\tkubectl apply -f deployment.yaml\n\tkubectl rollout restart deployment/grpc-echo-service\n\tkubectl rollout status deployment/grpc-echo-service\n\n.PHONY: call\ncall: ## Invokes service through Dapr API \n\t$(eval API_TOKEN=$(shell kubectl get secret dapr-api-token -o jsonpath=\"{.data.token}\" | base64 --decode))\n\tcurl -d '{ \"message\": \"ping\" }' \\\n     -H \"Content-type: application/json\" \\\n     -H \"dapr-api-token: $(API_TOKEN)\" \\\n     \"https://api.cloudylabs.dev/v1.0/invoke/$(SERVICE_NAME)/method/echo\"\n\n.PHONY: lint\nlint: ## Lints the entire project \n\tgolangci-lint run --timeout=3m\n\n.PHONY: tag\ntag: ## Creates release tag \n\tgit tag $(RELEASE_VERSION)\n\tgit push origin $(RELEASE_VERSION)\n\n.PHONY: clean\nclean: ## Cleans up generated files \n\tgo clean\n\trm -fr ./bin\n\trm -fr ./vendor\n\n.PHONY: help\nhelp: ## Display available commands\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \\\n\t\t'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n"
  },
  {
    "path": "fan-out/grpc-echo-service/README.md",
    "content": "# grpc-service\n\nFor more information about service invocation see the [Dapr docs](https://github.com/dapr/docs/tree/master/concepts/service-invocation)\n\n## Run \n\nTo run this demo in Dapr, run:\n\n```shell\ndapr run \\\n    --app-id grpc-service-demo \\\n    --app-port 50001 \\\n    --app-protocol grpc \\\n    --dapr-http-port 3500 \\\n    --components-path ./config \\\n    go run main.go\n```\n\n## Deploy\n\nDeploy and wait for the pod to be ready \n\n```shell\nkubectl apply -f deployment.yaml\nkubectl rollout status deployment/grpc-echo-service\n```\n\nIf you have changed an existing component, make sure to reload the ingress and wait until the new version is ready\n\n```shell\nkubectl rollout restart deployment/nginx-ingress-nginx-controller\nkubectl rollout status deployment/nginx-ingress-nginx-controller\n```\n\nFollow logs\n\n```shell\nkubectl logs -l app=grpc-echo-service -c service -f\n```\n\nIn a separate terminal session export API token\n\n```shell\nexport API_TOKEN=$(kubectl get secret dapr-api-token -o jsonpath=\"{.data.token}\" | base64 --decode)\n```\n\nAnd invoke the service\n\n```shell\ncurl -d '{ \"message\": \"ping\" }' \\\n     -H \"Content-type: application/json\" \\\n     -H \"dapr-api-token: ${API_TOKEN}\" \\\n     \"https://api.cloudylabs.dev/v1.0/invoke/grpc-echo-service/method/echo\"\n```\n\nThe response should include the sent message \n\n```json\n{ \n    \"message\": \"ping\" \n}\n```\n\nAnd the logs\n\n```shell\nInvocation (ContentType:application/json, Verb:POST, QueryString:map[], Data:{ \"message\": \"ping\" })\n```\n\n## Disclaimer\n\nThis is my personal project and it does not represent my employer. While I do my best to ensure that everything works, I take no responsibility for issues caused by this code.\n\n## License\n\nThis software is released under the [MIT](../LICENSE)\n"
  },
  {
    "path": "fan-out/grpc-echo-service/deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: grpc-echo-service\n  labels:\n    app: grpc-echo-service\n    demo: echo\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: grpc-echo-service\n  template:\n    metadata:\n      labels:\n        app: grpc-echo-service\n        demo: echo\n      annotations:\n        dapr.io/enabled: \"true\"\n        dapr.io/app-id: \"grpc-echo-service\"\n        dapr.io/app-protocol: \"grpc\"\n        dapr.io/app-port: \"60002\"\n        dapr.io/config: \"tracing\"\n        dapr.io/log-as-json: \"true\"\n        dapr.io/log-level: \"debug\"\n    spec:\n      containers:\n      - name: service\n        image: mchmarny/grpc-echo-service:v0.11.1\n        imagePullPolicy: Always\n        ports:\n        - containerPort: 60002\n        env:\n        - name: ADDRESS\n          value: \":60002\""
  },
  {
    "path": "fan-out/grpc-echo-service/go.mod",
    "content": "module github.com/mchmarny/dapr-demos/grpc-echo-service\n\ngo 1.15\n\nrequire github.com/dapr/go-sdk v0.11.0\n"
  },
  {
    "path": "fan-out/grpc-echo-service/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo=\ngithub.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=\ngolang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 h1:4X356008q5SA3YXu8PiRap39KFmy4Lf6sGlceJKZQsU=\ngolang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0 h1:uslsjIdqvZYANxSBQjTI47vZfwMaTN3mLELkMnMIY/A=\ngoogle.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=\ngoogle.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "fan-out/grpc-echo-service/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/dapr/go-sdk/service/common\"\n\tdaprd \"github.com/dapr/go-sdk/service/grpc\"\n)\n\nvar (\n\tlogger         = log.New(os.Stdout, \"\", 0)\n\tserviceAddress = getEnvVar(\"ADDRESS\", \":60015\")\n)\n\nfunc main() {\n\t// create serving server\n\ts, err := daprd.NewService(serviceAddress)\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to start the server: %v\", err)\n\t}\n\n\t// add handler to the service\n\ts.AddServiceInvocationHandler(\"echo\", echoHandler)\n\n\t// start the server to handle incoming events\n\tlog.Printf(\"starting server at %s...\", serviceAddress)\n\tif err := s.Start(); err != nil {\n\t\tlog.Fatalf(\"server error: %v\", err)\n\t}\n}\n\nfunc echoHandler(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) {\n\tlogger.Printf(\n\t\t\"Invocation (ContentType:%s, Verb:%s, QueryString:%s, Data:%s)\",\n\t\tin.ContentType, in.Verb, in.QueryString, string(in.Data),\n\t)\n\n\t// TODO: implement handling logic here\n\tout = &common.Content{\n\t\tContentType: in.ContentType,\n\t\tData:        in.Data,\n\t}\n\n\treturn\n}\n\nfunc getEnvVar(key, fallbackValue string) string {\n\tif val, ok := os.LookupEnv(key); ok {\n\t\treturn strings.TrimSpace(val)\n\t}\n\treturn fallbackValue\n}\n"
  },
  {
    "path": "fan-out/http-format-converter/Dockerfile",
    "content": "FROM golang:1.15.0 as builder\n\nWORKDIR /src/\nCOPY . /src/\n\nENV GO111MODULE=on\n\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \\\n    go build -a -tags netgo -mod vendor -o ./service .\n\nFROM gcr.io/distroless/static:nonroot\nCOPY --from=builder /src/service .\n\nENTRYPOINT [\"./service\"]\n"
  },
  {
    "path": "fan-out/http-format-converter/Makefile",
    "content": "RELEASE_VERSION  =v0.11.1\nSERVICE_NAME    ?=http-format-converter\nDOCKER_USERNAME ?=$(DOCKER_USER)\nTARGET_FORMAT    =xml\n\n.PHONY: all\nall: help\n\n.PHONY: tidy\ntidy: ## Updates the go modules and vendors all dependencies \n\tgo mod tidy\n\tgo mod vendor\n\n.PHONY: tidy\ntest: tidy ## Tests the entire project \n\tgo test -count=1 -race ./...\n\n.PHONY: run\nrun: tidy ## Runs uncompiled code in Dapr (make run TARGET_FORMAT=csv)\n\tTARGET_FORMAT=$(TARGET_FORMAT) dapr run \\\n        --app-id $(SERVICE_NAME) \\\n        --app-port 60011 \\\n        --app-protocol grpc \\\n        --components-path ./config \\\n\t\t--log-level debug \\\n        go run main.go\n\n.PHONY: image\nimage: tidy ## Builds and publish docker image \n\tdocker build -t \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\" .\n\tdocker push \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\"\n\n.PHONY: deploy\ndeploy: ## Deploys prebuild image to k8s using currently selected context \n\tkubectl apply -f k8s/components.yaml\n\tkubectl apply -f k8s/deployment.yaml\n\tkubectl rollout restart deployment/$(SERVICE_NAME)\n\n.PHONY: lint\nlint: ## Lints the entire project \n\tgolangci-lint run --timeout=3m\n\n.PHONY: tag\ntag: ## Creates release tag \n\tgit tag $(RELEASE_VERSION)\n\tgit push origin $(RELEASE_VERSION)\n\n.PHONY: clean\nclean: ## Cleans up generated files \n\tgo clean\n\trm -fr ./bin\n\trm -fr ./vendor\n\n.PHONY: help\nhelp: ## Display available commands\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \\\n\t\t'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n"
  },
  {
    "path": "fan-out/http-format-converter/config/source-pubsub.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: fanout-source-pubsub\nspec:\n  type: pubsub.redis\n  metadata:\n  - name: redisHost\n    value: localhost:6379\n  - name: redisPassword\n    value: \"\""
  },
  {
    "path": "fan-out/http-format-converter/config/target-binding.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: fanout-http-target-post-binding\nspec:\n  type: bindings.http\n  metadata:\n  - name: url\n    value: https://postman-echo.com/post\n  - name: method\n    value: POST"
  },
  {
    "path": "fan-out/http-format-converter/go.mod",
    "content": "module github.com/mchmarny/dapr-demos/fan-out/http-format-converter\n\ngo 1.15\n\nrequire (\n\tgithub.com/dapr/go-sdk v0.11.0\n\tgithub.com/pkg/errors v0.9.1\n)\n"
  },
  {
    "path": "fan-out/http-format-converter/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo=\ngithub.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=\ngolang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 h1:4X356008q5SA3YXu8PiRap39KFmy4Lf6sGlceJKZQsU=\ngolang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0 h1:uslsjIdqvZYANxSBQjTI47vZfwMaTN3mLELkMnMIY/A=\ngoogle.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=\ngoogle.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "fan-out/http-format-converter/k8s/components.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: grpc-events\nspec:\n  type: pubsub.redis\n  metadata:\n  - name: redisHost\n    value: redis-master.redis.svc.cluster.local:6379\n  - name: redisPassword\n    secretKeyRef:\n      name: redis-secret\n      key: password\n  - name: allowedTopics\n    value: \"messages\"\nscopes:\n- xml-converter"
  },
  {
    "path": "fan-out/http-format-converter/k8s/deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: grpc-event-subscriber\n  labels:\n    app: grpc-event-subscriber\n    demo: grpc-event\nspec:\n  selector:\n    matchLabels:\n      app: grpc-event-subscriber\n  template:\n    metadata:\n      labels:\n        app: grpc-event-subscriber\n        demo: grpc-event\n      annotations:\n        dapr.io/enabled: \"true\"\n        dapr.io/app-id: \"grpc-event-subscriber\"\n        dapr.io/app-protocol: \"grpc\"\n        dapr.io/app-port: \"60002\"\n        dapr.io/config: \"tracing\"\n        dapr.io/log-as-json: \"true\"\n        dapr.io/log-level: \"debug\"\n    spec:\n      containers:\n      - name: service\n        image: mchmarny/grpc-event-subscriber:v0.11.1\n        ports:\n        - containerPort: 60002\n        env:\n        - name: ADDRESS\n          value: \":60002\"\n        - name: PUBSUB_NAME\n          value: \"grpc-events\"\n        - name: TOPIC_NAME\n          value: \"messages\"\n"
  },
  {
    "path": "fan-out/http-format-converter/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"encoding/xml\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\tdapr \"github.com/dapr/go-sdk/client\"\n\t\"github.com/dapr/go-sdk/service/common\"\n\tdaprd \"github.com/dapr/go-sdk/service/grpc\"\n\t\"github.com/pkg/errors\"\n)\n\nvar (\n\tlogger = log.New(os.Stdout, \"\", 0)\n\tclient dapr.Client\n\n\tserviceAddress = getEnvVar(\"ADDRESS\", \":60011\")\n\n\tsourcePubSubName = getEnvVar(\"SOURCE_PUBSUB_NAME\", \"fanout-source-pubsub\")\n\tsourceTopicName  = getEnvVar(\"SOURCE_TOPIC_NAME\", \"events\")\n\n\ttargetBindingName = getEnvVar(\"TARGET_BINDING\", \"fanout-http-target-post-binding\")\n\ttargetFormat      = getEnvVar(\"TARGET_FORMAT\", \"json\")\n)\n\nfunc main() {\n\t// create Dapr service\n\ts, err := daprd.NewService(serviceAddress)\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to start the server: %v\", err)\n\t}\n\n\tc, err := dapr.NewClient()\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to create Dapr client: %v\", err)\n\t}\n\tclient = c\n\tdefer client.Close()\n\n\t// add handler to the service\n\tsub := &common.Subscription{PubsubName: sourcePubSubName, Topic: sourceTopicName}\n\ts.AddTopicEventHandler(sub, eventHandler)\n\n\t// start the server to handle incoming events\n\tif err := s.Start(); err != nil {\n\t\tlog.Fatalf(\"server error: %v\", err)\n\t}\n}\n\n// SourceEvent represents the input event\ntype SourceEvent struct {\n\tID          string  `json:\"id\"`\n\tTemperature float64 `json:\"temperature\"`\n\tHumidity    float64 `json:\"humidity\"`\n\tTime        int64   `json:\"time\"`\n}\n\nfunc eventHandler(ctx context.Context, e *common.TopicEvent) (retry bool, err error) {\n\tlogger.Printf(\"Event - PubsubName:%s, Topic:%s, ID:%s\", e.PubsubName, e.Topic, e.ID)\n\n\td, ok := e.Data.([]byte)\n\tif !ok {\n\t\treturn false, errors.Errorf(\"invalid event data type: %T\", e.Data)\n\t}\n\n\tvar se SourceEvent\n\tif err := json.Unmarshal(d, &se); err != nil {\n\t\treturn false, errors.Errorf(\"error parsing input content: %v\", err)\n\t}\n\n\tvar (\n\t\tme error\n\t\tb  []byte\n\t)\n\n\tswitch strings.ToLower(targetFormat) {\n\tcase \"json\":\n\t\tb = d\n\tcase \"xml\":\n\t\tif b, me = xml.Marshal(&e); me != nil {\n\t\t\treturn false, errors.Errorf(\"error while converting content: %v\", me)\n\t\t}\n\tcase \"csv\":\n\t\tb = []byte(fmt.Sprintf(`\"%s\",%f,%f,\"%s\"`,\n\t\t\tse.ID, se.Temperature, se.Humidity, time.Unix(se.Time, 0).Format(time.RFC3339)))\n\tdefault:\n\t\treturn false, errors.Errorf(\"invalid target format: %s\", targetFormat)\n\t}\n\tlogger.Printf(\"Target (%s): %s\", targetFormat, b)\n\n\tcontent := &dapr.BindingInvocation{\n\t\tData: b,\n\t\tMetadata: map[string]string{\n\t\t\t\"record-id\":       e.ID,\n\t\t\t\"conversion-time\": time.Now().UTC().Format(time.RFC3339),\n\t\t},\n\t\tName:      targetBindingName,\n\t\tOperation: \"create\",\n\t}\n\n\tif err := client.InvokeOutputBinding(ctx, content); err != nil {\n\t\treturn true, errors.Wrap(err, \"error invoking target binding\")\n\t}\n\n\treturn false, nil\n}\n\nfunc getEnvVar(key, fallbackValue string) string {\n\tif val, ok := os.LookupEnv(key); ok {\n\t\treturn strings.TrimSpace(val)\n\t}\n\treturn fallbackValue\n}\n"
  },
  {
    "path": "fan-out/queue-event-consumer/Dockerfile",
    "content": "FROM golang:1.15.0 as builder\n\nWORKDIR /src/\nCOPY . /src/\n\nENV GO111MODULE=on\n\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \\\n    go build -a -tags netgo -mod vendor -o ./service .\n\nFROM gcr.io/distroless/static:nonroot\nCOPY --from=builder /src/service .\n\nENTRYPOINT [\"./service\"]\n"
  },
  {
    "path": "fan-out/queue-event-consumer/Makefile",
    "content": "RELEASE_VERSION  =v0.11.1\nSERVICE_NAME    ?=queue-format-consumer\nDOCKER_USERNAME ?=$(DOCKER_USER)\nTARGET_FORMAT    =xml\n\n.PHONY: all\nall: help\n\n.PHONY: tidy\ntidy: ## Updates the go modules and vendors all dependencies \n\tgo mod tidy\n\tgo mod vendor\n\n.PHONY: tidy\ntest: tidy ## Tests the entire project \n\tgo test -count=1 -race ./...\n\n.PHONY: run\nrun: tidy ## Runs uncompiled code in Dapr\n\tdapr run \\\n        --app-id $(SERVICE_NAME) \\\n        --app-port 60030 \\\n        --app-protocol grpc \\\n        --components-path ./config \\\n\t\t--log-level debug \\\n        go run main.go\n\n.PHONY: image\nimage: tidy ## Builds and publish docker image \n\tdocker build -t \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\" .\n\tdocker push \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\"\n\n.PHONY: deploy\ndeploy: ## Deploys prebuild image to k8s using currently selected context \n\tkubectl apply -f k8s/components.yaml\n\tkubectl apply -f k8s/deployment.yaml\n\tkubectl rollout restart deployment/$(SERVICE_NAME)\n\n.PHONY: lint\nlint: ## Lints the entire project \n\tgolangci-lint run --timeout=3m\n\n.PHONY: tag\ntag: ## Creates release tag \n\tgit tag $(RELEASE_VERSION)\n\tgit push origin $(RELEASE_VERSION)\n\n.PHONY: clean\nclean: ## Cleans up generated files \n\tgo clean\n\trm -fr ./bin\n\trm -fr ./vendor\n\n.PHONY: help\nhelp: ## Display available commands\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \\\n\t\t'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n"
  },
  {
    "path": "fan-out/queue-event-consumer/config/source-pabusb.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: fanout-source-pubsub\nspec:\n  type: pubsub.redis\n  metadata:\n  - name: redisHost\n    value: localhost:6379\n  - name: redisPassword\n    value: \"\""
  },
  {
    "path": "fan-out/queue-event-consumer/go.mod",
    "content": "module github.com/mchmarny/dapr-demos/fan-out/queue-format-consumer\n\ngo 1.15\n\nrequire github.com/dapr/go-sdk v0.11.0\n"
  },
  {
    "path": "fan-out/queue-event-consumer/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo=\ngithub.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=\ngolang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 h1:4X356008q5SA3YXu8PiRap39KFmy4Lf6sGlceJKZQsU=\ngolang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0 h1:uslsjIdqvZYANxSBQjTI47vZfwMaTN3mLELkMnMIY/A=\ngoogle.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=\ngoogle.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "fan-out/queue-event-consumer/k8s/deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: grpc-event-subscriber\n  labels:\n    app: grpc-event-subscriber\n    demo: grpc-event\nspec:\n  selector:\n    matchLabels:\n      app: grpc-event-subscriber\n  template:\n    metadata:\n      labels:\n        app: grpc-event-subscriber\n        demo: grpc-event\n      annotations:\n        dapr.io/enabled: \"true\"\n        dapr.io/app-id: \"grpc-event-subscriber\"\n        dapr.io/app-protocol: \"grpc\"\n        dapr.io/app-port: \"60002\"\n        dapr.io/config: \"tracing\"\n        dapr.io/log-as-json: \"true\"\n        dapr.io/log-level: \"debug\"\n    spec:\n      containers:\n      - name: service\n        image: mchmarny/grpc-event-subscriber:v0.11.1\n        ports:\n        - containerPort: 60002\n        env:\n        - name: ADDRESS\n          value: \":60002\"\n        - name: PUBSUB_NAME\n          value: \"grpc-events\"\n        - name: TOPIC_NAME\n          value: \"messages\"\n"
  },
  {
    "path": "fan-out/queue-event-consumer/k8s/target-kafka.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: fanout-queue-kafka-target\nspec:\n  type: pubsub.kafka\n  metadata:\n    - name: brokers\n      value: \"localhost:9092\"\n    - name: authRequired\n      value: \"false\"\nscopes:\n- xml-converter"
  },
  {
    "path": "fan-out/queue-event-consumer/k8s/target-redis.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: fanout-queue-kafka-target\nspec:\n  type: pubsub.kafka\n  metadata:\n    - name: brokers\n      value: \"localhost:9092\"\n    - name: authRequired\n      value: \"false\"\nscopes:\n- xml-converter"
  },
  {
    "path": "fan-out/queue-event-consumer/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/dapr/go-sdk/service/common\"\n\tdaprd \"github.com/dapr/go-sdk/service/grpc\"\n)\n\nvar (\n\tlogger           = log.New(os.Stdout, \"\", 0)\n\tserviceAddress   = getEnvVar(\"ADDRESS\", \":60030\")\n\tsourcePubSubName = getEnvVar(\"SOURCE_PUBSUB_NAME\", \"fanout-source-pubsub\")\n\tsourceTopicName  = getEnvVar(\"SOURCE_TOPIC_NAME\", \"test-topic\")\n)\n\nfunc main() {\n\t// create Dapr service\n\ts, err := daprd.NewService(serviceAddress)\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to start the server: %v\", err)\n\t}\n\n\t// add handler to the service\n\tsub := &common.Subscription{\n\t\tPubsubName: sourcePubSubName,\n\t\tTopic:      sourceTopicName,\n\t}\n\ts.AddTopicEventHandler(sub, eventHandler)\n\n\t// start the server to handle incoming events\n\tif err := s.Start(); err != nil {\n\t\tlog.Fatalf(\"server error: %v\", err)\n\t}\n}\n\nfunc eventHandler(ctx context.Context, e *common.TopicEvent) (retry bool, err error) {\n\tlogger.Printf(\"Event - PubsubName:%s, Topic:%s, ID:%s\", e.PubsubName, e.Topic, e.ID)\n\treturn false, nil\n}\n\nfunc getEnvVar(key, fallbackValue string) string {\n\tif val, ok := os.LookupEnv(key); ok {\n\t\treturn strings.TrimSpace(val)\n\t}\n\treturn fallbackValue\n}\n"
  },
  {
    "path": "fan-out/queue-event-producer/Dockerfile",
    "content": "FROM golang:1.15.0 as builder\n\nWORKDIR /src/\nCOPY . /src/\n\nENV GO111MODULE=on\n\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \\\n    go build -a -tags netgo -mod vendor -o ./service .\n\nFROM gcr.io/distroless/static:nonroot\nCOPY --from=builder /src/service .\n\nENTRYPOINT [\"./service\"]\n"
  },
  {
    "path": "fan-out/queue-event-producer/Makefile",
    "content": "RELEASE_VERSION  =v0.11.1\nSERVICE_NAME    ?=queue-event-producer\nDOCKER_USERNAME ?=$(DOCKER_USER)\n\n.PHONY: all\nall: help\n\n.PHONY: tidy\ntidy: ## Updates the go modules and vendors all dependencies \n\tgo mod tidy\n\tgo mod vendor\n\n.PHONY: tidy\ntest: tidy ## Tests the entire project \n\tgo test -count=1 -race ./...\n\n.PHONY: run\nrun: tidy ## Runs uncompiled code in Dapr (make run TARGET_FORMAT=csv)\n\tdapr run \\\n        --app-id $(SERVICE_NAME) \\\n        --app-port 60013 \\\n        --app-protocol grpc \\\n        --components-path ./config \\\n\t\t--log-level debug \\\n        go run main.go\n\n.PHONY: image\nimage: tidy ## Builds and publish docker image \n\tdocker build -t \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\" .\n\tdocker push \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\"\n\n.PHONY: deploy\ndeploy: ## Deploys prebuild image to k8s using currently selected context \n\tkubectl apply -f k8s/components.yaml\n\tkubectl apply -f k8s/deployment.yaml\n\tkubectl rollout restart deployment/$(SERVICE_NAME)\n\n.PHONY: lint\nlint: ## Lints the entire project \n\tgolangci-lint run --timeout=3m\n\n.PHONY: tag\ntag: ## Creates release tag \n\tgit tag $(RELEASE_VERSION)\n\tgit push origin $(RELEASE_VERSION)\n\n.PHONY: clean\nclean: ## Cleans up generated files \n\tgo clean\n\trm -fr ./bin\n\trm -fr ./vendor\n\n.PHONY: help\nhelp: ## Display available commands\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \\\n\t\t'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n"
  },
  {
    "path": "fan-out/queue-event-producer/config/target-pabusb.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: fanout-source-pubsub\nspec:\n  type: pubsub.redis\n  metadata:\n  - name: redisHost\n    value: localhost:6379\n  - name: redisPassword\n    value: \"\""
  },
  {
    "path": "fan-out/queue-event-producer/go.mod",
    "content": "module github.com/mchmarny/dapr-demos/fan-out/queue-event-producer\n\ngo 1.15\n\nrequire (\n\tgithub.com/dapr/go-sdk v0.11.0\n\tgithub.com/google/uuid v1.1.2\n\tgithub.com/pkg/errors v0.9.1\n)\n"
  },
  {
    "path": "fan-out/queue-event-producer/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo=\ngithub.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=\ngolang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 h1:4X356008q5SA3YXu8PiRap39KFmy4Lf6sGlceJKZQsU=\ngolang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0 h1:uslsjIdqvZYANxSBQjTI47vZfwMaTN3mLELkMnMIY/A=\ngoogle.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=\ngoogle.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "fan-out/queue-event-producer/k8s/deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: grpc-event-subscriber\n  labels:\n    app: grpc-event-subscriber\n    demo: grpc-event\nspec:\n  selector:\n    matchLabels:\n      app: grpc-event-subscriber\n  template:\n    metadata:\n      labels:\n        app: grpc-event-subscriber\n        demo: grpc-event\n      annotations:\n        dapr.io/enabled: \"true\"\n        dapr.io/app-id: \"grpc-event-subscriber\"\n        dapr.io/app-protocol: \"grpc\"\n        dapr.io/app-port: \"60002\"\n        dapr.io/config: \"tracing\"\n        dapr.io/log-as-json: \"true\"\n        dapr.io/log-level: \"debug\"\n    spec:\n      containers:\n      - name: service\n        image: mchmarny/grpc-event-subscriber:v0.11.1\n        ports:\n        - containerPort: 60002\n        env:\n        - name: ADDRESS\n          value: \":60002\"\n        - name: PUBSUB_NAME\n          value: \"grpc-events\"\n        - name: TOPIC_NAME\n          value: \"messages\"\n"
  },
  {
    "path": "fan-out/queue-event-producer/k8s/target-kafka.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: fanout-queue-kafka-target\nspec:\n  type: pubsub.kafka\n  metadata:\n    - name: brokers\n      value: \"localhost:9092\"\n    - name: authRequired\n      value: \"false\"\nscopes:\n- xml-converter"
  },
  {
    "path": "fan-out/queue-event-producer/k8s/target-redis.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: fanout-queue-kafka-target\nspec:\n  type: pubsub.kafka\n  metadata:\n    - name: brokers\n      value: \"localhost:9092\"\n    - name: authRequired\n      value: \"false\"\nscopes:\n- xml-converter"
  },
  {
    "path": "fan-out/queue-event-producer/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"log\"\n\t\"math/rand\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\tdapr \"github.com/dapr/go-sdk/client\"\n\tdaprd \"github.com/dapr/go-sdk/service/grpc\"\n\t\"github.com/google/uuid\"\n\t\"github.com/pkg/errors\"\n)\n\nvar (\n\tlogger = log.New(os.Stdout, \"\", 0)\n\n\tserviceAddress = getEnvVar(\"ADDRESS\", \":60013\")\n\n\ttargetPubSubName = getEnvVar(\"TARGET_PUBSUB_NAME\", \"fanout-source-pubsub\")\n\ttargetTopicName  = getEnvVar(\"TARGET_TOPIC_NAME\", \"events\")\n\n\tthreadFreq = getEnvVar(\"THREAD_PUB_FREQ\", \"3s\")\n)\n\nfunc main() {\n\tctx := context.Background()\n\n\ttf, err := time.ParseDuration(threadFreq)\n\tif err != nil {\n\t\tlogger.Fatalf(\"invalid thread frequency, expected duration: %s - %v\", threadFreq, err)\n\t}\n\tlogger.Printf(\"thread frequency: %s\", tf)\n\n\t// create Dapr service\n\ts, err := daprd.NewService(serviceAddress)\n\tif err != nil {\n\t\tlogger.Fatalf(\"failed to start the server: %v\", err)\n\t}\n\tdefer s.Stop()\n\n\t// dapr client\n\tc, err := dapr.NewClient()\n\tif err != nil {\n\t\tlogger.Fatalf(\"failed to create Dapr client: %v\", err)\n\t}\n\tdefer c.Close()\n\n\t// timer\n\ttimer := time.NewTicker(tf)\n\tdefer timer.Stop()\n\n\t// produce\n\tgo func() {\n\t\tif err := produce(ctx, c, timer); err != nil {\n\t\t\tlogger.Fatalf(\"error: %v\", err)\n\t\t}\n\t}()\n\n\t// start the server to handle incoming events\n\tif err := s.Start(); err != nil {\n\t\tlog.Fatalf(\"server error: %v\", err)\n\t}\n}\n\nfunc produce(ctx context.Context, c dapr.Client, t *time.Ticker) error {\n\tfor {\n\t\tselect {\n\t\tcase <-t.C:\n\t\t\tb, err := json.Marshal(getRoomReading())\n\t\t\tif err != nil {\n\t\t\t\treturn errors.Wrap(err, \"error serializing reading\")\n\t\t\t}\n\t\t\tif err := c.PublishEvent(ctx, targetPubSubName, targetTopicName, b); err != nil {\n\t\t\t\treturn errors.Wrap(err, \"error publishing content\")\n\t\t\t}\n\t\t\tlogger.Printf(\"published: %s\", b)\n\t\t}\n\t}\n}\n\ntype roomReading struct {\n\tID          string  `json:\"id\"`\n\tTemperature float64 `json:\"temperature\"`\n\tHumidity    float64 `json:\"humidity\"`\n\tTime        int64   `json:\"time\"`\n}\n\nfunc getRoomReading() interface{} {\n\tmin := 0.01\n\tmax := 100.00\n\treturn &roomReading{\n\t\tID:          uuid.New().String(),\n\t\tTemperature: min + rand.Float64()*(max-min),\n\t\tHumidity:    min + rand.Float64()*(max-min),\n\t\tTime:        time.Now().UTC().Unix(),\n\t}\n}\n\nfunc getEnvVar(key, fallbackValue string) string {\n\tif val, ok := os.LookupEnv(key); ok {\n\t\treturn strings.TrimSpace(val)\n\t}\n\treturn fallbackValue\n}\n"
  },
  {
    "path": "fan-out/queue-format-converter/Dockerfile",
    "content": "FROM golang:1.15.0 as builder\n\nWORKDIR /src/\nCOPY . /src/\n\nENV GO111MODULE=on\n\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \\\n    go build -a -tags netgo -mod vendor -o ./service .\n\nFROM gcr.io/distroless/static:nonroot\nCOPY --from=builder /src/service .\n\nENTRYPOINT [\"./service\"]\n"
  },
  {
    "path": "fan-out/queue-format-converter/Makefile",
    "content": "RELEASE_VERSION  =v0.11.1\nSERVICE_NAME    ?=queue-format-converter\nDOCKER_USERNAME ?=$(DOCKER_USER)\nTARGET_FORMAT    =xml\n\n.PHONY: all\nall: help\n\n.PHONY: tidy\ntidy: ## Updates the go modules and vendors all dependencies \n\tgo mod tidy\n\tgo mod vendor\n\n.PHONY: tidy\ntest: tidy ## Tests the entire project \n\tgo test -count=1 -race ./...\n\n.PHONY: run\nrun: tidy ## Runs uncompiled code in Dapr (make run TARGET_FORMAT=csv)\n\tTARGET_TOPIC_FORMAT=$(TARGET_FORMAT) dapr run \\\n        --app-id $(SERVICE_NAME) \\\n        --app-port 60010 \\\n        --app-protocol grpc \\\n        --components-path ./config \\\n\t\t--log-level debug \\\n        go run main.go\n\n.PHONY: image\nimage: tidy ## Builds and publish docker image \n\tdocker build -t \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\" .\n\tdocker push \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\"\n\n.PHONY: deploy\ndeploy: ## Deploys prebuild image to k8s using currently selected context \n\tkubectl apply -f k8s/components.yaml\n\tkubectl apply -f k8s/deployment.yaml\n\tkubectl rollout restart deployment/$(SERVICE_NAME)\n\n.PHONY: lint\nlint: ## Lints the entire project \n\tgolangci-lint run --timeout=3m\n\n.PHONY: tag\ntag: ## Creates release tag \n\tgit tag $(RELEASE_VERSION)\n\tgit push origin $(RELEASE_VERSION)\n\n.PHONY: clean\nclean: ## Cleans up generated files \n\tgo clean\n\trm -fr ./bin\n\trm -fr ./vendor\n\n.PHONY: help\nhelp: ## Display available commands\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \\\n\t\t'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n"
  },
  {
    "path": "fan-out/queue-format-converter/config/kafka.yaml",
    "content": "version: '2'\nservices:\n  zookeeper:\n    image: wurstmeister/zookeeper:latest\n    ports:\n      - \"2181:2181\"\n  kafka:\n    image: wurstmeister/kafka:latest\n    ports:\n      - \"9092:9092\"\n    environment:\n      KAFKA_ADVERTISED_HOST_NAME: 127.0.0.1\n      KAFKA_CREATE_TOPICS: \"events:1:1\"\n      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181"
  },
  {
    "path": "fan-out/queue-format-converter/config/source-pabusb.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: fanout-source-pubsub\nspec:\n  type: pubsub.redis\n  metadata:\n  - name: redisHost\n    value: localhost:6379\n  - name: redisPassword\n    value: \"\""
  },
  {
    "path": "fan-out/queue-format-converter/config/target-pubsub.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: fanout-target-pubsub\nspec:\n  type: pubsub.kafka\n  metadata:\n    - name: brokers\n      value: \"localhost:9092\"\n    - name: authRequired\n      value: \"false\""
  },
  {
    "path": "fan-out/queue-format-converter/go.mod",
    "content": "module github.com/mchmarny/dapr-demos/fan-out/queue-format-converter\n\ngo 1.15\n\nrequire (\n\tgithub.com/dapr/go-sdk v0.11.0\n\tgithub.com/pkg/errors v0.9.1\n)\n"
  },
  {
    "path": "fan-out/queue-format-converter/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo=\ngithub.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=\ngolang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 h1:4X356008q5SA3YXu8PiRap39KFmy4Lf6sGlceJKZQsU=\ngolang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0 h1:uslsjIdqvZYANxSBQjTI47vZfwMaTN3mLELkMnMIY/A=\ngoogle.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=\ngoogle.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "fan-out/queue-format-converter/k8s/deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: grpc-event-subscriber\n  labels:\n    app: grpc-event-subscriber\n    demo: grpc-event\nspec:\n  selector:\n    matchLabels:\n      app: grpc-event-subscriber\n  template:\n    metadata:\n      labels:\n        app: grpc-event-subscriber\n        demo: grpc-event\n      annotations:\n        dapr.io/enabled: \"true\"\n        dapr.io/app-id: \"grpc-event-subscriber\"\n        dapr.io/app-protocol: \"grpc\"\n        dapr.io/app-port: \"60002\"\n        dapr.io/config: \"tracing\"\n        dapr.io/log-as-json: \"true\"\n        dapr.io/log-level: \"debug\"\n    spec:\n      containers:\n      - name: service\n        image: mchmarny/grpc-event-subscriber:v0.11.1\n        ports:\n        - containerPort: 60002\n        env:\n        - name: ADDRESS\n          value: \":60002\"\n        - name: PUBSUB_NAME\n          value: \"grpc-events\"\n        - name: TOPIC_NAME\n          value: \"messages\"\n"
  },
  {
    "path": "fan-out/queue-format-converter/k8s/target-kafka.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: fanout-queue-kafka-target\nspec:\n  type: pubsub.kafka\n  metadata:\n    - name: brokers\n      value: \"localhost:9092\"\n    - name: authRequired\n      value: \"false\"\nscopes:\n- xml-converter"
  },
  {
    "path": "fan-out/queue-format-converter/k8s/target-redis.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: fanout-queue-kafka-target\nspec:\n  type: pubsub.kafka\n  metadata:\n    - name: brokers\n      value: \"localhost:9092\"\n    - name: authRequired\n      value: \"false\"\nscopes:\n- xml-converter"
  },
  {
    "path": "fan-out/queue-format-converter/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"encoding/xml\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\tdapr \"github.com/dapr/go-sdk/client\"\n\t\"github.com/dapr/go-sdk/service/common\"\n\tdaprd \"github.com/dapr/go-sdk/service/grpc\"\n\t\"github.com/pkg/errors\"\n)\n\nvar (\n\tlogger = log.New(os.Stdout, \"\", 0)\n\tclient dapr.Client\n\n\tserviceAddress = getEnvVar(\"ADDRESS\", \":60010\")\n\n\tsourcePubSubName = getEnvVar(\"SOURCE_PUBSUB_NAME\", \"fanout-source-pubsub\")\n\tsourceTopicName  = getEnvVar(\"SOURCE_TOPIC_NAME\", \"events\")\n\n\ttargetPubSubName  = getEnvVar(\"TARGET_PUBSUB_NAME\", \"fanout-target-pubsub\")\n\ttargetTopicName   = getEnvVar(\"TARGET_TOPIC_NAME\", \"events\")\n\ttargetTopicFormat = getEnvVar(\"TARGET_TOPIC_FORMAT\", \"csv\")\n)\n\nfunc main() {\n\t// create Dapr service\n\ts, err := daprd.NewService(serviceAddress)\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to start the server: %v\", err)\n\t}\n\n\tc, err := dapr.NewClient()\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to create Dapr client: %v\", err)\n\t}\n\tclient = c\n\tdefer client.Close()\n\n\t// add handler to the service\n\tsub := &common.Subscription{PubsubName: sourcePubSubName, Topic: sourceTopicName}\n\ts.AddTopicEventHandler(sub, eventHandler)\n\n\t// start the server to handle incoming events\n\tif err := s.Start(); err != nil {\n\t\tlog.Fatalf(\"server error: %v\", err)\n\t}\n}\n\n// SourceEvent represents the input event\ntype SourceEvent struct {\n\tID          string  `json:\"id\"`\n\tTemperature float64 `json:\"temperature\"`\n\tHumidity    float64 `json:\"humidity\"`\n\tTime        int64   `json:\"time\"`\n}\n\nfunc eventHandler(ctx context.Context, e *common.TopicEvent) (retry bool, err error) {\n\tlogger.Printf(\"Event - PubsubName:%s, Topic:%s, ID:%s\", e.PubsubName, e.Topic, e.ID)\n\n\td, ok := e.Data.([]byte)\n\tif !ok {\n\t\treturn false, errors.Errorf(\"invalid event data type: %T\", e.Data)\n\t}\n\n\tvar se SourceEvent\n\tif err := json.Unmarshal(d, &se); err != nil {\n\t\treturn false, errors.Errorf(\"error parsing input content: %v\", err)\n\t}\n\n\tvar (\n\t\tme error\n\t\tb  []byte\n\t)\n\n\tswitch strings.ToLower(targetTopicFormat) {\n\tcase \"json\":\n\t\tb = d\n\tcase \"xml\":\n\t\tif b, me = xml.Marshal(&e); me != nil {\n\t\t\treturn false, errors.Errorf(\"error while converting content: %v\", me)\n\t\t}\n\tcase \"csv\":\n\t\tb = []byte(fmt.Sprintf(`\"%s\",%f,%f,\"%s\"`,\n\t\t\tse.ID, se.Temperature, se.Humidity, time.Unix(se.Time, 0).Format(time.RFC3339)))\n\tdefault:\n\t\treturn false, errors.Errorf(\"invalid target format: %s\", targetTopicFormat)\n\t}\n\tlogger.Printf(\"Target (%s): %s\", targetTopicFormat, b)\n\n\tif err := client.PublishEvent(ctx, targetPubSubName, targetTopicName, b); err != nil {\n\t\treturn true, errors.Wrap(err, \"error publishing converted content\")\n\t}\n\n\treturn false, nil\n}\n\nfunc getEnvVar(key, fallbackValue string) string {\n\tif val, ok := os.LookupEnv(key); ok {\n\t\treturn strings.TrimSpace(val)\n\t}\n\treturn fallbackValue\n}\n"
  },
  {
    "path": "fan-out/service-format-converter/Dockerfile",
    "content": "FROM golang:1.15.0 as builder\n\nWORKDIR /src/\nCOPY . /src/\n\nENV GO111MODULE=on\n\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \\\n    go build -a -tags netgo -mod vendor -o ./service .\n\nFROM gcr.io/distroless/static:nonroot\nCOPY --from=builder /src/service .\n\nENTRYPOINT [\"./service\"]\n"
  },
  {
    "path": "fan-out/service-format-converter/Makefile",
    "content": "RELEASE_VERSION  =v0.11.1\nSERVICE_NAME    ?=service-format-converter\nDOCKER_USERNAME ?=$(DOCKER_USER)\nTARGET_FORMAT    =xml\nTARGET_SERVICE   =grpc-echo-service\nTARGET_METHOD    =echo\n\n.PHONY: all\nall: help\n\n.PHONY: tidy\ntidy: ## Updates the go modules and vendors all dependencies \n\tgo mod tidy\n\tgo mod vendor\n\n.PHONY: tidy\ntest: tidy ## Tests the entire project \n\tgo test -count=1 -race ./...\n\n.PHONY: run\nrun: tidy ## Runs uncompiled code in Dapr (make run TARGET_FORMAT=csv)\n\tTARGET_FORMAT=$(TARGET_FORMAT) \\\n\tTARGET_SERVICE=$(TARGET_SERVICE) \\\n\tTARGET_METHOD=$(TARGET_METHOD) \\\n\tdapr run \\\n        --app-id $(SERVICE_NAME) \\\n        --app-port 60012 \\\n        --app-protocol grpc \\\n        --components-path ./config \\\n\t\t--log-level debug \\\n        go run main.go\n\n.PHONY: image\nimage: tidy ## Builds and publish docker image \n\tdocker build -t \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\" .\n\tdocker push \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\"\n\n.PHONY: deploy\ndeploy: ## Deploys prebuild image to k8s using currently selected context \n\tkubectl apply -f k8s/components.yaml\n\tkubectl apply -f k8s/deployment.yaml\n\tkubectl rollout restart deployment/$(SERVICE_NAME)\n\n.PHONY: lint\nlint: ## Lints the entire project \n\tgolangci-lint run --timeout=3m\n\n.PHONY: tag\ntag: ## Creates release tag \n\tgit tag $(RELEASE_VERSION)\n\tgit push origin $(RELEASE_VERSION)\n\n.PHONY: clean\nclean: ## Cleans up generated files \n\tgo clean\n\trm -fr ./bin\n\trm -fr ./vendor\n\n.PHONY: help\nhelp: ## Display available commands\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \\\n\t\t'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n"
  },
  {
    "path": "fan-out/service-format-converter/config/source-pubsub.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: fanout-source-pubsub\nspec:\n  type: pubsub.redis\n  metadata:\n  - name: redisHost\n    value: localhost:6379\n  - name: redisPassword\n    value: \"\""
  },
  {
    "path": "fan-out/service-format-converter/go.mod",
    "content": "module github.com/mchmarny/dapr-demos/fan-out/service-format-converter\n\ngo 1.15\n\nrequire (\n\tgithub.com/dapr/go-sdk v0.11.0\n\tgithub.com/pkg/errors v0.9.1\n)\n"
  },
  {
    "path": "fan-out/service-format-converter/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo=\ngithub.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=\ngolang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 h1:4X356008q5SA3YXu8PiRap39KFmy4Lf6sGlceJKZQsU=\ngolang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0 h1:uslsjIdqvZYANxSBQjTI47vZfwMaTN3mLELkMnMIY/A=\ngoogle.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=\ngoogle.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "fan-out/service-format-converter/k8s/components.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: grpc-events\nspec:\n  type: pubsub.redis\n  metadata:\n  - name: redisHost\n    value: redis-master.redis.svc.cluster.local:6379\n  - name: redisPassword\n    secretKeyRef:\n      name: redis-secret\n      key: password\n  - name: allowedTopics\n    value: \"messages\"\nscopes:\n- xml-converter"
  },
  {
    "path": "fan-out/service-format-converter/k8s/deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: grpc-event-subscriber\n  labels:\n    app: grpc-event-subscriber\n    demo: grpc-event\nspec:\n  selector:\n    matchLabels:\n      app: grpc-event-subscriber\n  template:\n    metadata:\n      labels:\n        app: grpc-event-subscriber\n        demo: grpc-event\n      annotations:\n        dapr.io/enabled: \"true\"\n        dapr.io/app-id: \"grpc-event-subscriber\"\n        dapr.io/app-protocol: \"grpc\"\n        dapr.io/app-port: \"60002\"\n        dapr.io/config: \"tracing\"\n        dapr.io/log-as-json: \"true\"\n        dapr.io/log-level: \"debug\"\n    spec:\n      containers:\n      - name: service\n        image: mchmarny/grpc-event-subscriber:v0.11.1\n        ports:\n        - containerPort: 60002\n        env:\n        - name: ADDRESS\n          value: \":60002\"\n        - name: PUBSUB_NAME\n          value: \"grpc-events\"\n        - name: TOPIC_NAME\n          value: \"messages\"\n"
  },
  {
    "path": "fan-out/service-format-converter/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"encoding/xml\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\tdapr \"github.com/dapr/go-sdk/client\"\n\t\"github.com/dapr/go-sdk/service/common\"\n\tdaprd \"github.com/dapr/go-sdk/service/grpc\"\n\t\"github.com/pkg/errors\"\n)\n\nvar (\n\tlogger = log.New(os.Stdout, \"\", 0)\n\tclient dapr.Client\n\n\tserviceAddress = getEnvVar(\"ADDRESS\", \":60012\")\n\n\tsourcePubSubName = getEnvVar(\"SOURCE_PUBSUB_NAME\", \"fanout-source-pubsub\")\n\tsourceTopicName  = getEnvVar(\"SOURCE_TOPIC_NAME\", \"events\")\n\n\ttargetServiceID  = getEnvVar(\"TARGET_SERVICE\", \"grpc-echo-service\")\n\ttargetMethodName = getEnvVar(\"TARGET_METHOD\", \"echo\")\n\ttargetFormat     = getEnvVar(\"TARGET_FORMAT\", \"xml\")\n)\n\nfunc main() {\n\t// create Dapr service\n\ts, err := daprd.NewService(serviceAddress)\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to start the server: %v\", err)\n\t}\n\n\tc, err := dapr.NewClient()\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to create Dapr client: %v\", err)\n\t}\n\tclient = c\n\tdefer client.Close()\n\n\t// add handler to the service\n\tsub := &common.Subscription{PubsubName: sourcePubSubName, Topic: sourceTopicName}\n\ts.AddTopicEventHandler(sub, eventHandler)\n\n\t// start the server to handle incoming events\n\tif err := s.Start(); err != nil {\n\t\tlog.Fatalf(\"server error: %v\", err)\n\t}\n}\n\n// SourceEvent represents the input event\ntype SourceEvent struct {\n\tID          string  `json:\"id\"`\n\tTemperature float64 `json:\"temperature\"`\n\tHumidity    float64 `json:\"humidity\"`\n\tTime        int64   `json:\"time\"`\n}\n\nfunc eventHandler(ctx context.Context, e *common.TopicEvent) (retry bool, err error) {\n\tlogger.Printf(\"Event - PubsubName:%s, Topic:%s, ID:%s\", e.PubsubName, e.Topic, e.ID)\n\n\td, ok := e.Data.([]byte)\n\tif !ok {\n\t\treturn false, errors.Errorf(\"invalid event data type: %T\", e.Data)\n\t}\n\n\tvar se SourceEvent\n\tif err := json.Unmarshal(d, &se); err != nil {\n\t\treturn false, errors.Errorf(\"error parsing input content: %v\", err)\n\t}\n\n\tvar (\n\t\tme error\n\t\tb  []byte\n\t\tct string\n\t)\n\n\tswitch strings.ToLower(targetFormat) {\n\tcase \"json\":\n\t\tb = d\n\t\tct = \"application/json\"\n\tcase \"xml\":\n\t\tif b, me = xml.Marshal(&e); me != nil {\n\t\t\treturn false, errors.Errorf(\"error while converting content: %v\", me)\n\t\t}\n\t\tct = \"application/xml\"\n\tcase \"csv\":\n\t\tb = []byte(fmt.Sprintf(`\"%s\",%f,%f,\"%s\"`,\n\t\t\tse.ID, se.Temperature, se.Humidity, time.Unix(se.Time, 0).Format(time.RFC3339)))\n\t\tct = \"text/csv\"\n\tdefault:\n\t\treturn false, errors.Errorf(\"invalid target format: %s\", targetFormat)\n\t}\n\tlogger.Printf(\"Target (%s): %s\", targetFormat, b)\n\n\tcontent := &dapr.DataContent{Data: b, ContentType: ct}\n\tout, err := client.InvokeServiceWithContent(ctx, targetServiceID, targetMethodName, content)\n\tif err != nil {\n\t\treturn true, errors.Wrap(err, \"error invoking target binding\")\n\t}\n\tlogger.Printf(\"Response: %s\", out)\n\n\treturn false, nil\n}\n\nfunc getEnvVar(key, fallbackValue string) string {\n\tif val, ok := os.LookupEnv(key); ok {\n\t\treturn strings.TrimSpace(val)\n\t}\n\treturn fallbackValue\n}\n"
  },
  {
    "path": "grpc-echo-service/Dockerfile",
    "content": "FROM golang:1.15.5 as builder\n\nWORKDIR /src/\nCOPY . /src/\n\nENV GO111MODULE=on\n\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \\\n    go build -a -tags netgo -mod vendor -o ./app .\n\nFROM gcr.io/distroless/static:nonroot\nCOPY --from=builder /src/app .\n\nENTRYPOINT [\"./app\"]\n"
  },
  {
    "path": "grpc-echo-service/Makefile",
    "content": "RELEASE_VERSION  =v1.0.1\nSERVICE_NAME    ?=echo\nIMAGE_NAME      ?=grpc-echo-service\nIMAGE_TAG       ?=v0.1.4\nIMAGE_OWNER     ?=$(shell git config --get user.username)\n\n.PHONY: all\nall: help\n\n.PHONY: tidy\ntidy: ## Updates the go modules and vendors all dependencies \n\tgo mod tidy\n\tgo mod vendor\n\n.PHONY: test\ntest: tidy ## Tests the entire project \n\tgo test -count=1 -race ./...\n\n.PHONY: run\nrun: tidy ## Runs uncompiled code in Dapr\n\tdapr run \\\n\t\t--app-id $(SERVICE_NAME) \\\n\t\t--app-port 60002 \\\n\t\t--app-protocol grpc \\\n\t\t--dapr-http-port 3500 \\\n        go run main.go\n\n.PHONY: invoke\ninvoke: ## Invokes service through Dapr API \n\tcurl -d '{ \"message\": \"ping\" }' \\\n     -H \"Content-type: application/json\" \\\n     \"http://localhost:3500/v1.0/invoke/$(SERVICE_NAME)/method/echo\"\n\n.PHONY: image\nimage: tidy ## Builds and publish image \n\tdocker build -t \"ghcr.io/$(IMAGE_OWNER)/$(IMAGE_NAME):$(IMAGE_TAG)\" .\n\tdocker push \"ghcr.io/$(IMAGE_OWNER)/$(IMAGE_NAME):$(IMAGE_TAG)\"\n\n.PHONY: call\ncall: ## Invokes service through Dapr API \n\t$(eval API_TOKEN=$(shell kubectl get secret dapr-api-token -o jsonpath=\"{.data.token}\" | base64 --decode))\n\tcurl -d '{ \"message\": \"ping\" }' \\\n     -H \"Content-type: application/json\" \\\n     -H \"dapr-api-token: $(API_TOKEN)\" \\\n     \"https://api.cloudylabs.dev/v1.0/invoke/$(SERVICE_NAME)/method/echo\"\n\n.PHONY: lint\nlint: ## Lints the entire project \n\tgolangci-lint run --timeout=3m\n\n.PHONY: tag\ntag: ## Creates release tag \n\tgit tag $(RELEASE_VERSION)\n\tgit push origin $(RELEASE_VERSION)\n\n.PHONY: clean\nclean: ## Cleans up generated files \n\tgo clean\n\trm -fr ./bin\n\trm -fr ./vendor\n\n.PHONY: help\nhelp: ## Display available commands\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \\\n\t\t'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n"
  },
  {
    "path": "grpc-echo-service/README.md",
    "content": "# grpc-service\n\nFor more information about service invocation see the [Dapr docs](https://github.com/dapr/docs/tree/master/concepts/service-invocation)\n\n> You can replicate this demo on any Kubernetes cluster configured with Dapr. To demo the cross-namespace service invocation with external API gateway you will need \"dapr'ized' cluster ingress (ingress with Dapr sidecar). You can setup fully configured Dapr cluster with all these dependencies using included [Dapr cluster setup](../setup#dapr-cluster-setup).\n\n## Run \n\nTo run this demo in Dapr, run:\n\n```shell\ndapr run \\\n    --app-id echo \\\n    --app-port 50001 \\\n    --app-protocol grpc \\\n    --dapr-http-port 3500 \\\n    --components-path ./config \\\n    go run main.go\n```\n\n## Deploy\n\nTo deploy this demo, first setup the `echo` namespace:\n\n```shell\nkubectl apply -f deployment/space.yaml\n```\n\nThen deploy and wait for the `echo-service` app pod to be ready:\n\n```shell\nkubectl apply -f deployment/app.yaml\nkubectl rollout status deployment/echo-service -n echo \n```\n\nIf you have changed an existing component, make sure to reload the ingress and wait until the new version is ready\n\n```shell\nkubectl rollout restart deployment/echo-service -n echo \nkubectl rollout status deployment/echo-service -n echo \n```\n\nFollow logs\n\n```shell\nkubectl logs -l app=echo-service -c service -f -n echo\n```\n\nIn a separate terminal session export API token\n\n```shell\nexport API_TOKEN=$(kubectl get secret dapr-api-token -n nginx -o jsonpath=\"{.data.token}\" | base64 --decode)\n```\n\nAnd invoke the service\n\n```shell\ncurl -d '{ \"message\": \"ping\" }' \\\n     -H \"Content-type: application/json\" \\\n     -H \"dapr-api-token: ${API_TOKEN}\" \\\n     \"https://api.demo.dapr.team/v1.0/invoke/echo-service.echo/method/echo\"\n```\n\n> Notice the use of `echo-service.echo` namespace in the service invocation \n\nThe response should include the sent message \n\n```json\n{ \n    \"message\": \"ping\" \n}\n```\n\nAnd the logs\n\n```shell\nInvocation (ContentType:application/json, Verb:POST, QueryString:map[], Data:{ \"message\": \"ping\" })\n```\n\n## Disclaimer\n\nThis is my personal project and it does not represent my employer. While I do my best to ensure that everything works, I take no responsibility for issues caused by this code.\n\n## License\n\nThis software is released under the [MIT](../LICENSE)\n"
  },
  {
    "path": "grpc-echo-service/deployment/app.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: echo-service\n  namespace: echo\n  labels:\n    app: echo-service\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: echo-service\n  template:\n    metadata:\n      labels:\n        app: echo-service\n      annotations:\n        dapr.io/enabled: \"true\"\n        dapr.io/app-id: \"echo-service\"\n        dapr.io/app-protocol: \"grpc\"\n        dapr.io/app-port: \"60002\"\n        dapr.io/config: \"echo-config\"\n        dapr.io/log-as-json: \"true\"\n        dapr.io/log-level: \"debug\"\n    spec:\n      containers:\n      - name: service\n        image: ghcr.io/mchmarny/grpc-echo-service:v0.1.4\n        ports:\n        - containerPort: 60002\n        env:\n        - name: ADDRESS\n          value: \":60002\""
  },
  {
    "path": "grpc-echo-service/deployment/space.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  name: echo\n---\napiVersion: dapr.io/v1alpha1\nkind: Configuration\nmetadata:\n  name: echo-config\n  namespace: echo\nspec:\n  tracing:\n    samplingRate: \"1\"\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: secret-reader\n  namespace: echo\nrules:\n- apiGroups: [\"\"]\n  resources: [\"secrets\"]\n  verbs: [\"get\"]\n---\nkind: RoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: dapr-secret-reader\n  namespace: echo\nsubjects:\n- kind: ServiceAccount\n  name: default\nroleRef:\n  kind: Role\n  name: secret-reader\n  apiGroup: rbac.authorization.k8s.io\n---\napiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: zipkin\n  namespace: echo\nspec:\n  type: exporters.zipkin\n  metadata:\n  - name: enabled\n    value: \"true\"\n  - name: exporterAddress\n    value: \"http://zipkin.dapr-monitoring.svc.cluster.local:9411/api/v2/spans\"\n"
  },
  {
    "path": "grpc-echo-service/go.mod",
    "content": "module github.com/mchmarny/dapr-demos/grpc-echo-service\n\ngo 1.15\n\nrequire (\n\tgithub.com/dapr/go-sdk v1.0.0-rc-1\n\tgolang.org/x/sys v0.0.0-20201116194326-cc9327a14d48 // indirect\n\tgoogle.golang.org/genproto v0.0.0-20201116205149-79184cff4dfe // indirect\n)\n"
  },
  {
    "path": "grpc-echo-service/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/dapr/go-sdk v1.0.0-rc-1 h1:aBkgeQfrE9VAabvwL094z4RJWwt41gNKAusE2JoOrB4=\ngithub.com/dapr/go-sdk v1.0.0-rc-1/go.mod h1:A53nH+Wc8ahU9+A2Eddz11916eE6opTmwfxAajL0bYo=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=\ngolang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201116194326-cc9327a14d48 h1:AYCWBZhgIw6XobZ5CibNJr0Rc4ZofGGKvWa1vcx2IGk=\ngolang.org/x/sys v0.0.0-20201116194326-cc9327a14d48/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=\ngolang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20201112120144-2985b7af83de/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201116205149-79184cff4dfe h1:4zuxUDBUB7MD0/Nb3BD2e2+YHXs2BQxZ9ivB5RJd7ng=\ngoogle.golang.org/genproto v0.0.0-20201116205149-79184cff4dfe/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.33.2 h1:EQyQC3sa8M+p6Ulc8yy9SWSS2GVwyRc83gAbG8lrl4o=\ngoogle.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "grpc-echo-service/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/dapr/go-sdk/service/common\"\n\tdaprd \"github.com/dapr/go-sdk/service/grpc\"\n)\n\nvar (\n\tlogger         = log.New(os.Stdout, \"\", 0)\n\tserviceAddress = getEnvVar(\"ADDRESS\", \":60002\")\n)\n\nfunc main() {\n\t// create serving server\n\ts, err := daprd.NewService(serviceAddress)\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to start the server: %v\", err)\n\t}\n\n\t// add handler to the service\n\ts.AddServiceInvocationHandler(\"echo\", echoHandler)\n\n\t// start the server to handle incoming events\n\tlog.Printf(\"starting server at %s...\", serviceAddress)\n\tif err := s.Start(); err != nil {\n\t\tlog.Fatalf(\"server error: %v\", err)\n\t}\n}\n\nfunc echoHandler(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) {\n\tlogger.Printf(\n\t\t\"Invocation (ContentType:%s, Verb:%s, QueryString:%s, Data:%s)\",\n\t\tin.ContentType, in.Verb, in.QueryString, string(in.Data),\n\t)\n\n\t// TODO: implement handling logic here\n\tout = &common.Content{\n\t\tContentType: in.ContentType,\n\t\tData:        in.Data,\n\t}\n\n\treturn\n}\n\nfunc getEnvVar(key, fallbackValue string) string {\n\tif val, ok := os.LookupEnv(key); ok {\n\t\treturn strings.TrimSpace(val)\n\t}\n\treturn fallbackValue\n}\n"
  },
  {
    "path": "grpc-event-subscriber/Dockerfile",
    "content": "FROM golang:1.15.0 as builder\n\nWORKDIR /src/\nCOPY . /src/\n\nENV GO111MODULE=on\n\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \\\n    go build -a -tags netgo -mod vendor -o ./service .\n\nFROM gcr.io/distroless/static:nonroot\nCOPY --from=builder /src/service .\n\nENTRYPOINT [\"./service\"]\n"
  },
  {
    "path": "grpc-event-subscriber/Makefile",
    "content": "RELEASE_VERSION  =v0.11.1\nSERVICE_NAME    ?=grpc-event-subscriber\nDOCKER_USERNAME ?=$(DOCKER_USER)\n\n.PHONY: tidy test debug build run jsonevent xmlevent binevent image lint clean tag\nall: help\n\ntidy: ## Updates the go modules and vendors all dependencies \n\tgo mod tidy\n\tgo mod vendor\n\ntest: tidy ## Tests the entire project \n\tgo test -count=1 -race ./...\n\ndebug: tidy ## Runs uncompiled code in Dapr\n\tdapr run \\\n        --app-id $(SERVICE_NAME) \\\n        --app-port 60002 \\\n        --app-protocol grpc \\\n        --dapr-http-port 3500 \\\n        --components-path ./config \\\n        go run main.go\n\nbuild: tidy ## Builds local release binary\n\tCGO_ENABLED=0 go build -a -tags netgo -mod vendor -o bin/$(SERVICE_NAME) .\n\nrun: build ## Builds binary and runs it in Dapr\n\tdapr run \\\n        --app-id $(SERVICE_NAME) \\\n        --app-port 60002 \\\n        --app-protocol grpc \\\n        --dapr-http-port 3500 \\\n        --components-path ./config \\\n        bin/$(SERVICE_NAME) \n\njsonevent: ## Publishes sample JSON message to Dapr pubsub API \n\tcurl -d '{ \"from\": \"John\", \"to\": \"Lary\", \"message\": \"hi\" }' \\\n     -H \"Content-type: application/json\" \\\n     \"http://localhost:3500/v1.0/publish/grpc-events/messages\"\n\nxmlevent: ## Publishes sample XML message to Dapr pubsub API \n\tcurl -d '<message><from>John</from><to>Lary</to></message>' \\\n     -H \"Content-type: application/xml\" \\\n     \"http://localhost:3500/v1.0/publish/grpc-events/messages\"\n\nbinevent: ## Publishes sample binary message to Dapr pubsub API \n\tcurl -d '0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40' \\\n     -H \"Content-type: application/octet-stream\" \\\n     \"http://localhost:3500/v1.0/publish/grpc-events/messages\"\n\nimage: tidy ## Builds and publish docker image \n\tdocker build -t \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\" .\n\tdocker push \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\"\n\ndeploy: ## Deploys prebuild image to k8s using currently selected context \n\tkubectl apply -f k8s/component.yaml\n\tkubectl apply -f k8s/deployment.yaml\n\tkubectl rollout restart deployment/grpc-event-subscriber\n\tkubectl rollout restart deployment/nginx-ingress-nginx-controller\n\tkubectl rollout status deployment/nginx-ingress-nginx-controller\n\nevent: ## Publishes sample JSON message to Dapr pubsub API \n\t$(eval API_TOKEN=$(shell kubectl get secret dapr-api-token -o jsonpath=\"{.data.token}\" | base64 --decode))\n\tcurl -d '{ \"from\": \"John\", \"to\": \"Lary\", \"message\": \"hi\" }' \\\n     -H \"Content-type: application/json\" \\\n     -H \"dapr-api-token: $(API_TOKEN)\" \\\n     \"https://api.cloudylabs.dev/v1.0/publish/grpc-events/messages\"\n\nlint: ## Lints the entire project \n\tgolangci-lint run --timeout=3m\n\ntag: ## Creates release tag \n\tgit tag $(RELEASE_VERSION)\n\tgit push origin $(RELEASE_VERSION)\n\nclean: ## Cleans up generated files \n\tgo clean\n\trm -fr ./bin\n\trm -fr ./vendor\n\nhelp: ## Display available commands\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \\\n\t\t'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n"
  },
  {
    "path": "grpc-event-subscriber/README.md",
    "content": "# grpc-event-subscriber\n\n## Components\n\n```yaml\napiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: events\nspec:\n  type: pubsub.redis\n  metadata:\n  - name: redisHost\n    value: localhost:6379\n  - name: redisPassword\n    value: \"\"\n\n```\n\nFor more information about pub/sub see the [Dapr docs](https://github.com/dapr/docs/tree/master/concepts/publish-subscribe-messaging)\n\n## Run \n\nTo run this demo in Dapr, run:\n\n```shell\ndapr run --app-id grpc-event-subscriber-demo \\\n             --app-port 50001 \\\n             --app-protocol grpc \\\n             --dapr-http-port 3500 \\\n             --components-path ./config \\\n             go run main.go\n```\n\n## Deploy\n\nDeploy and wait for the pod to be ready \n\n```shell\nkubectl apply -f k8s/component.yaml\nkubectl apply -f k8s/deployment.yaml\nkubectl rollout status deployment/grpc-event-subscriber\n```\n\nIf you have changed an existing component, make sure to reload the deployment and wait until the new version is ready\n\n```shell\nkubectl rollout restart deployment/nginx-ingress-nginx-controller\nkubectl rollout status deployment/nginx-ingress-nginx-controller\n```\n\nFollow logs\n\n```shell\nkubectl logs -l app=grpc-event-subscriber -c service -f\n```\n\nIn a separate terminal session export API token\n\n```shell\nexport API_TOKEN=$(kubectl get secret dapr-api-token -o jsonpath=\"{.data.token}\" | base64 --decode)\n```\n\nAnd invoke the service\n\n```shell\ncurl -d '{ \"from\": \"John\", \"to\": \"Lary\", \"message\": \"hi\" }' \\\n     -H \"Content-type: application/json\" \\\n     -H \"dapr-api-token: ${API_TOKEN}\" \\\n     \"https://api.cloudylabs.dev/v1.0/publish/grpc-events/messages\"\n```\n\nIn the logs, you should see now an entry similar to this. Feel free to edit the message and try again.\n\n```shell\nevent - PubsubName:http-events, Topic:messages, ID:6b6cc665-684d-456c-8880-56e20cdf0519, Data: map[from:John message:hi to:Lary]\n```\n\n## Disclaimer\n\nThis is my personal project and it does not represent my employer. While I do my best to ensure that everything works, I take no responsibility for issues caused by this code.\n\n## License\n\nThis software is released under the [MIT](../LICENSE)\n"
  },
  {
    "path": "grpc-event-subscriber/config/pubsub.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: grpc-events\nspec:\n  type: pubsub.redis\n  metadata:\n  - name: redisHost\n    value: localhost:6379\n  - name: redisPassword\n    value: \"\""
  },
  {
    "path": "grpc-event-subscriber/go.mod",
    "content": "module github.com/mchmarny/dapr-demos/grpc-event-subscriber\n\ngo 1.15\n\nrequire github.com/dapr/go-sdk v0.11.0\n"
  },
  {
    "path": "grpc-event-subscriber/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo=\ngithub.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=\ngolang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 h1:4X356008q5SA3YXu8PiRap39KFmy4Lf6sGlceJKZQsU=\ngolang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0 h1:uslsjIdqvZYANxSBQjTI47vZfwMaTN3mLELkMnMIY/A=\ngoogle.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=\ngoogle.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "grpc-event-subscriber/k8s/component.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: grpc-events\nspec:\n  type: pubsub.redis\n  metadata:\n  - name: redisHost\n    value: redis-master.redis.svc.cluster.local:6379\n  - name: redisPassword\n    secretKeyRef:\n      name: redis-secret\n      key: password\n  - name: allowedTopics\n    value: \"messages\"\nscopes:\n- nginx-ingress\n- grpc-event-subscriber"
  },
  {
    "path": "grpc-event-subscriber/k8s/deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: grpc-event-subscriber\n  labels:\n    app: grpc-event-subscriber\n    demo: grpc-event\nspec:\n  selector:\n    matchLabels:\n      app: grpc-event-subscriber\n  template:\n    metadata:\n      labels:\n        app: grpc-event-subscriber\n        demo: grpc-event\n      annotations:\n        dapr.io/enabled: \"true\"\n        dapr.io/app-id: \"grpc-event-subscriber\"\n        dapr.io/app-protocol: \"grpc\"\n        dapr.io/app-port: \"60002\"\n        dapr.io/config: \"tracing\"\n        dapr.io/log-as-json: \"true\"\n        dapr.io/log-level: \"debug\"\n    spec:\n      containers:\n      - name: service\n        image: mchmarny/grpc-event-subscriber:v0.11.1\n        ports:\n        - containerPort: 60002\n        env:\n        - name: ADDRESS\n          value: \":60002\"\n        - name: PUBSUB_NAME\n          value: \"grpc-events\"\n        - name: TOPIC_NAME\n          value: \"messages\"\n"
  },
  {
    "path": "grpc-event-subscriber/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/dapr/go-sdk/service/common\"\n\tdaprd \"github.com/dapr/go-sdk/service/grpc\"\n)\n\nvar (\n\tlogger         = log.New(os.Stdout, \"\", 0)\n\tserviceAddress = getEnvVar(\"ADDRESS\", \":60002\")\n\tpubSubName     = getEnvVar(\"PUBSUB_NAME\", \"grpc-events\")\n\ttopicName      = getEnvVar(\"TOPIC_NAME\", \"messages\")\n)\n\nfunc main() {\n\t// create Dapr service\n\ts, err := daprd.NewService(serviceAddress)\n\tif err != nil {\n\t\tlogger.Fatalf(\"failed to start the server: %v\", err)\n\t}\n\n\t// add handler to the service\n\tsubscription := &common.Subscription{\n\t\tPubsubName: pubSubName,\n\t\tTopic:      topicName,\n\t}\n\ts.AddTopicEventHandler(subscription, eventHandler)\n\n\t// start the server to handle incoming events\n\tif err := s.Start(); err != nil {\n\t\tlogger.Fatalf(\"server error: %v\", err)\n\t}\n}\n\nfunc eventHandler(ctx context.Context, e *common.TopicEvent) (retry bool, err error) {\n\tlogger.Printf(\n\t\t\"event - PubsubName:%s, Topic:%s, ID:%s, Data: %s\",\n\t\te.PubsubName, e.Topic, e.ID, e.Data,\n\t)\n\n\t// TODO: do something with the cloud event data\n\n\treturn false, nil\n}\n\nfunc getEnvVar(key, fallbackValue string) string {\n\tif val, ok := os.LookupEnv(key); ok {\n\t\treturn strings.TrimSpace(val)\n\t}\n\treturn fallbackValue\n}\n"
  },
  {
    "path": "hardened/Makefile",
    "content": ".PHONY: all\nall: help\n\n.PHONY: apply\napply: ## Applies all and restarts the apps \n\t# apply\n\tkubectl apply -f k8s/ -n hardened\n\t# restart\n\tkubectl rollout restart deployment/app1 -n hardened\n\tkubectl rollout restart deployment/app2 -n hardened\n\tkubectl rollout restart deployment/app3 -n hardened\n\t# status\n\tkubectl rollout status deployment/app1 -n hardened\n\tkubectl rollout status deployment/app2 -n hardened\n\tkubectl rollout status deployment/app3 -n hardened\n\n.PHONY: ping\nping: ## Execute ping on the Dapr API \n\t$(eval API_TOKEN=$(shell kubectl get secret dapr-api-token -o jsonpath=\"{.data.token}\" | base64 --decode))\n\tcurl -i -d '{ \"message\": \"hello\" }' \\\n      -H \"Content-type: application/json\" \\\n      -H \"dapr-api-token: $(API_TOKEN)\" \\\n      https://api.thingz.io/v1.0/invoke/app1.hardened/method/ping\n\n.PHONY: count\ncount: ## Execute counter on the Dapr API \n\t$(eval API_TOKEN=$(shell kubectl get secret dapr-api-token -o jsonpath=\"{.data.token}\" | base64 --decode))\n\tcurl -i -d '{ \"on\": 1603627556200126373, \"count\": 2 }' \\\n      -H \"Content-type: application/json\" \\\n      -H \"dapr-api-token: $(API_TOKEN)\" \\\n      https://api.thingz.io/v1.0/invoke/app2.hardened/method/counter\n\n.PHONY: help\nhelp: ## Display available commands\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \\\n\t\t'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n"
  },
  {
    "path": "hardened/README.md",
    "content": "# Hardening Dapr app demo \n\nIn addition to support for Kubernetes namespace isolation and Role-Based Access Control (RBAC) authorization, Dapr also provides additional, more granular, controls to harden applications deployment in Kubernetes. Some security related features, like in-transit encryption for all sidecar-to-sidecar communication using mutual TLS, are enabled by default. Others, like middleware to apply [Open Policy Agent](https://www.openpolicyagent.org/) (OPA) policies on incoming requests, require opt-in. This demo will overview: \n\n* Cross-namespace service invocation with logical domain groups trust relationship management\n* Secure microservice communication using mTLS and [SPIFFE](https://spiffe.io/) identity verification \n* Per operation verb access control settings (e.g. deny all except `POST` from `app2` on `/op1`)\n* Per application component scoping (i.e. which app should be able to access a given component)\n* Pub/Sub topic scoping (i.e. which app should be able to publish or subscriber to a given topic)\n* Application-level secret access control (i.e. which secrets the app should be able to access)\n* Token authentication on cluster ingress for both HTTP and gRPC API\n\n![](img/diagram.png)\n\n> You can replicate this demo on any Kubernetes cluster configured with Dapr. To demo the cross-namespace service invocation with external API gateway you will need \"dapr'ized' cluster ingress (ingress with Dapr sidecar). You can setup fully configured Dapr cluster with all these dependencies using included [Dapr cluster setup](../setup#dapr-cluster-setup).\n\n## Setup \n\nIn Kubernetes, [namespaces](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/) provide a way to divide cluster resources between multiple users or applications. To isolate all the microservices in this demo, first, create a namespace on your cluster.\n\n> For purposes of this demo, the namespace will be called `hardened` but you can choose your own name.\n\n```shell\nkubectl apply -f deployment/namespace\n```\n\nAlso, to illustrate Dapr component scoping (e.g. PubSub and State), this demo will use in-cluster Redis deployment (see [Redis setup](../setup#usage)). To showcase the declarative access control for applications over secrets this demo will use `redis-secret` defined in the `hardened` namespace.\n\n```shell\nkubectl create secret generic redis-secret \\\n    --from-literal=password=\"${REDIS_PASS}\" \\\n    -n hardened\n```\n\n> If this is Redis on your cluster you can look it up using `export REDIS_PASS=$(kubectl get secret -n redis redis -o jsonpath=\"{.data.redis-password}\" | base64 --decode)` and define the `REDIS_PASS` environment variable with that secret. \n\nFinally, create one more `demo` secret to illustrate later how Dapr controls application's access to secrets.\n\n```shell\nkubectl create secret generic demo-secret --from-literal=demo=\"demo\" -n hardened \n```\n\n## Deploy\n\nWith the namespace configured and the Redis password created, it's time to deploy:\n\n* [app1.yaml](./deployment/hardened/app1.yaml), [app2.yaml](./deployment/hardened/app1.yaml), and [app2.yaml](./deployment/hardened/app1.yaml) are the Kubernetes deployments with their Dapr configuration.\n* [pubsub.yaml](./deployment/hardened/pubsub.yaml) and [state.yaml](./deployment/hardened/state.yaml) are the configuration files for PubSub and State components using Redis\n* [role.yaml](./deployment/hardened/role.yaml) defines the Role and RoleBinding required for Dapr application access the Kubernetes secrets in the `hardened` namespace.\n\n> This demo uses [prebuilt application images](https://github.com/mchmarny?tab=packages&q=hardened-app). You can review the code for these 3 applications in the [src](./src) directory.\n\nNow, apply the demo resources to the cluster.\n\n```shell\nkubectl apply -f deployment/hardened -n hardened\n```\n\nThe response from the above command should confirm that all the resources were configured.\n\n```shell\ndeployment.apps/app1 configured\nconfiguration.dapr.io/app1-config configured\ndeployment.apps/app2 configured\nconfiguration.dapr.io/app2-config configured\ndeployment.apps/app3 configured\nconfiguration.dapr.io/app3-config configured\ncomponent.dapr.io/pubsub configured\ncomponent.dapr.io/state configured\n```\n\n## Verify \n\nTo ensure the rest of the demo goes smoothly, check that everything was deployed correctly.\n\n```shell\nkubectl get pods -n hardened\n```\n\nIf everything went well, the response should include `app1`, `app2`, and `app3` pods with the status `Running` and the ready state of `2/2` indicating that the Dapr sidecar has been injected and components successfully loaded.\n\n```shell\nNAME                    READY   STATUS    RESTARTS   AGE\napp1-6df587fb45-k46sz   2/2     Running   0          40s\napp2-685fd94f69-5vkwl   2/2     Running   0          40s\napp3-6d57778cbd-mxn2k   2/2     Running   0          40s\n```\n\n## Demo \n\nThe Dapr API exposed on the cluster ingress is protected with [token authentication](https://github.com/dapr/docs/tree/master/howto/enable-dapr-api-token-based-authentication#enable-dapr-apis-token-based-authentication). Start by exporting that token from the cluster secret to allow for API invocation in this demo.\n\n```shell\nexport API_TOKEN=$(kubectl get secret dapr-api-token -n nginx -o jsonpath=\"{.data.token}\" | base64 --decode)\n```\n\n### Service Invocation\n\nThe app identity and its access control within Dapr as controlled using [policies](https://github.com/dapr/docs/blob/master/howto/allowlists-serviceinvocation/README.md) which are defined in the app configuration. To \"attach\" [configuration](https://github.com/dapr/docs/blob/master/howto/allowlists-serviceinvocation/README.md), the app deployment template has to be annotated with the name of the configuration:\n\n```yaml\nannotations:\n  dapr.io/config: \"app1-config\"\n```\n\nIn this demo, to allow only the Dapr'ized NGNX ingress to invoke the `/ping` method on [app1.yaml](./deployment/hardened/app1.yaml), the default action is set to `deny` and an explicit policy created for `nginx-ingress` in the `default` namespace which also, first denies access to all methods on that app, and only then allows access on the `/ping` method (aka operation) when the HTTP verb is `POST`. \n\n```yaml\naccessControl:\n  defaultAction: deny\n  trustDomain: \"hardened\"\n  policies:\n  - appId: nginx-ingress\n    namespace: \"default\"\n    defaultAction: deny \n    operations:\n    - name: /ping\n      httpVerb: [\"POST\"] \n      action: allow\n```\n\nTo demo this now, invoke the `ping` method on `app1` in the `hardened` namespace using the Dapr API exposed on the NGNX ingress.\n\n> The [Dapr cluster setup](../setup#dapr-cluster-setup) includes custom domain and TLS certificate support. This demo users `api.demo.dapr.team` domain and a wildcard certificates for al (`*`) subdomains.\n\n```shell\ncurl -i -d '{ \"message\": \"hello\" }' \\\n     -H \"Content-type: application/json\" \\\n     -H \"dapr-api-token: ${API_TOKEN}\" \\\n     https://api.demo.dapr.team/v1.0/invoke/app1.hardened/method/ping\n```\n\nDapr should respond with HTTP status code `200` as well as parent trace ID for this invocation (`traceparent`) in the header, and a JSON payload with the number of API invocations and nano epoch timestamp.\n\n> The count of API invocations is persisted in the Dapr sate store configured in [State component](./deployment/hardened/state.yaml)\n\n```shell\nHTTP/2 200\ndate: Sun, 25 Oct 2020 12:05:56 GMT\ncontent-type: text/plain; charset=utf-8\ncontent-length: 39\ntraceparent: 00-ecbbc473826b3e328ea00f5ac0ce222b-0824d3896092d8ce-01\nstrict-transport-security: max-age=15724800; includeSubDomains\n\n{ \"on\": 1603627556200126373, \"count\": 8 }\n```\n\nTo demo the active access policy, try also to invoke the `counter` method on `app2` in the `hardened` namespace.\n\n```shell\ncurl -i -d '{ \"on\": 1603627556200126373, \"count\": 2 }' \\\n     -H \"Content-type: application/json\" \\\n     -H \"dapr-api-token: ${API_TOKEN}\" \\\n     https://api.demo.dapr.team/v1.0/invoke/app2.hardened/method/counter\n```\n\nThat invocation will result in an error. The response will include `PermissionDenied` message:\n\n```json\n{\n  \"errorCode\": \"ERR_DIRECT_INVOKE\",\n  \"message\": \"rpc error: code = PermissionDenied desc = access control policy has denied access to appid: app2 operation: ping verb: POST\"\n}\n```\n\nThe access control defined above applies also to in-cluster invocation ([app2.yaml](./deployment/hardened/app2.yaml)). Where the additional `trustDomain` setting on `app2` configuration is used to only allow access to invoke the `/counter` method when the calling app is `app1`:\n\n```yaml\npolicies:\n  - appId: app1\n    defaultAction: deny \n    trustDomain: \"hardened\"\n    namespace: \"hardened\"\n    operations:\n    - name: /counter\n      httpVerb: [\"POST\"] \n      action: allow\n```\n\nTo demo this, forward local port to any other Dapr sidecar besides `app1` in that cluster.\n\n```shell\nkubectl port-forward deployment/app2 3500 -n hardened\n```\n\nAnd then try to invoke the `/ping` method on the `app1`. That too will result in `PermissionDenied` message. \n\n```shell\ncurl -i -d '{ \"message\": \"hello\" }' \\\n     -H \"Content-type: application/json\" \\\n     http://localhost:3500/v1.0/invoke/app1/method/ping\n```\n\n> In this configuration, all invocations that are not explicitly permitted in Dapr access policy will be denied!\n\n### Components\n\n\nJust like in case of invocation, access to components in Dapr is also driven by configuration. The [state store](./deployment/hardened/state.yaml) component in this demo is scoped to only be accessible by `app2`:\n\n```yaml\nscopes:\n- app2\n```\n\n> Dapr automatically isolates state between applications by contextualizing to the app that created it, rendering it inaccessible by any other application regardless of policies.\n\n### Topic Publishing and Subscription \n\nThe topic access of the PubSub component is further defined by the `publishingScopes` and `subscriptionScopes` lists. In this case `app2` can only publish, and the `app3` can only subscribe to the `messages` topic:\n\n```yaml\n- name: publishingScopes\n  value: \"app2=messages\"\n- name: subscriptionScopes\n  value: \"app3=messages\"\n```\n\nTo demo this, while still forwarding local port to the `app2` pod, try publish to any other topic besides `messages`.\n\n```shell\ncurl -i -d '{ \"message\": \"test\" }' \\\n     -H \"Content-type: application/json\" \\\n     http://localhost:3500/v1.0/publish/pubsub/test\n```\n\nThe above publish will result in error:\n\n```json\n{\n  \"errorCode\": \"ERR_PUBSUB_PUBLISH_MESSAGE\",\n  \"message\": \"topic test is not allowed for app id app2\"\n}\n```\n\nYou can also try to subscribe to the `messages` topic or even forward port to `app3` and try to publish to the valid topic there, and still receive the same error, because that application is only allowed to subscribe to the `messages` topic, not publish to it.\n\n### Secrets \n\nApplication access to secrets within Dapr is also driven by configuration. In this demo, the `app2` for example, has its secrets configuration defined as follow: `deny` this application's access to all secrets except `redis-secret`: \n\n```yaml\nsecrets:\n  scopes:\n    - storeName: kubernetes\n      defaultAccess: deny\n      allowedSecrets: [\"redis-secret\"]\n```        \n\nTo demo this, while still forwarding local port to the `app2` pod, and access the `redis-secret`.\n\n```shell\ncurl -i \"http://localhost:3500/v1.0/secrets/kubernetes/redis-secret?metadata.namespace=hardened\"\n```\n\nNow, try access the other secret we created in the `hardened` namespace during setup: `demo-secret`.\n\n```shell\ncurl -i \"http://localhost:3500/v1.0/secrets/kubernetes/demo-secret?metadata.namespace=hardened\"\n```\n\nThe above query will result in `403 Forbidden` as the `demo-secret` secret is not listed in the `allowedSecrets` list and the `defaultAccess` is set to `deny`.\n\n```json\n{\n  \"errorCode\": \"ERR_PERMISSION_DENIED\", \n  \"message\": \"Access denied by policy to get demo-secret from kubernetes\"\n}\n```\n\n## Tracing \n\nStart by generating some requests:\n\n```shell\nfor i in {1..100}; do \\\n  sleep 1; \\\n  curl -i -d '{ \"message\": \"hello\" }' \\\n     -H \"Content-type: application/json\" \\\n     -H \"dapr-api-token: ${API_TOKEN}\" \\\n     https://api.demo.dapr.team/v1.0/invoke/app1.hardened/method/ping\ndone\n```\n\nThen forward the Zipkin port locally:\n\n```shell\nkubectl port-forward svc/zipkin 9411 -n dapr-monitoring\n```\n\nAnd navigate to the Zipkin UI to review traces \n\nhttp://localhost:9411/\n\n![](img/trace.png)\n\n## Summary \n\nThis demo illustrated just a few of the options that Dapr provides to harden application deployments. For more security-related information (including network, threat model, and latest security audit) see the [Security section](https://docs.dapr.io/concepts/security-concept/) in Dapr documentation.\n\n## Logging \n\nTo view logs from either the app container (`app`) or Dapr (`daprd`) use the label selector with the app ID (e.g. `app1`, `app2`, or `app3`):\n\n```shell\nkubectl logs -l app=app1 -c daprd -n hardened\n```\n\n> Because this demo set Dapr logs to be output as JSON you can use [jq](https://stedolan.github.io/jq/) or similar to query the logs \n\n```shell\nkubectl logs -l app=app1 -c daprd -n hardened --tail 300 | jq \".msg\"\n```\n\nResulting in:\n\n```shell\n\"starting Dapr Runtime -- version 0.11.3 -- commit a1a8e11\"\n\"log level set to: debug\"\n\"metrics server started on :9090/\"\n\"kubernetes mode configured\"\n\"app id: app1\"\n```\n\n## Restarts\n\nIf you update components you may have to restart the deployments.\n\n```shell\nkubectl rollout restart deployment app1 app2 app3 -n hardened\nkubectl rollout status deployment app1 -n hardened\nkubectl rollout status deployment app2 -n hardened\nkubectl rollout status deployment app3 -n hardened\n```\n\n## Cleanup\n\n> By deleting the `hardened` Kubernetes will cascade delete all resources created in that namespace.\n\n```shell\nkubectl delete ns hardened\n```\n\n## Disclaimer\n\nThis is my personal project and it does not represent my employer. While I do my best to ensure that everything works, I take no responsibility for issues caused by this code.\n\n## License\n\nThis software is released under the [MIT](../LICENSE)\n"
  },
  {
    "path": "hardened/config/pubsub.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: pubsub\n  namespace: hardened\nspec:\n  type: pubsub.redis\n  metadata:\n  - name: redisHost\n    value: localhost:6379\n  - name: redisPassword\n    value: \"\"\n  - name: publishingScopes\n    value: \"app2=messages\"\n  - name: subscriptionScopes\n    value: \"app3=messages\"\nscopes:\n- app2\n- app3"
  },
  {
    "path": "hardened/config/state.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: state\n  namespace: hardened\nspec:\n  type: state.redis\n  metadata:\n  - name: redisHost\n    value: localhost:6379\n  - name: redisPassword\n    value: \"\"\nscopes:\n- app2"
  },
  {
    "path": "hardened/deployment/hardened/app1.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: app1\n  namespace: hardened\n  labels:\n    app: app1\n    demo: hardened\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: app1\n  template:\n    metadata:\n      labels:\n        app: app1\n        demo: hardened\n      annotations:\n        dapr.io/enabled: \"true\"\n        dapr.io/app-id: \"app1\"\n        dapr.io/app-protocol: \"http\"\n        dapr.io/app-port: \"8081\"\n        dapr.io/config: \"app1-config\"\n        dapr.io/log-as-json: \"true\"\n        dapr.io/log-level: \"debug\"\n    spec:\n      containers:\n      - name: app\n        image: ghcr.io/mchmarny/hardened-app1:v0.1.4\n        ports:\n        - containerPort: 8081\n        env:\n        - name: PUBSUB_NAME\n          value: \"pubsub\"\n        - name: TOPIC_NAME\n          value: \"messages\"\n---\napiVersion: dapr.io/v1alpha1\nkind: Configuration\nmetadata:\n  name: app1-config\n  namespace: hardened\nspec:\n  tracing:\n    samplingRate: \"1\"\n  secrets:\n    scopes:\n      - storeName: kubernetes\n        defaultAccess: deny\n  accessControl:\n    defaultAction: deny\n    trustDomain: \"hardened\"\n    policies:\n    - appId: nginx-ingress\n      defaultAction: deny \n      trustDomain: \"public\"\n      namespace: \"nginx\" # where NGINX is installed \n      operations:\n      - name: /ping\n        httpVerb: [\"POST\"] \n        action: allow\n"
  },
  {
    "path": "hardened/deployment/hardened/app2.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: app2\n  namespace: hardened\n  labels:\n    app: app2\n    demo: hardened\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: app2\n  template:\n    metadata:\n      labels:\n        app: app2\n        demo: hardened\n      annotations:\n        dapr.io/enabled: \"true\"\n        dapr.io/app-id: \"app2\"\n        dapr.io/app-protocol: \"http\"\n        dapr.io/app-port: \"8082\"\n        dapr.io/config: \"app2-config\"\n        dapr.io/log-as-json: \"true\"\n        dapr.io/log-level: \"debug\"\n    spec:\n      containers:\n      - name: app\n        image: ghcr.io/mchmarny/hardened-app2:v0.1.4\n        ports:\n        - containerPort: 8082\n        env:\n        - name: PUBSUB_NAME\n          value: \"pubsub\"\n        - name: TOPIC_NAME\n          value: \"messages\"\n        - name: STORE_NAME\n          value: \"state\"\n---\napiVersion: dapr.io/v1alpha1\nkind: Configuration\nmetadata:\n  name: app2-config\n  namespace: hardened\nspec:\n  tracing:\n    samplingRate: \"1\"\n  secrets:\n    scopes:\n      - storeName: kubernetes\n        defaultAccess: deny\n        allowedSecrets: [\"redis-secret\"]\n  accessControl:\n    defaultAction: deny\n    trustDomain: \"hardened\"\n    policies:\n    - appId: app1\n      defaultAction: deny \n      trustDomain: \"hardened\"\n      namespace: \"hardened\"\n      operations:\n      - name: /counter\n        httpVerb: [\"POST\"] \n        action: allow\n"
  },
  {
    "path": "hardened/deployment/hardened/app3.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: app3\n  namespace: hardened\n  labels:\n    app: app3\n    demo: hardened\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: app3\n  template:\n    metadata:\n      labels:\n        app: app3\n        demo: hardened\n      annotations:\n        dapr.io/enabled: \"true\"\n        dapr.io/app-id: \"app3\"\n        dapr.io/app-protocol: \"http\"\n        dapr.io/app-port: \"8083\"\n        dapr.io/config: \"app3-config\"\n        dapr.io/log-as-json: \"true\"\n        dapr.io/log-level: \"debug\"\n    spec:\n      containers:\n      - name: app\n        image: ghcr.io/mchmarny/hardened-app3:v0.1.4\n        ports:\n        - containerPort: 8083\n        env:\n        - name: PUBSUB_NAME\n          value: \"pubsub\"\n        - name: TOPIC_NAME\n          value: \"messages\"\n---\napiVersion: dapr.io/v1alpha1\nkind: Configuration\nmetadata:\n  name: app3-config\n  namespace: hardened\nspec:\n  tracing:\n    samplingRate: \"1\"\n  secrets:\n    scopes:\n      - storeName: kubernetes\n        defaultAccess: deny\n        allowedSecrets: [\"redis-secret\"]\n          "
  },
  {
    "path": "hardened/deployment/hardened/pubsub.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: pubsub\n  namespace: hardened\nspec:\n  type: pubsub.redis\n  metadata:\n  - name: redisHost\n    value: redis-master.redis.svc.cluster.local:6379\n  - name: redisPassword\n    secretKeyRef:\n      name: redis-secret\n      key: password\n  - name: publishingScopes\n    value: \"app2=messages\"\n  - name: subscriptionScopes\n    value: \"app3=messages\"\nscopes:\n- app2\n- app3"
  },
  {
    "path": "hardened/deployment/hardened/state.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: state\n  namespace: hardened\nspec:\n  type: state.redis\n  metadata:\n  - name: redisHost\n    value: redis-master.redis.svc.cluster.local:6379\n  - name: redisPassword\n    secretKeyRef:\n      name: redis-secret\n      key: password\nscopes:\n- app2"
  },
  {
    "path": "hardened/deployment/namespace/ns.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  name: hardened"
  },
  {
    "path": "hardened/deployment/namespace/role.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: secret-reader\n  namespace: hardened\nrules:\n- apiGroups: [\"\"]\n  resources: [\"secrets\"]\n  verbs: [\"get\"]\n---\nkind: RoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: dapr-secret-reader\n  namespace: hardened\nsubjects:\n- kind: ServiceAccount\n  name: default\nroleRef:\n  kind: Role\n  name: secret-reader\n  apiGroup: rbac.authorization.k8s.io"
  },
  {
    "path": "hardened/deployment/namespace/trace.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: zipkin\n  namespace: hardened\nspec:\n  type: exporters.zipkin\n  metadata:\n  - name: enabled\n    value: \"true\"\n  - name: exporterAddress\n    value: \"http://zipkin.dapr-monitoring.svc.cluster.local:9411/api/v2/spans\""
  },
  {
    "path": "hardened/deployment/simple/app1.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: app1\n  namespace: hardened\n  labels:\n    app: app1\n    demo: hardened\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: app1\n  template:\n    metadata:\n      labels:\n        app: app1\n        demo: hardened\n      annotations:\n        dapr.io/enabled: \"true\"\n        dapr.io/app-id: \"app1\"\n        dapr.io/app-protocol: \"http\"\n        dapr.io/app-port: \"8081\"\n        dapr.io/log-as-json: \"true\"\n        dapr.io/log-level: \"debug\"\n    spec:\n      containers:\n      - name: app\n        image: ghcr.io/mchmarny/hardened-app1:v0.1.4\n        ports:\n        - containerPort: 8081\n        env:\n        - name: PUBSUB_NAME\n          value: \"pubsub\"\n        - name: TOPIC_NAME\n          value: \"messages\"\n"
  },
  {
    "path": "hardened/deployment/simple/app2.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: app2\n  namespace: hardened\n  labels:\n    app: app2\n    demo: hardened\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: app2\n  template:\n    metadata:\n      labels:\n        app: app2\n        demo: hardened\n      annotations:\n        dapr.io/enabled: \"true\"\n        dapr.io/app-id: \"app2\"\n        dapr.io/app-protocol: \"http\"\n        dapr.io/app-port: \"8082\"\n        dapr.io/log-as-json: \"true\"\n        dapr.io/log-level: \"debug\"\n    spec:\n      containers:\n      - name: app\n        image: ghcr.io/mchmarny/hardened-app2:v0.1.4\n        ports:\n        - containerPort: 8082\n        env:\n        - name: PUBSUB_NAME\n          value: \"pubsub\"\n        - name: TOPIC_NAME\n          value: \"messages\"\n        - name: STORE_NAME\n          value: \"state\"\n\n"
  },
  {
    "path": "hardened/deployment/simple/app3.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: app3\n  namespace: hardened\n  labels:\n    app: app3\n    demo: hardened\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: app3\n  template:\n    metadata:\n      labels:\n        app: app3\n        demo: hardened\n      annotations:\n        dapr.io/enabled: \"true\"\n        dapr.io/app-id: \"app3\"\n        dapr.io/app-protocol: \"http\"\n        dapr.io/app-port: \"8083\"\n        dapr.io/log-as-json: \"true\"\n        dapr.io/log-level: \"debug\"\n    spec:\n      containers:\n      - name: app\n        image: ghcr.io/mchmarny/hardened-app3:v0.1.4\n        ports:\n        - containerPort: 8083\n        env:\n        - name: PUBSUB_NAME\n          value: \"pubsub\"\n        - name: TOPIC_NAME\n          value: \"messages\"\n"
  },
  {
    "path": "hardened/deployment/simple/pubsub.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: pubsub\n  namespace: hardened\nspec:\n  type: pubsub.redis\n  metadata:\n  - name: redisHost\n    value: redis-master.redis.svc.cluster.local:6379\n  - name: redisPassword\n    secretKeyRef:\n      name: redis-secret\n      key: password"
  },
  {
    "path": "hardened/deployment/simple/state.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: state\n  namespace: hardened\nspec:\n  type: state.redis\n  metadata:\n  - name: redisHost\n    value: redis-master.redis.svc.cluster.local:6379\n  - name: redisPassword\n    secretKeyRef:\n      name: redis-secret\n      key: password\nscopes:\n- app2"
  },
  {
    "path": "hardened/src/app1/Dockerfile",
    "content": "FROM golang:1.15.3 as builder\n\nWORKDIR /src/\nCOPY . /src/\n\nENV GO111MODULE=on\n\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \\\n    go build -a -tags netgo -mod vendor -o ./app .\n\nFROM gcr.io/distroless/static:nonroot\nCOPY --from=builder /src/app .\n\nENTRYPOINT [\"./app\"]\n"
  },
  {
    "path": "hardened/src/app1/Makefile",
    "content": "IMAGE_NAME  ?=hardened-app1\nIMAGE_TAG   ?=v0.1.4\nIMAGE_OWNER ?=$(shell git config --get user.username)\nAPP_ID      ?=app1\nAPP_PORT    ?=8081\n\n.PHONY: all\nall: help\n\n.PHONY: tidy\ntidy: ## Updates the go modules and vendors all dependencies \n\tgo mod tidy\n\tgo mod vendor\n\n.PHONY: test\ntest: tidy ## Tests the entire project \n\tgo test -count=1 -race ./...\n\n.PHONY: run\nrun: tidy ## Runs uncompiled code in Dapr\n\tdapr run \\\n\t --app-id $(APP_ID) \\\n\t --app-port $(APP_PORT) \\\n\t --app-protocol http \\\n\t --dapr-http-port 3500 \\\n\t --components-path ../../config \\\n\t --log-level debug \\\n\t go run main.go\n\n.PHONY: image\nimage: tidy ## Builds and publish image \n\tdocker build -t \"ghcr.io/$(IMAGE_OWNER)/$(IMAGE_NAME):$(IMAGE_TAG)\" .\n\tdocker push \"ghcr.io/$(IMAGE_OWNER)/$(IMAGE_NAME):$(IMAGE_TAG)\"\n\n.PHONY: lint\nlint: ## Lints the entire project \n\tgolangci-lint run --timeout=3m\n\n.PHONY: clean\nclean: ## Cleans up generated files \n\tgo clean\n\trm -fr ./bin\n\trm -fr ./vendor\n\n.PHONY: help\nhelp: ## Display available commands\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \\\n\t\t'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n"
  },
  {
    "path": "hardened/src/app1/go.mod",
    "content": "module github.com/mchmarny/dapr-demos/hardened/src/app1\n\ngo 1.15\n\nrequire (\n\tgithub.com/dapr/go-sdk v0.11.1\n\tgithub.com/pkg/errors v0.9.1\n)\n"
  },
  {
    "path": "hardened/src/app1/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/dapr/go-sdk v0.11.1 h1:83lLYLrOKK1dAI1XhuamY9SkQoo2t5Fd8zlNlDMtwWA=\ngithub.com/dapr/go-sdk v0.11.1/go.mod h1:l9aJ4Zqsfzi9adwheYgFND6ZxhPDbb34IF/NecuPYJo=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20201022231255-08b38378de70 h1:Z6x4N9mAi4oF0TbHweCsH618MO6OI6UFgV0FP5n0wBY=\ngolang.org/x/net v0.0.0-20201022231255-08b38378de70/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd h1:WgqgiQvkiZWz7XLhphjt2GI2GcGCTIZs9jqXMWmH+oc=\ngolang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20201022181438-0ff5f38871d5 h1:YejJbGvoWsTXHab4OKNrzk27Dr7s4lPLnewbHue1+gM=\ngoogle.golang.org/genproto v0.0.0-20201022181438-0ff5f38871d5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.33.1 h1:DGeFlSan2f+WEtCERJ4J9GJWk15TxUi8QGagfI87Xyc=\ngoogle.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "hardened/src/app1/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\tdapr \"github.com/dapr/go-sdk/client\"\n\t\"github.com/dapr/go-sdk/service/common\"\n\tdaprd \"github.com/dapr/go-sdk/service/http\"\n\t\"github.com/pkg/errors\"\n)\n\nconst (\n\tmethodName      = \"ping\"\n\tinvocationError = \"error invoking\"\n)\n\nvar (\n\tlogger = log.New(os.Stdout, \"\", 0)\n\n\tserviceAddress    = getEnvVar(\"ADDRESS\", \":8081\")\n\ttargetServiceName = getEnvVar(\"TARGET_SERVICE_NAME\", \"app2\")\n\ttargetMethodName  = getEnvVar(\"TARGET_METHOD_NAME\", \"counter\")\n\n\tclient dapr.Client\n)\n\nfunc main() {\n\ts := daprd.NewService(serviceAddress)\n\n\tvar clientErr error\n\tif client, clientErr = dapr.NewClient(); clientErr != nil {\n\t\tlogger.Fatalf(\"error creating Dapr client: %v\", clientErr)\n\t}\n\tdefer client.Close()\n\n\tif err := s.AddServiceInvocationHandler(methodName, handler); err != nil {\n\t\tlogger.Fatalf(\"error adding call invocation handler: %v\", err)\n\t}\n\n\tlogger.Printf(\"starting server at %s...\", serviceAddress)\n\tif err := s.Start(); err != nil {\n\t\tlogger.Fatalf(\"error starting server: %v\", err)\n\t}\n}\n\nfunc handler(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) {\n\tlogger.Printf(\"Method %s invoked  (ContentType:%s, Verb:%s, QueryString:%s, Data:%s)\",\n\t\tmethodName, in.ContentType, in.Verb, in.QueryString, in.Data)\n\n\tm := &dapr.DataContent{ContentType: in.ContentType, Data: in.Data}\n\n\tdata, err := client.InvokeServiceWithContent(ctx, targetServiceName, targetMethodName, m)\n\tif err != nil {\n\t\tlogger.Printf(\"%s %s/%s: %v\", invocationError, targetServiceName, targetMethodName, err)\n\t\treturn nil, errors.Wrapf(err, \"%s %s/%s\", invocationError, targetServiceName, targetMethodName)\n\t}\n\tlogger.Printf(\"Response from invocation: %s\", data)\n\n\tj := []byte(fmt.Sprintf(`{\"on\": %d, \"count\": %s}`, time.Now().UTC().UnixNano(), string(data)))\n\n\tout = &common.Content{ContentType: in.ContentType, Data: j}\n\treturn\n}\n\nfunc getEnvVar(key, fallbackValue string) string {\n\tif val, ok := os.LookupEnv(key); ok {\n\t\treturn strings.TrimSpace(val)\n\t}\n\treturn fallbackValue\n}\n"
  },
  {
    "path": "hardened/src/app2/Dockerfile",
    "content": "FROM golang:1.15.3 as builder\n\nWORKDIR /src/\nCOPY . /src/\n\nENV GO111MODULE=on\n\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \\\n    go build -a -tags netgo -mod vendor -o ./app .\n\nFROM gcr.io/distroless/static:nonroot\nCOPY --from=builder /src/app .\n\nENTRYPOINT [\"./app\"]\n"
  },
  {
    "path": "hardened/src/app2/Makefile",
    "content": "IMAGE_NAME  ?=hardened-app2\nIMAGE_TAG   ?=v0.1.4\nIMAGE_OWNER ?=$(shell git config --get user.username)\nAPP_ID      ?=app2\nAPP_PORT    ?=8082\n\nall: help\n\n.PHONY: tidy\ntidy: ## Updates the go modules and vendors all dependencies \n\tgo mod tidy\n\tgo mod vendor\n\n.PHONY: test\ntest: tidy ## Tests the entire project \n\tgo test -count=1 -race ./...\n\n.PHONY: run\nrun: tidy ## Runs uncompiled code in Dapr\n\tdapr run \\\n      --app-id $(APP_ID) \\\n      --app-port $(APP_PORT) \\\n      --app-protocol http \\\n      --components-path ../../config \\\n      --log-level debug \\\n      go run main.go\n\n.PHONY: image\nimage: tidy ## Builds and publishes image \n\tdocker build -t \"ghcr.io/$(IMAGE_OWNER)/$(IMAGE_NAME):$(IMAGE_TAG)\" .\n\tdocker push \"ghcr.io/$(IMAGE_OWNER)/$(IMAGE_NAME):$(IMAGE_TAG)\"\n\n.PHONY: lint\nlint: ## Lints the entire project \n\tgolangci-lint run --timeout=3m\n\n.PHONY: clean\nclean: ## Cleans up generated files \n\tgo clean\n\trm -fr ./bin\n\trm -fr ./vendor\n\n.PHONY: help\nhelp: ## Display available commands\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \\\n\t\t'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n"
  },
  {
    "path": "hardened/src/app2/go.mod",
    "content": "module github.com/mchmarny/dapr-demos/hardened/src/app2\n\ngo 1.15\n\nrequire (\n\tgithub.com/dapr/go-sdk v0.11.1\n\tgithub.com/pkg/errors v0.9.1\n)\n"
  },
  {
    "path": "hardened/src/app2/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/dapr/go-sdk v0.11.1 h1:83lLYLrOKK1dAI1XhuamY9SkQoo2t5Fd8zlNlDMtwWA=\ngithub.com/dapr/go-sdk v0.11.1/go.mod h1:l9aJ4Zqsfzi9adwheYgFND6ZxhPDbb34IF/NecuPYJo=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20201022231255-08b38378de70 h1:Z6x4N9mAi4oF0TbHweCsH618MO6OI6UFgV0FP5n0wBY=\ngolang.org/x/net v0.0.0-20201022231255-08b38378de70/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd h1:WgqgiQvkiZWz7XLhphjt2GI2GcGCTIZs9jqXMWmH+oc=\ngolang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20201022181438-0ff5f38871d5 h1:YejJbGvoWsTXHab4OKNrzk27Dr7s4lPLnewbHue1+gM=\ngoogle.golang.org/genproto v0.0.0-20201022181438-0ff5f38871d5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.33.1 h1:DGeFlSan2f+WEtCERJ4J9GJWk15TxUi8QGagfI87Xyc=\ngoogle.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "hardened/src/app2/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"strconv\"\n\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\n\tdapr \"github.com/dapr/go-sdk/client\"\n\t\"github.com/dapr/go-sdk/service/common\"\n\tdaprd \"github.com/dapr/go-sdk/service/http\"\n\t\"github.com/pkg/errors\"\n)\n\nconst (\n\tmethodName      = \"counter\"\n\tstateCounterKey = \"count\"\n\n\taddInvokeHandlerError = \"error adding invocation handler\"\n\tstartingServiceError  = \"error starting service\"\n\tclientCreateError     = \"error creating Dapr client\"\n\tpublisingError        = \"error publishing\"\n\tgetStateError         = \"error getting state\"\n\tsaveStateError        = \"error saving state\"\n\tintParsingError       = \"error parsing data\"\n)\n\nvar (\n\tlogger  = log.New(os.Stdout, \"\", 0)\n\taddress = getEnvVar(\"ADDRESS\", \":8082\")\n\n\tpubSubName = getEnvVar(\"PUBSUB_NAME\", \"pubsub\")\n\ttopicName  = getEnvVar(\"TOPIC_NAME\", \"messages\")\n\n\tstoreName = getEnvVar(\"STORE_NAME\", \"state\")\n\n\tclient dapr.Client\n)\n\nfunc main() {\n\ts := daprd.NewService(address)\n\n\tvar clientErr error\n\tif client, clientErr = dapr.NewClient(); clientErr != nil {\n\t\tlogger.Fatalf(\"%s: %v\", clientCreateError, clientErr)\n\t}\n\tdefer client.Close()\n\n\tif err := s.AddServiceInvocationHandler(methodName, handler); err != nil {\n\t\tlogger.Fatalf(\"%s: %v\", addInvokeHandlerError, err)\n\t}\n\n\tif err := s.Start(); err != nil && err != http.ErrServerClosed {\n\t\tlogger.Fatalf(\"%s: %v\", startingServiceError, err)\n\t}\n}\n\nfunc handler(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) {\n\tlogger.Printf(\"Method %s invoked  (ContentType:%s, Verb:%s, QueryString:%s, Data:%s)\",\n\t\tmethodName, in.ContentType, in.Verb, in.QueryString, string(in.Data))\n\n\t// get previous number\n\tdata, err := client.GetState(ctx, storeName, stateCounterKey)\n\tif err != nil {\n\t\tlogger.Printf(\"%s: %v\", getStateError, err)\n\t\treturn nil, errors.Wrap(err, getStateError)\n\t}\n\n\t// convert the content to number\n\tvar count int64 = 0\n\tvar cnvErr error\n\tif data != nil && data.Value != nil {\n\t\tcount, cnvErr = strconv.ParseInt(fmt.Sprintf(\"%s\", data.Value), 10, 64)\n\t\tif cnvErr != nil {\n\t\t\tlogger.Printf(\"%s %v: %v\", intParsingError, data.Value, cnvErr)\n\t\t\treturn nil, errors.Wrap(cnvErr, intParsingError)\n\t\t}\n\t}\n\n\t// increment\n\tcount = count + 1\n\tlogger.Printf(\"New count: %d\", count)\n\n\t// data from counter\n\tb := []byte(fmt.Sprintf(\"%d\", count))\n\n\t// save new number\n\titem := &dapr.SetStateItem{\n\t\tEtag:  data.Etag, // using the retreaved etag to ensure count consistency\n\t\tKey:   stateCounterKey,\n\t\tValue: b,\n\t\tOptions: &dapr.StateOptions{\n\t\t\tConcurrency: dapr.StateConcurrencyLastWrite,\n\t\t\tConsistency: dapr.StateConsistencyStrong,\n\t\t},\n\t}\n\tif err := client.SaveStateItems(ctx, storeName, item); err != nil {\n\t\tlogger.Printf(\"%s: %v\", saveStateError, err)\n\t\treturn nil, errors.Wrap(err, saveStateError)\n\t}\n\n\t// publish the results for processing down the stream\n\tif err := client.PublishEvent(ctx, pubSubName, topicName, b); err != nil {\n\t\tlogger.Printf(\"%s to %s/%s: %v\", publisingError, pubSubName, topicName, err)\n\t\treturn nil, errors.Wrapf(err, \"%s to %s/%s\", publisingError, pubSubName, topicName)\n\t}\n\n\tout = &common.Content{ContentType: in.ContentType, Data: b}\n\n\treturn\n}\n\nfunc getEnvVar(key, fallbackValue string) string {\n\tif val, ok := os.LookupEnv(key); ok {\n\t\treturn strings.TrimSpace(val)\n\t}\n\treturn fallbackValue\n}\n"
  },
  {
    "path": "hardened/src/app3/Dockerfile",
    "content": "FROM golang:1.15.3 as builder\n\nWORKDIR /src/\nCOPY . /src/\n\nENV GO111MODULE=on\n\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \\\n    go build -a -tags netgo -mod vendor -o ./app .\n\nFROM gcr.io/distroless/static:nonroot\nCOPY --from=builder /src/app .\n\nENTRYPOINT [\"./app\"]\n"
  },
  {
    "path": "hardened/src/app3/Makefile",
    "content": "IMAGE_NAME  ?=hardened-app3\nIMAGE_TAG   ?=v0.1.4\nIMAGE_OWNER ?=$(shell git config --get user.username)\nAPP_ID      ?=app3\nAPP_PORT    ?=8083\n\nall: help\n\n.PHONY: tidy\ntidy: ## Updates the go modules and vendors all dependencies \n\tgo mod tidy\n\tgo mod vendor\n\n.PHONY: test\ntest: tidy ## Tests the entire project \n\tgo test -count=1 -race ./...\n\n.PHONY: run\nrun: tidy ## Runs uncompiled code in Dapr\n\tdapr run \\\n      --app-id $(APP_ID) \\\n      --app-port $(APP_PORT) \\\n      --app-protocol http \\\n      --components-path ../../config \\\n      --log-level debug \\\n      go run main.go\n\n.PHONY: image\nimage: tidy ## Builds and publishes image \n\tdocker build -t \"ghcr.io/$(IMAGE_OWNER)/$(IMAGE_NAME):$(IMAGE_TAG)\" .\n\tdocker push \"ghcr.io/$(IMAGE_OWNER)/$(IMAGE_NAME):$(IMAGE_TAG)\"\n\n.PHONY: lint\nlint: ## Lints the entire project \n\tgolangci-lint run --timeout=3m\n\n.PHONY: clean\nclean: ## Cleans up generated files \n\tgo clean\n\trm -fr ./bin\n\trm -fr ./vendor\n\n.PHONY: help\nhelp: ## Display available commands\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \\\n\t\t'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n"
  },
  {
    "path": "hardened/src/app3/go.mod",
    "content": "module github.com/mchmarny/dapr-demos/hardened/src/app3\n\ngo 1.15\n\nrequire (\n\tgithub.com/dapr/go-sdk v0.11.1\n\tgithub.com/pkg/errors v0.9.1\n)\n"
  },
  {
    "path": "hardened/src/app3/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/dapr/go-sdk v0.11.1 h1:83lLYLrOKK1dAI1XhuamY9SkQoo2t5Fd8zlNlDMtwWA=\ngithub.com/dapr/go-sdk v0.11.1/go.mod h1:l9aJ4Zqsfzi9adwheYgFND6ZxhPDbb34IF/NecuPYJo=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20201022231255-08b38378de70 h1:Z6x4N9mAi4oF0TbHweCsH618MO6OI6UFgV0FP5n0wBY=\ngolang.org/x/net v0.0.0-20201022231255-08b38378de70/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd h1:WgqgiQvkiZWz7XLhphjt2GI2GcGCTIZs9jqXMWmH+oc=\ngolang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20201022181438-0ff5f38871d5 h1:YejJbGvoWsTXHab4OKNrzk27Dr7s4lPLnewbHue1+gM=\ngoogle.golang.org/genproto v0.0.0-20201022181438-0ff5f38871d5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.33.1 h1:DGeFlSan2f+WEtCERJ4J9GJWk15TxUi8QGagfI87Xyc=\ngoogle.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "hardened/src/app3/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"strconv\"\n\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/dapr/go-sdk/service/common\"\n\tdaprd \"github.com/dapr/go-sdk/service/http\"\n\t\"github.com/pkg/errors\"\n)\n\nconst (\n\teventDataParsingError = \"error parsing event data\"\n\taddSubscriptionError  = \"error adding topic subscription\"\n\tstartingServiceError  = \"error starting service\"\n\tintParsingError       = \"error parsing data\"\n)\n\nvar (\n\tlogger  = log.New(os.Stdout, \"\", 0)\n\taddress = getEnvVar(\"ADDRESS\", \":8083\")\n\n\tpubSubName = getEnvVar(\"PUBSUB_NAME\", \"pubsub\")\n\ttopicName  = getEnvVar(\"TOPIC_NAME\", \"messages\")\n)\n\nfunc main() {\n\ts := daprd.NewService(address)\n\n\tsubscription := &common.Subscription{\n\t\tPubsubName: pubSubName,\n\t\tTopic:      topicName,\n\t\tRoute:      fmt.Sprintf(\"/%s\", topicName),\n\t}\n\n\tif err := s.AddTopicEventHandler(subscription, handler); err != nil {\n\t\tlogger.Fatalf(\"%s: %v\", addSubscriptionError, err)\n\t}\n\n\tif err := s.Start(); err != nil && err != http.ErrServerClosed {\n\t\tlogger.Fatalf(\"%s: %v\", startingServiceError, err)\n\t}\n}\n\nfunc handler(ctx context.Context, e *common.TopicEvent) (retry bool, err error) {\n\tlogger.Printf(\"Event received (PubsubName:%s, Topic:%s, Data: %v\", e.PubsubName, e.Topic, e.Data)\n\n\tvar (\n\t\tcount  int64 = 0\n\t\tcnvErr error\n\t)\n\n\tdata := fmt.Sprintf(\"%v\", e.Data)\n\tcount, cnvErr = strconv.ParseInt(data, 10, 64)\n\tif cnvErr != nil {\n\t\tlogger.Printf(\"%s %v: %v\", intParsingError, data, cnvErr)\n\t\treturn false, errors.Wrap(cnvErr, intParsingError)\n\t}\n\n\tlogger.Printf(\"Even: %v\", count%2 == 0)\n\n\treturn false, nil\n}\n\nfunc getEnvVar(key, fallbackValue string) string {\n\tif val, ok := os.LookupEnv(key); ok {\n\t\treturn strings.TrimSpace(val)\n\t}\n\treturn fallbackValue\n}\n"
  },
  {
    "path": "http-echo-service/Dockerfile",
    "content": "FROM golang:1.15.0 as builder\n\nWORKDIR /src/\nCOPY . /src/\n\nENV GO111MODULE=on\n\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \\\n    go build -a -tags netgo -mod vendor -o ./service .\n\nFROM gcr.io/distroless/static:nonroot\nCOPY --from=builder /src/service .\n\nENTRYPOINT [\"./service\"]\n"
  },
  {
    "path": "http-echo-service/Makefile",
    "content": "RELEASE_VERSION  =v0.11.1\nSERVICE_NAME    ?=http-echo-service\nDOCKER_USERNAME ?=$(DOCKER_USER)\n\n.PHONY: all\nall: help\n\n.PHONY: tidy\ntidy: ## Updates the go modules and vendors all dependencies \n\tgo mod tidy\n\tgo mod vendor\n\n.PHONY: test\ntest: tidy ## Tests the entire project \n\tgo test -count=1 -race ./...\n\n.PHONY: run\nrun: tidy ## Runs uncompiled code in Dapr\n\tdapr run \\\n\t\t--app-id $(SERVICE_NAME) \\\n\t\t--app-port 8080 \\\n\t\t--app-protocol http \\\n\t\t--dapr-http-port 3500 \\\n        go run main.go\n\n.PHONY: invoke\ninvoke: ## Invokes service through Dapr API \n\tcurl -d '{ \"message\": \"ping\" }' \\\n     -H \"Content-type: application/json\" \\\n     \"http://localhost:3500/v1.0/invoke/$(SERVICE_NAME)/method/echo\"\n\n.PHONY: image\nimage: tidy ## Builds and publish docker image \n\tdocker build -t \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\" .\n\tdocker push \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\"\n\n.PHONY: deploy\ndeploy: ## Deploys prebuild image to k8s using currently selected context \n\tkubectl apply -f deployment.yaml\n\tkubectl rollout restart deployment/grpc-echo-service\n\tkubectl rollout status deployment/grpc-echo-service\n\n.PHONY: call\ncall: ## Invokes service through Dapr API \n\t$(eval API_TOKEN=$(shell kubectl get secret dapr-api-token -o jsonpath=\"{.data.token}\" | base64 --decode))\n\tcurl -d '{ \"message\": \"ping\" }' \\\n     -H \"Content-type: application/json\" \\\n     -H \"dapr-api-token: $(API_TOKEN)\" \\\n     \"https://api.cloudylabs.dev/v1.0/invoke/$(SERVICE_NAME)/method/echo\"\n\n.PHONY: lint\nlint: ## Lints the entire project \n\tgolangci-lint run --timeout=3m\n\n.PHONY: tag\ntag: ## Creates release tag \n\tgit tag $(RELEASE_VERSION)\n\tgit push origin $(RELEASE_VERSION)\n\n.PHONY: clean\nclean: ## Cleans up generated files \n\tgo clean\n\trm -fr ./bin\n\trm -fr ./vendor\n\n.PHONY: help\nhelp: ## Display available commands\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \\\n\t\t'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n"
  },
  {
    "path": "http-echo-service/README.md",
    "content": "# http-service\n\nFor more information about service invocation see the [Dapr docs](https://github.com/dapr/docs/tree/master/concepts/service-invocation)\n\n## Run \n\nTo run this demo in Dapr, run:\n\n```shell\ndapr run \\\n    --app-id http-service-demo \\\n    --app-port 8080 \\\n    --app-protocol http \\\n    --dapr-http-port 3500 \\\n    --components-path ./config \\\n    go run main.go\n```\n\n## Deploy\n\nDeploy and wait for the pod to be ready \n\n```shell\nkubectl apply -f deployment.yaml\nkubectl rollout status deployment/http-echo-service\n```\n\nIf you have changed an existing component, make sure to reload the ingress and wait until the new version is ready\n\n```shell\nkubectl rollout restart deployment/nginx-ingress-nginx-controller\nkubectl rollout status deployment/nginx-ingress-nginx-controller\n```\n\nFollow logs\n\n```shell\nkubectl logs -l app=http-echo-service -c service -f\n```\n\nIn a separate terminal session export API token\n\n```shell\nexport API_TOKEN=$(kubectl get secret dapr-api-token -o jsonpath=\"{.data.token}\" | base64 --decode)\n```\n\nAnd invoke the service\n\n```shell\ncurl -d '{ \"message\": \"ping\" }' \\\n     -H \"Content-type: application/json\" \\\n     -H \"dapr-api-token: ${API_TOKEN}\" \\\n     \"https://api.cloudylabs.dev/v1.0/invoke/http-echo-service/method/echo\"\n```\n\nThe response should include the sent message \n\n```json\n{ \n    \"message\": \"ping\" \n}\n```\n\nAnd the logs\n\n```shell\nInvocation (ContentType:application/json, Verb:POST, QueryString:map[], Data:{ \"message\": \"ping\" })\n```\n\n## Disclaimer\n\nThis is my personal project and it does not represent my employer. While I do my best to ensure that everything works, I take no responsibility for issues caused by this code.\n\n## License\n\nThis software is released under the [MIT](../LICENSE)\n"
  },
  {
    "path": "http-echo-service/deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: http-echo-service\n  labels:\n    app: http-echo-service\n    demo: echo\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: http-echo-service\n  template:\n    metadata:\n      labels:\n        app: http-echo-service\n        demo: echo\n      annotations:\n        dapr.io/enabled: \"true\"\n        dapr.io/app-id: \"http-echo-service\"\n        dapr.io/app-protocol: \"http\"\n        dapr.io/app-port: \"8080\"\n        dapr.io/config: \"tracing\"\n        dapr.io/log-as-json: \"true\"\n        dapr.io/log-level: \"debug\"\n    spec:\n      containers:\n      - name: service\n        image: mchmarny/http-echo-service:v0.11.1\n        imagePullPolicy: Always\n        ports:\n        - containerPort: 8080\n        env:\n        - name: ADDRESS\n          value: \":8080\""
  },
  {
    "path": "http-echo-service/go.mod",
    "content": "module github.com/mchmarny/dapr-demos/http-echo-service\n\ngo 1.15\n\nrequire github.com/dapr/go-sdk v0.11.0\n"
  },
  {
    "path": "http-echo-service/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo=\ngithub.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "http-echo-service/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/dapr/go-sdk/service/common\"\n\tdaprd \"github.com/dapr/go-sdk/service/http\"\n)\n\nvar (\n\tlogger         = log.New(os.Stdout, \"\", 0)\n\tserviceAddress = getEnvVar(\"ADDRESS\", \":8080\")\n)\n\nfunc main() {\n\t// create serving server\n\ts := daprd.NewService(serviceAddress)\n\n\t// add handler to the service\n\ts.AddServiceInvocationHandler(\"echo\", echoHandler)\n\n\t// start the server to handle incoming events\n\tlog.Printf(\"starting server at %s...\", serviceAddress)\n\tif err := s.Start(); err != nil {\n\t\tlog.Fatalf(\"server error: %v\", err)\n\t}\n}\n\nfunc echoHandler(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) {\n\tlogger.Printf(\n\t\t\"Invocation (ContentType:%s, Verb:%s, QueryString:%s, Data:%s)\",\n\t\tin.ContentType, in.Verb, in.QueryString, string(in.Data),\n\t)\n\n\t// TODO: implement handling logic here\n\tout = &common.Content{\n\t\tContentType: in.ContentType,\n\t\tData:        in.Data,\n\t}\n\n\treturn\n}\n\nfunc getEnvVar(key, fallbackValue string) string {\n\tif val, ok := os.LookupEnv(key); ok {\n\t\treturn strings.TrimSpace(val)\n\t}\n\treturn fallbackValue\n}\n"
  },
  {
    "path": "http-event-subscriber/Dockerfile",
    "content": "FROM golang:1.15.0 as builder\n\nWORKDIR /src/\nCOPY . /src/\n\nENV GO111MODULE=on\n\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \\\n    go build -a -tags netgo -mod vendor -o ./service .\n\nFROM gcr.io/distroless/static:nonroot\nCOPY --from=builder /src/service .\n\nENTRYPOINT [\"./service\"]\n"
  },
  {
    "path": "http-event-subscriber/Makefile",
    "content": "RELEASE_VERSION  =v0.11.1\nSERVICE_NAME    ?=http-event-subscriber\nDOCKER_USERNAME ?=$(DOCKER_USER)\n\n.PHONY: tidy test debug build run jsonevent xmlevent binevent image lint clean tag\nall: help\n\ntidy: ## Updates the go modules and vendors all dependencies \n\tgo mod tidy\n\tgo mod vendor\n\ntest: tidy ## Tests the entire project \n\tgo test -count=1 -race ./...\n\ndebug: tidy ## Runs uncompiled code in Dapr\n\tdapr run \\\n      --app-id $(SERVICE_NAME) \\\n      --app-port 8080 \\\n      --app-protocol http \\\n      --dapr-http-port 3500 \\\n      --components-path ./config \\\n      --log-level debug \\\n      go run main.go\n\nbuild: tidy ## Builds local release binary\n\tCGO_ENABLED=0 go build -a -tags netgo -mod vendor -o bin/$(SERVICE_NAME) .\n\nrun: build ## Builds binary and runs it in Dapr\n\tdapr run \\\n      --app-id $(SERVICE_NAME) \\\n      --app-port 8080 \\\n      --app-protocol http \\\n      --dapr-http-port 3500 \\\n      --components-path ./config \\\n      --log-level debug \\\n      bin/$(SERVICE_NAME) \n\nce: ## Publishes CloudEvent message to Dapr pubsub API \n\tcurl -d@clooudevent.json \\\n     -H \"Content-type: application/json\" \\\n     \"http://localhost:3500/v1.0/publish/http-events/messages\"\n\njsonevent: ## Publishes sample JSON message to Dapr pubsub API \n\tcurl -d '{ \"from\": \"John\", \"to\": \"Lary\", \"message\": \"hi\" }' \\\n     -H \"Content-type: application/json\" \\\n     \"http://localhost:3500/v1.0/publish/http-events/messages\"\n\nxmlevent: ## Publishes sample XML message to Dapr pubsub API \n\tcurl -d '<message><from>John</from><to>Lary</to></message>' \\\n     -H \"Content-type: application/xml\" \\\n     \"http://localhost:3500/v1.0/publish/http-events/messages\"\n\nbinevent: ## Publishes sample binary message to Dapr pubsub API \n\tcurl -d '0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40' \\\n     -H \"Content-type: application/octet-stream\" \\\n     \"http://localhost:3500/v1.0/publish/http-events/messages\"\n\nimage: tidy ## Builds and publishes docker image \n\tdocker build -t \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\" .\n\tdocker push \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\"\n\ndeploy: ## Deploys prebuild image to k8s using currently selected context \n\tkubectl apply -f k8s/component.yaml\n\tkubectl apply -f k8s/deployment.yaml\n\tkubectl rollout restart deployment/http-event-subscriber\n\tkubectl rollout restart deployment/nginx-ingress-nginx-controller\n\tkubectl rollout status deployment/nginx-ingress-nginx-controller\n\nevent: ## Publishes sample JSON message to Dapr pubsub API \n\t$(eval API_TOKEN=$(shell kubectl get secret dapr-api-token -o jsonpath=\"{.data.token}\" | base64 --decode))\n\tcurl -d '{ \"from\": \"John\", \"to\": \"Lary\", \"message\": \"hi\" }' \\\n     -H \"Content-type: application/json\" \\\n     -H \"dapr-api-token: $(API_TOKEN)\" \\\n     \"https://api.cloudylabs.dev/v1.0/publish/http-events/messages\"\n\nlint: ## Lints the entire project \n\tgolangci-lint run --timeout=3m\n\ntag: ## Creates release tag \n\tgit tag $(RELEASE_VERSION)\n\tgit push origin $(RELEASE_VERSION)\n\nclean: ## Cleans up generated files \n\tgo clean\n\trm -fr ./bin\n\trm -fr ./vendor\n\nhelp: ## Display available commands\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \\\n\t\t'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n"
  },
  {
    "path": "http-event-subscriber/README.md",
    "content": "# dapr-event-subscriber-template\n\n## Components\n\n```yaml\napiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: events\nspec:\n  type: pubsub.redis\n  metadata:\n  - name: redisHost\n    value: localhost:6379\n  - name: redisPassword\n    value: \"\"\n\n```\n\nFor more information about pub/sub see the [Dapr docs](https://github.com/dapr/docs/tree/master/concepts/publish-subscribe-messaging)\n\n## Run \n\nTo run this demo in Dapr, run:\n\n```shell\ndapr run \\\n    --app-id grpc-event-subscriber-demo \\\n    --app-port 50001 \\\n    --app-protocol http \\\n    --dapr-http-port 3500 \\\n    --components-path ./config \\\n    go run main.go\n```\n\n\n## Deploy\n\nDeploy and wait for the pod to be ready \n\n```shell\nkubectl apply -f k8s/component.yaml\nkubectl apply -f k8s/deployment.yaml\nkubectl rollout status deployment/nginx-ingress-nginx-controller\n```\n\nIf you have changed an existing component, make sure to reload the deployment and wait until the new version is ready\n\n```shell\nkubectl rollout restart deployment/nginx-ingress-nginx-controller\nkubectl rollout status deployment/nginx-ingress-nginx-controller\n```\n\nFollow logs\n\n```shell\nkubectl logs -l app=http-event-subscriber -c service -f\n```\n\nIn a separate terminal session export API token\n\n```shell\nexport API_TOKEN=$(kubectl get secret dapr-api-token -o jsonpath=\"{.data.token}\" | base64 --decode)\n```\n\nAnd invoke the service\n\n```shell\ncurl -d '{ \"from\": \"John\", \"to\": \"Lary\", \"message\": \"hi\" }' \\\n     -H \"Content-type: application/json\" \\\n     -H \"dapr-api-token: ${API_TOKEN}\" \\\n     \"https://api.cloudylabs.dev/v1.0/publish/http-events/messages\"\n```\n\nIn the logs, you should see now an entry similar to this. Feel free to edit the message and try again.\n\n```shell\nevent - PubsubName:http-events, Topic:messages, ID:6b6cc665-684d-456c-8880-56e20cdf0519, Data: map[from:John message:hi to:Lary]\n```\n\n## Disclaimer\n\nThis is my personal project and it does not represent my employer. While I do my best to ensure that everything works, I take no responsibility for issues caused by this code.\n\n## License\n\nThis software is released under the [MIT](../LICENSE)\n"
  },
  {
    "path": "http-event-subscriber/clooudevent.json",
    "content": "{\n    \"specversion\" : \"1.0\",\n    \"type\" : \"some.other.operation\",\n    \"source\" : \"https://domain.com/cloudevents/operation\",\n    \"subject\" : \"test operation\",\n    \"id\" : \"id-1234-5678-9011\",\n    \"time\" : \"2020-09-23T06:23:21Z\",\n    \"comexampleextension1\" : \"value\",\n    \"comexampleothervalue\" : 5,\n    \"datacontenttype\" : \"text/xml\",\n    \"data\" : \"<note><to>User1</to><from>user2</from><message>hi</message></note>\"\n}"
  },
  {
    "path": "http-event-subscriber/config/pubsub.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: http-events\nspec:\n  type: pubsub.redis\n  metadata:\n  - name: redisHost\n    value: localhost:6379\n  - name: redisPassword\n    value: \"\"\n"
  },
  {
    "path": "http-event-subscriber/go.mod",
    "content": "module github.com/mchmarny/dapr-demos/http-event-subscriber\n\ngo 1.15\n\nrequire github.com/dapr/go-sdk v0.11.0\n"
  },
  {
    "path": "http-event-subscriber/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo=\ngithub.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "http-event-subscriber/k8s/component.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: http-events\nspec:\n  type: pubsub.redis\n  metadata:\n  - name: redisHost\n    value: redis-master.redis.svc.cluster.local:6379\n  - name: redisPassword\n    secretKeyRef:\n      name: redis-secret\n      key: password\n  - name: allowedTopics\n    value: \"messages\"\nscopes:\n- nginx-ingress\n- http-event-subscriber"
  },
  {
    "path": "http-event-subscriber/k8s/deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: http-event-subscriber\n  labels:\n    app: http-event-subscriber\n    demo: http-event\nspec:\n  selector:\n    matchLabels:\n      app: http-event-subscriber\n  template:\n    metadata:\n      labels:\n        app: http-event-subscriber\n        demo: http-event\n      annotations:\n        dapr.io/enabled: \"true\"\n        dapr.io/app-id: \"http-event-subscriber\"\n        dapr.io/app-port: \"8080\"\n        dapr.io/config: \"tracing\"\n        dapr.io/log-as-json: \"true\"\n        dapr.io/log-level: \"debug\"\n    spec:\n      containers:\n      - name: service\n        image: mchmarny/http-event-subscriber:v0.11.1\n        ports:\n        - containerPort: 8080\n        env:\n        - name: PORT\n          value: \"8080\"\n        - name: DAPR_HTTP_PORT\n          value: \"3500\"\n        - name: PUBSUB_NAME\n          value: \"http-events\"\n        - name: TOPIC_NAME\n          value: \"messages\""
  },
  {
    "path": "http-event-subscriber/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/dapr/go-sdk/service/common\"\n\tdaprd \"github.com/dapr/go-sdk/service/http\"\n)\n\nvar (\n\tlogger     = log.New(os.Stdout, \"\", 0)\n\taddress    = getEnvVar(\"ADDRESS\", \":8080\")\n\tpubSubName = getEnvVar(\"PUBSUB_NAME\", \"http-events\")\n\ttopicName  = getEnvVar(\"TOPIC_NAME\", \"messages\")\n)\n\nfunc main() {\n\t// create a Dapr service\n\ts := daprd.NewService(address)\n\n\t// add some topic subscriptions\n\tsubscription := &common.Subscription{\n\t\tPubsubName: pubSubName,\n\t\tTopic:      topicName,\n\t\tRoute:      fmt.Sprintf(\"/%s\", topicName),\n\t}\n\n\tif err := s.AddTopicEventHandler(subscription, eventHandler); err != nil {\n\t\tlogger.Fatalf(\"error adding topic subscription: %v\", err)\n\t}\n\n\t// start the service\n\tif err := s.Start(); err != nil && err != http.ErrServerClosed {\n\t\tlogger.Fatalf(\"error starting service: %v\", err)\n\t}\n}\n\nfunc eventHandler(ctx context.Context, e *common.TopicEvent) (retry bool, err error) {\n\tlogger.Printf(\n\t\t\"event - PubsubName:%s, Topic:%s, ID:%s, Data: %v\",\n\t\te.PubsubName, e.Topic, e.ID, e.Data,\n\t)\n\n\t// TODO: do something with the cloud event data\n\n\treturn false, nil\n}\n\nfunc getEnvVar(key, fallbackValue string) string {\n\tif val, ok := os.LookupEnv(key); ok {\n\t\treturn strings.TrimSpace(val)\n\t}\n\treturn fallbackValue\n}\n"
  },
  {
    "path": "order-cancellation/README.md",
    "content": "# Dapr integrations demo\n\n> WIP: This demo is being currently updated to Dapr v1.0.0.rc-1\n\n## Use-case\n\nOrder cancellation demo, loosely based on [Shopify API](https://shopify.dev/docs/admin-api/rest/reference/orders/order?api[version]=2020-04) order cancellation use-case to showcase multiple Dapr service integrations in a single solution: \n\n![Use-case](img/usecase.png)\n\n## Component Overview \n\n* **Dapr API** endpoint published with JWT token auth in **Daprized Ngnx** ingress\n* **Dapr Workflows** to orchestrate cancellation process using **Logic Apps** runtime\n* **Dapr Functions** extensions to create and persist audit state into **Mongo DB**\n* **Dapr Eventing** using **Redis Queue** for order message queue\n* **Daprized Web App** as order processing dashboard\n* **Dapr Binding** to send confirmation emails using **SendGrid**\n* **Dapr Distributed Tracing** to capture and forward traces to **Zipkin**\n\n![Draft Demo Flow Diagram](img/diagram.png)\n\n\n## Demo \n\nDapr integration demo consists of:\n\n1. Starting the order processing dashboard \n2. Submitting cancellation request \n3. Viewing processed request in the dashboard \n4. Querying the state store for cancellation data\n5. Showing order cancellation confirmation email \n6. Review of the distributed traces for entire process \n\n> Note, instructions on how to setup a Kubernetes cluster for this demo are located [here](../setup/README.md)\n\n### 1. Dashboard \n\n> Note, these instructions assume `demo.dapr.team` domain setup in the [cluster setup](../setup/README.md) step. You will need to substitute this for your own domain. \n\nNavigate to https://order.demo.dapr.team to start the order processing dashboard. There won't be any data yet, so this is just to open the WebSocket connection. \n\n![Initial UI](img/ui1.png)\n\n### 2. Submit Cancellation \n\nSubmit the order [cancellation.json](demo/data/cancellation.json) file using `curl`\n\n```shell\nAPI_TOKEN=$(kubectl get secret dapr-api-token -n nginx -o jsonpath=\"{.data.token}\" | base64 --decode)\ncurl -i \\\n     -d @demo/data/cancellation.json \\\n     -H \"Content-type: application/json\" \\\n     -H \"dapr-api-token: ${API_TOKEN}\" \\\n     \"https://api.demo.dapr.team/v1.0/invoke/workflows.order/method/order-cancel\"\n```\n\n### 3. Dashboard (updated)\n\nView the dashboard again at https://order.demo.dapr.team to see the orders\n\n### 4. Email \n\nCheck configured email box for cancellation confirmation after the processed completed \n\n> Make sure to check junk mail!\n\n### 5. Observability \n\nForward local ports:\n\n```shell\nkubectl port-forward svc/kibana-kibana 5601 -n dapr-monitoring &\nkubectl port-forward svc/grafana 8888:80 -n dapr-monitoring &\nkubectl port-forward svc/zipkin 9411 -n dapr-monitoring &\n```\n\n#### Traces\n\nNavigate to Zipkin: http://localhost:9411\n\n#### Logs \n\nNavigate to Kibana: http://localhost:5601\n\n#### Metrics \n\nNavigate to Grafana: http://localhost:8888\n\n## Setup  \n\nFor script through this demo see [setup](./demo).\n\n\n\n"
  },
  {
    "path": "order-cancellation/demo/Makefile",
    "content": ".PHONY: help, workflowport, fnport, postmeta, postmail, postcancel, postgateway\nall: help\n\nworkflowport: ## Forwards Dapr Workflow locally \n\tkubectl port-forward deployment/dapr-workflows-host 3500:3500\n\nfnport: ## Forwards Dapr Functions locally \n\tkubectl port-forward deployment/auditor 3500:3500\n\npostmeta: ## Invokes cancel on the workflow \n\tcurl -v -d @data/meta.json -H \"Content-type: application/json\" \\\n\t\t http://localhost:3500/v1.0/publish/cancellations\n\npostmail: ## Invokes email on the workflow \n\tcurl -v -d @data/email.json -H \"Content-type: application/json\" \\\n\t\t http://localhost:3500/v1.0/bindings/email\n\npostcancel: ## Invokes cancel on the workflow \n\tcurl -v -d @data/cancellation.json \\\n\t\t -H \"Content-type: application/json\" \\\n\t\t http://localhost:3500/v1.0/invoke/workflows/method/order-cancel\n\npostgateway: ## Invokes cancel on the gateway \n\t$(eval API_TOKEN=$(shell kubectl get secret dapr-api-token -o jsonpath=\"{.data.token}\" | base64 --decode))\n\tcurl -v -d @data/cancellation.json \\\n     -H \"Content-type: application/json\" \\\n     -H \"dapr-api-token: $(API_TOKEN)\" \\\n     \"https://api.cloudylabs.dev/v1.0/publish/queue/processed\"\n\n\t\nhelp: ## Display available commands\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \\\n\t\t'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n"
  },
  {
    "path": "order-cancellation/demo/README.md",
    "content": "# Dapr integrations demo setup\n\n> Note, this setup assumes Kubernetes cluster created using the [demo cluster setup instructions](../../setup/)\n\n## Components \n\nStart by create the `order` namespace: \n\n```shell\nkubectl apply -f ./deployment/space.yaml\n```\n\nSandGrid secret\n\n> the queue and store secrets were already created during the cluster setup\n\n```shell\nkubectl create secret generic email --from-literal=api-key=\"${SENDGRID_KEY}\" -n order\n```\n\nNow deploy the queue, state and workflow stores, and email components\n\n```shell\nkubectl apply -f ./component\n```\n\n## Deployments \n\n### Auditor \n\nDeploy Dapr auditor functions and wait for it to be ready \n\n```shell\nkubectl apply -f deployment/auditor.yaml\nkubectl rollout status deployment/order-auditor -n order\n```\n\nCheck logs for errors from both containers\n\n```shell\nkubectl logs -l app=order-auditor -c daprd -n order --tail 300\nkubectl logs -l app=order-auditor -c auditor -n order\n```\n\n### Workflow \n\nCreate the config map to hold the Dapr workflow definition\n\n```shell\nkubectl create cm workflows --from-file config/order-cancel.json -n order\n```\n\nCreate the Azure storage account \n\n```shell\naz storage account create --name daprintdemo --sku Standard_LRS\nexport AZSAKEY=$(az storage account keys list --account-name daprintdemo --query \"[0].value\" --output tsv)\n```\n\nCreate secret to hold the workflow Azure storage account key\n\n> TODO: Remove Azure storage account  dependency \n\n```shell\nkubectl create secret generic dapr-workflows \\\n  --from-literal=accountName=daprintdemo \\\n  --from-literal=accountKey=$AZSAKEY \\\n  -n order\n```\n\nDeploy Dapr Workflows host and wait for it to be ready\n\n```shell\nkubectl apply -f deployment/workflow.yaml\nkubectl rollout status deployment/workflows-host\n```\n\nCheck logs for errors from both containers\n\n```shell\nkubectl logs -l app=workflows-host -c daprd -n order --tail 300\nkubectl logs -l app=workflows-host -c host -n order\n```\n\n### Viewer\n\nDeploy Dashboard \n\n```shell\nkubectl apply -f deployment/viewer.yaml\nkubectl rollout status deployment/order-viewer\n```\n\nCreate the TLS certs for this domain \n\n> `demo.dapr.team` is the domain I'm using for this demo\n\n```shell\nkubectl create secret tls tls-secret \\\n    -n order \\\n    --key ../../setup/certs/demo.dapr.team/cert-pk.pem \\\n    --cert ../../setup/certs/demo.dapr.team/cert-ca.pem\n```\n\nDeploy ingress for `order` \n\n```shell\nkubectl apply -f deployment/ingress.yaml\n```\n\n\nTest it: https://order.demo.dapr.team\n\n## Cleanup \n\n\n```shell\nkubectl delete -f ./deployment\nkubectl delete -f ./component\n\nkubectl delete secret email -n order\nkubectl delete secret dapr-workflows -n order\nkubectl delete configmap workflows -n order\n\naz storage account delete --name daprintdemo\n```\n\n\n"
  },
  {
    "path": "order-cancellation/demo/component/audit-store.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: order-audit-store\n  namespace: order\nspec:\n  type: state.mongodb\n  metadata:\n  - name: host\n    value: mongo-mongodb-headless.mongo.svc.cluster.local:27017\n  - name: username\n    value: dapr\n  - name: password\n    secretKeyRef:\n      name: mongo-secret\n      key: password\n  - name: databaseName\n    value: dapr\n  - name: collectionName\n    value: audit\nscopes:\n- order-auditor"
  },
  {
    "path": "order-cancellation/demo/component/email.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: order-email\n  namespace: order\nspec:\n  type: bindings.twilio.sendgrid\n  metadata:\n  - name: emailFrom\n    value: \"demo@thingz.io\"\n  - name: apiKey\n    secretKeyRef:\n      name: email\n      key: api-key\nscopes:\n- workflows"
  },
  {
    "path": "order-cancellation/demo/component/queue.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: order-queue\n  namespace: order\nspec:\n  type: pubsub.redis\n  metadata:\n  - name: redisHost\n    value: redis-master.redis.svc.cluster.local:6379\n  - name: redisPassword\n    secretKeyRef:\n      name: redis-secret\n      key: password\nscopes:\n- order-auditor\n- workflows\n- order-viewer"
  },
  {
    "path": "order-cancellation/demo/component/workflow-store.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: order-store\n  namespace: order\nspec:\n  type: state.mongodb\n  metadata:\n  - name: host\n    value: mongo-mongodb-headless.mongo.svc.cluster.local:27017\n  - name: username\n    value: dapr\n  - name: password\n    secretKeyRef:\n      name: mongo-secret\n      key: password\n  - name: databaseName\n    value: dapr\n  - name: collectionName\n    value: record\nscopes:\n- workflows\n"
  },
  {
    "path": "order-cancellation/demo/config/order-cancel.json",
    "content": "{\n    \"definition\": {\n        \"$schema\": \"https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#\",\n        \"actions\": {\n            \"PublishCancellationsTopic\": {\n                \"type\": \"Http\",\n                \"inputs\": {\n                    \"method\": \"POST\",\n                    \"uri\": \"http://localhost:3500/v1.0/publish/order-queue/cancellations\",\n                    \"body\": {\n                        \"id\": \"@triggerBody().id\",\n                        \"note\": \"@triggerBody().note\",\n                        \"submitted_on\": \"@triggerBody().submitted_on\"\n                    },\n                    \"headers\": {\n                        \"Content-Type\": \"application/json\"\n                    }\n                },\n                \"runAfter\": {}\n            },\n            \"SaveState\": {\n                \"type\": \"Http\",\n                \"inputs\": {\n                    \"method\": \"POST\",\n                    \"uri\": \"http://localhost:3500/v1.0/state/order-store\",\n                    \"body\": [\n                        {\n                            \"key\": \"@triggerBody().id\",\n                            \"value\": \"@triggerBody()\"\n                        }\n                    ],\n                    \"headers\": {\n                        \"Content-Type\": \"application/json\"\n                    }\n                },\n                \"runAfter\": {\n                    \"PublishCancellationsTopic\": [\n                        \"Succeeded\"\n                    ]\n                }\n            },\n            \"PublishProcessedTopic\": {\n                \"type\": \"Http\",\n                \"inputs\": {\n                    \"method\": \"POST\",\n                    \"uri\": \"http://localhost:3500/v1.0/publish/order-queue/processed\",\n                    \"body\": \"@triggerBody()\",\n                    \"headers\": {\n                        \"Content-Type\": \"application/json\"\n                    }\n                },\n                \"runAfter\": {\n                    \"SaveState\": [\n                        \"Succeeded\"\n                    ]\n                }\n            },\n            \"SendConfirmationEmail\": {\n                \"type\": \"Http\",\n                \"inputs\": {\n                    \"method\": \"POST\",\n                    \"uri\": \"http://localhost:3500/v1.0/bindings/order-email\",\n                    \"body\": {\n                        \"operation\": \"create\",\n                        \"metadata\": {\n                            \"emailTo\": \"mchmarny@microsoft.com\",\n                            \"subject\": \"Order cancellation confirmation\"\n                          }, \n                          \"data\": \"<h1>Order cancellation confirmed</h1><p>Your order has been canceled.</p><p>Thank you and we hope you come back.</p>\"\n                    },\n                    \"headers\": {\n                        \"Content-Type\": \"application/json\"\n                    }\n                },\n                \"runAfter\": {\n                    \"PublishProcessedTopic\": [\n                        \"Succeeded\"\n                    ]\n                }\n            },\n            \"Response\": {\n                \"inputs\": {\n                    \"body\": {\n                        \"id:\": \"@triggerBody().id\",\n                        \"submitted_on\": \"@triggerBody().submitted_on\",\n                        \"approved\": true\n                    },\n                    \"statusCode\": 200\n                },\n                \"runAfter\": {\n                    \"PublishProcessedTopic\": [\n                        \"Succeeded\"\n                    ]\n                },\n                \"type\": \"Response\"\n            }\n        },\n        \"contentVersion\": \"1.0.0.0\",\n        \"outputs\": {},\n        \"parameters\": {},\n        \"triggers\": {\n            \"manual\": {\n                \"inputs\": {\n                    \"schema\": {}\n                },\n                \"kind\": \"Http\",\n                \"type\": \"Request\"\n            }\n        }\n    }\n}"
  },
  {
    "path": "order-cancellation/demo/data/cancellation.json",
    "content": "{\n  \"id\": \"f727735e-b64e-11ea-b3de-0242ac130004\",\n  \"note\": \"Customer made a mistake\",\n  \"submitted_on\": \"2020-06-24T13:02:15Z\",\n  \"refund\": {\n    \"shipping\": {\n      \"full_refund\": true\n    },\n    \"refund_line_items\": [\n      {\n        \"line_item_id\": 466157049,\n        \"quantity\": 1,\n        \"restock_type\": \"cancel\",\n        \"location_id\": 48752903\n      }\n    ],\n    \"transactions\": [\n      {\n        \"parent_id\": 1072844675,\n        \"amount\": \"10.00\",\n        \"kind\": \"refund\",\n        \"gateway\": \"bogus\"\n      },\n      {\n        \"parent_id\": 1072844676,\n        \"amount\": \"100.00\",\n        \"kind\": \"refund\",\n        \"gateway\": \"gift_card\"\n      }\n    ]\n  }\n}"
  },
  {
    "path": "order-cancellation/demo/data/email.json",
    "content": "{\n  \"operation\": \"create\",\n  \"metadata\": {\n    \"emailTo\": \"mchmarny@microsoft.com\",\n    \"subject\": \"Order cancellation confirmation\"\n  }, \n  \"data\": \"<h1>Order cancellation confirmed</h1><p>Your order from 2020-06-24T13:02:15Z has been canceled.</p><p>Thank you and we hope you come back.</p><p>Order: b76b547e-b64e-11ea-b3de-0242ac130004</p>\"\n}"
  },
  {
    "path": "order-cancellation/demo/data/meta.json",
    "content": "{\n  \"id\": \"b76b547e-b64e-11ea-b3de-0242ac130004\",\n  \"note\": \"Customer made a mistake\",\n  \"submitted_on\": \"2020-06-24T13:02:15Z\"\n}"
  },
  {
    "path": "order-cancellation/demo/deployment/auditor.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: order-auditor\n  namespace: order\n  labels:\n    app: order-auditor\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: order-auditor\n  template:\n    metadata:\n      labels:\n        app: order-auditor\n      annotations:\n        dapr.io/enabled: \"true\"\n        dapr.io/app-id: \"order-auditor\"\n        dapr.io/app-protocol: \"http\"\n        dapr.io/app-port: \"3001\"\n        dapr.io/config: \"order-config\"\n        dapr.io/log-as-json: \"true\"\n    spec:\n      containers:\n      - name: auditor\n        image: mchmarny/auditor:v0.2.10\n        ports:\n        - containerPort: 3001\n        env:\n        - name: StateStore\n          value: \"order-audit-store\"\n        - name: StateKey\n          value: \"id\"\n        - name: PubSubName\n          value: \"order-queue\"\n        - name: TopicName\n          value: \"cancellations\""
  },
  {
    "path": "order-cancellation/demo/deployment/ingress.yaml",
    "content": "apiVersion: extensions/v1beta1\nkind: Ingress\nmetadata:\n  name: order-ingress-rules\n  namespace: order\n  annotations:\n    kubernetes.io/ingress.class: nginx\n    nginx.ingress.kubernetes.io/rewrite-target: /\nspec:\n  tls:\n    - hosts:  \n      - order.demo.dapr.team\n      secretName: tls-secret\n  rules:\n    - host: order.demo.dapr.team\n      http:\n        paths:\n          - path: /\n            backend:\n              serviceName: order-viewer\n              servicePort: 80"
  },
  {
    "path": "order-cancellation/demo/deployment/space.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  name: order\n---\napiVersion: dapr.io/v1alpha1\nkind: Configuration\nmetadata:\n  name: order-config\n  namespace: order\nspec:\n  tracing:\n    samplingRate: \"1\"\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: secret-reader\n  namespace: order\nrules:\n- apiGroups: [\"\"]\n  resources: [\"secrets\"]\n  verbs: [\"get\"]\n---\nkind: RoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: dapr-secret-reader\n  namespace: order\nsubjects:\n- kind: ServiceAccount\n  name: default\nroleRef:\n  kind: Role\n  name: secret-reader\n  apiGroup: rbac.authorization.k8s.io\n---\napiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: zipkin\n  namespace: order\nspec:\n  type: exporters.zipkin\n  metadata:\n  - name: enabled\n    value: \"true\"\n  - name: exporterAddress\n    value: \"http://zipkin.dapr-monitoring.svc.cluster.local:9411/api/v2/spans\"\n"
  },
  {
    "path": "order-cancellation/demo/deployment/viewer.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: order-viewer\n  namespace: order\n  labels:\n    app: order-viewer\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: order-viewer\n  template:\n    metadata:\n      labels:\n        app: order-viewer\n      annotations:\n        dapr.io/enabled: \"true\"\n        dapr.io/app-id: \"order-viewer\"\n        dapr.io/app-protocol: \"http\"\n        dapr.io/app-port: \"8080\"\n        dapr.io/config: \"order-config\"\n        dapr.io/log-as-json: \"true\"\n        dapr.io/log-level: \"debug\"\n    spec:\n      containers:\n      - name: viewer\n        image: mchmarny/order-viewer:v0.11.1\n        ports:\n        - containerPort: 8080\n        env:\n        - name: ADDRESS\n          value: \":8080\"\n        - name: PUBSUB_NAME\n          value: order-queue\n        - name: TOPIC_NAME\n          value: processed\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: order-viewer\n  namespace: order\nspec:\n  selector:\n    app: order-viewer\n  ports:\n    - protocol: TCP\n      port: 80\n      targetPort: 8080"
  },
  {
    "path": "order-cancellation/demo/deployment/workflow.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: workflows-host\n  namespace: order\n  labels:\n    app: workflows-host\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: workflows-host\n  template:\n    metadata:\n      labels:\n        app: workflows-host\n      annotations:\n        dapr.io/app-id: \"workflows\"\n        dapr.io/enabled: \"true\"\n        dapr.io/app-port: \"50003\"\n        dapr.io/app-protocol: \"grpc\"\n        dapr.io/config: \"order-config\"\n        dapr.io/log-level: \"debug\"\n        dapr.io/log-as-json: \"true\"\n    spec:\n      containers:\n      - name: host\n        image: daprio/workflows:0.2.2\n        env:\n        - name: STORAGE_ACCOUNT_KEY\n          valueFrom:\n            secretKeyRef:\n              name: dapr-workflows\n              key: accountKey\n        - name: STORAGE_ACCOUNT_NAME\n          valueFrom:\n            secretKeyRef:\n              name: dapr-workflows\n              key: accountName\n        command: [\"dotnet\"]\n        args: [\"app/Dapr.Workflows.dll\", \"--workflows-path\", \"/workflows\"]\n        ports:\n        - containerPort: 50003\n        imagePullPolicy: Always\n        volumeMounts:\n        - name: workflows\n          mountPath: /workflows\n      volumes:\n        - name: workflows\n          configMap:\n            name: workflows"
  },
  {
    "path": "order-cancellation/src/fn/.gitignore",
    "content": "# Build generated files in src\n/objd/**/*\n/obj/**/*\n/bin/**/*\n/gen/**/*\n/.vs/**/*\n/.vscode/**/*\n"
  },
  {
    "path": "order-cancellation/src/fn/DaprAzFn.csproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n  <PropertyGroup>\n    <TargetFramework>netcoreapp3.0</TargetFramework>\n    <AzureFunctionsVersion>v3</AzureFunctionsVersion>\n    <RootNamespace>DaprAzFn</RootNamespace>\n  </PropertyGroup>\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.Azure.WebJobs.Extensions.Storage\" Version=\"3.0.10\" />\n    <PackageReference Include=\"Microsoft.NET.Sdk.Functions\" Version=\"3.0.3\" />    \n  </ItemGroup>\n  <ItemGroup>\n    <PackageReference Include=\"Dapr.AzureFunctions.Extension\" Version=\"0.10.0-preview01\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Update=\"host.json\">\n      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>\n    </None>\n    <None Update=\"local.settings.json\">\n      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>\n      <CopyToPublishDirectory>Never</CopyToPublishDirectory>\n    </None>\n  </ItemGroup>\n</Project>\n"
  },
  {
    "path": "order-cancellation/src/fn/Dockerfile",
    "content": "FROM mcr.microsoft.com/dotnet/core/sdk:3.0 AS installer-env\n\nCOPY . /src/DaprAzFnSample\nRUN cd /src/DaprAzFnSample && \\\n    mkdir -p /home/site/wwwroot && \\\n    dotnet publish *.csproj --output /home/site/wwwroot\n\n# To enable ssh & remote debugging on app service change the base image to the one below\n# FROM mcr.microsoft.com/azure-functions/dotnet:3.0-appservice\nFROM mcr.microsoft.com/azure-functions/dotnet:3.0\nENV AzureWebJobsScriptRoot=/home/site/wwwroot \\\n    AzureFunctionsJobHost__Logging__Console__IsEnabled=true\n\nCOPY --from=installer-env [\"/home/site/wwwroot\", \"/home/site/wwwroot\"]"
  },
  {
    "path": "order-cancellation/src/fn/Makefile",
    "content": "APP_NAME         =auditor\nRELEASE_VERSION  =v0.2.10\nDOCKER_USERNAME ?=$(DOCKER_USER)\n\n.PHONY: test mod run dapr lint image tag clean help\nall: test\n\nrestore: ## Restores the project\n\tdotnet restore\n\nrun: ## Runs function locally \n\tdapr run --app-id fn \\\n             --app-port 3001 \\\n             --port 3501 \\\n             --components-path config \\\n             env PubSubName=queue TopicName=cancellations StateStore=store func host start\n\nevent: ## Publishes event onto the Dapr topic \n\tcurl -d @./config/meta.json \\\n\t\t -H \"Content-type: application/json\" \\\n\t\t http://localhost:3501/v1.0/publish/queue/cancellations\n\ntest: restore ## Tests the entire project \n\tdotnet test\n\nimage: restore ## Builds docker image and publishes it to Dockerhub\n\tdocker build -t $(DOCKER_USERNAME)/$(APP_NAME):$(RELEASE_VERSION) .\n\tdocker push $(DOCKER_USERNAME)/$(APP_NAME):$(RELEASE_VERSION)\n\n\nhelp: ## Display available commands\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \\\n\t\t'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n"
  },
  {
    "path": "order-cancellation/src/fn/ReceiveEvent.cs",
    "content": "// ------------------------------------------------------------\n// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n// ------------------------------------------------------------\n\nnamespace DaprAzFn.Sample\n{\n    using System;\n    using System.IO;\n    using System.Threading.Tasks;\n    using Microsoft.AspNetCore.Mvc;\n    using CloudNative.CloudEvents;\n    using Microsoft.Azure.WebJobs;\n    using Dapr.AzureFunctions.Extension;\n    using Microsoft.Azure.WebJobs.Extensions.Http;\n    using Microsoft.AspNetCore.Http;\n    using Microsoft.Extensions.Logging;\n    using Newtonsoft.Json.Linq;\n\n    public static class ReceiveTopicMessage\n    {\n        /// <summary>\n        /// Subscribes to topic and saves it to state store \n        /// </summary>\n        [FunctionName(\"ReceiveTopicMessage\")]\n        public static async Task<IActionResult> Run(\n            [DaprTopicTrigger(\"%PubSubName%\", Topic = \"%TopicName%\")] CloudEvent cloudEvent,\n            [DaprState(\"%StateStore%\")] IAsyncCollector<DaprStateRecord> state, ILogger log)\n        {\n            // Get data from CloudEvent\n            log.LogInformation($\"Received message: {cloudEvent.Data}.\");\n            var cancellationData = cloudEvent.Data as JToken;\n\n            // TODO: Implement cancellation validaiton logic.  \n\n            // Parse state ID and Save\n            var keyname = Environment.GetEnvironmentVariable(\"StateKey\") ?? \"id\";\n            var key = cancellationData.Value<string>(keyname);\n            var stateRec = new DaprStateRecord(key, cancellationData);\n            await state.AddAsync(stateRec);\n            return new OkResult();\n        }\n    }\n}\n"
  },
  {
    "path": "order-cancellation/src/fn/config/meta.json",
    "content": "{\n  \"id\": \"b76b547e-b64e-11ea-b3de-0242ac130004\",\n  \"note\": \"Customer made a mistake\",\n  \"submitted_on\": \"2020-06-24T13:02:15Z\"\n}"
  },
  {
    "path": "order-cancellation/src/fn/config/pubsub.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: queue\nspec:\n  type: pubsub.redis\n  metadata:\n  - name: redisHost\n    value: localhost:6379\n  - name: redisPassword\n    value: \"\"\n"
  },
  {
    "path": "order-cancellation/src/fn/config/statestore.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: store\nspec:\n  type: state.redis\n  metadata:\n  - name: redisHost\n    value: localhost:6379\n  - name: redisPassword\n    value: \"\"\n"
  },
  {
    "path": "order-cancellation/src/fn/host.json",
    "content": "{\n    \"version\": \"2.0\"\n}"
  },
  {
    "path": "order-cancellation/src/fn/local.settings.json",
    "content": "{\n  \"IsEncrypted\": false,\n  \"Values\": {\n    \"AzureWebJobsStorage\": \"UseDevelopmentStorage=true\",\n    \"FUNCTIONS_WORKER_RUNTIME\": \"dotnet\",\n    \"StateStore\": \"statestore\",\n    \"StateKey\": \"id\",\n    \"TopicName\": \"cancellations\"\n  }\n}"
  },
  {
    "path": "order-cancellation/src/viewer/Dockerfile",
    "content": "FROM golang:1.15.0 as builder\n\nWORKDIR /src/\nCOPY . /src/\n\nARG RELEASE_VERSION=v0.0.1-default\n\nENV RELEASE_VERSION=$RELEASE_VERSION\nENV GO111MODULE=on\n\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \\\n    go build -a -tags netgo -ldflags \\\n    \"-w -extldflags '-static' -X main.AppVersion=${RELEASE_VERSION}\" \\\n    -mod vendor -o ./service .\n\nFROM gcr.io/distroless/static:nonroot\nCOPY --from=builder /src/service .\nCOPY --from=builder /src/resource ./resource/\n\nENTRYPOINT [\"./service\"]\n"
  },
  {
    "path": "order-cancellation/src/viewer/Makefile",
    "content": "APP_NAME         =order-viewer\nRELEASE_VERSION  =v0.11.1\nDOCKER_USERNAME ?=$(DOCKER_USER)\n\n.PHONY: test mod run dapr lint image tag clean help\nall: test\n\ntidy: ## Updates the go modules and vendors \n\tgo mod tidy\n\tgo mod vendor\n\ntest: mod ## Tests the entire project \n\tgo test -v -count=1 -race ./...\n\t# go test -v -count=1 -run NameOfSingleTest ./...\n\nrun: mod ## Runs the uncompiled code by itsef \n\tgo run -v main.go\n\nbuild: mod ## Build app binary locally \n\tCGO_ENABLED=0 GOOS=linux GOARCH=amd64 \\\n    go build -a -tags netgo -ldflags \\\n    \"-w -extldflags '-static' -X main.AppVersion=$(RELEASE_VERSION)\" \\\n    -mod vendor -o ./bin/service .\n\ndapr: mod ## Runs the uncompiled code in Dapr\n\tdapr run --app-id viewer \\\n\t\t\t --app-port 8083 \\\n\t\t\t --app-protocol http \\\n\t\t\t --dapr-http-port 3500 \\\n\t\t\t --components-path ./config \\\n\t\t\t go run main.go\n\nimage: mod ## Builds docker image and publishes it to Dockerhub\n\tdocker build --build-arg APP_VERSION=$(RELEASE_VERSION) \\\n\t-t $(DOCKER_USERNAME)/$(APP_NAME):$(RELEASE_VERSION) .\n\tdocker push $(DOCKER_USERNAME)/$(APP_NAME):$(RELEASE_VERSION)\n\nlint: ## Lints the entire project\n\tgolangci-lint run --timeout=3m\n\ntag: ## Creates release tag \n\tgit tag $(RELEASE_VERSION)\n\tgit push origin $(RELEASE_VERSION)\n\nclean: ## Cleans go and generated files in ./dapr/proto/\n\tgo clean\n\trm -fr ./vendor\n\nhelp: ## Display available commands\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \\\n\t\t'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n"
  },
  {
    "path": "order-cancellation/src/viewer/config/queue.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: queue\nspec:\n  type: pubsub.azure.servicebus\n  metadata:\n  - name: connectionString\n    value: Endpoint=sb://daprdemo.servicebus.windows.net/;SharedAccessKeyName=view;SharedAccessKey=RbTBiNnoj7wfxHqG8XeHWYn3sL4VFbdBpn0cTNi3Hyo=;EntityPath=processed\n"
  },
  {
    "path": "order-cancellation/src/viewer/go.mod",
    "content": "module github.com/mchmarny/dapr-demos/order-cancellation/src/viewer\n\ngo 1.15\n\nrequire (\n\tgithub.com/dapr/go-sdk v0.11.0\n\tgithub.com/gorilla/websocket v1.4.2 // indirect\n\tgithub.com/pkg/errors v0.9.1\n\tgopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376\n)\n"
  },
  {
    "path": "order-cancellation/src/viewer/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo=\ngithub.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=\ngithub.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376 h1:sY2a+y0j4iDrajJcorb+a0hJIQ6uakU5gybjfLWHlXo=\ngopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376/go.mod h1:BHKOc1m5wm8WwQkMqYBoo4vNxhmF7xg8+xhG8L+Cy3M=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "order-cancellation/src/viewer/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"html/template\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\n\t\"gopkg.in/olahol/melody.v1\"\n\n\t\"github.com/dapr/go-sdk/service/common\"\n\tdaprd \"github.com/dapr/go-sdk/service/http\"\n\t\"github.com/pkg/errors\"\n)\n\nvar (\n\t// AppVersion will be overritten during build\n\tAppVersion = \"v0.0.1-default\"\n\n\t// service\n\tlogger     = log.New(os.Stdout, \"\", 0)\n\taddress    = getEnvVar(\"ADDRESS\", \":8083\")\n\tpubSubName = getEnvVar(\"PUBSUB_NAME\", \"queue\")\n\ttopicName  = getEnvVar(\"TOPIC_NAME\", \"processed\")\n\n\tbroadcaster *melody.Melody\n\ttemplates   *template.Template\n)\n\nfunc main() {\n\n\t// server mux\n\tmux := http.NewServeMux()\n\n\t// static content\n\tmux.Handle(\"/static/\", http.StripPrefix(\"/static/\", http.FileServer(http.Dir(\"resource/static\"))))\n\tmux.HandleFunc(\"/favicon.ico\", faviconHandler)\n\n\t// tempalates\n\ttemplates = template.Must(template.ParseGlob(\"resource/template/*\"))\n\n\t// websocket upgrade\n\tbroadcaster = melody.New()\n\tbroadcaster.Upgrader.CheckOrigin = func(r *http.Request) bool { return true }\n\n\t// other handlers\n\tmux.HandleFunc(\"/\", rootHandler)\n\tmux.HandleFunc(\"/ws\", wsHandler)\n\n\t// create a Dapr service\n\ts := daprd.NewServiceWithMux(address, mux)\n\n\t// add some topic subscriptions\n\tsubscription := &common.Subscription{\n\t\tPubsubName: pubSubName,\n\t\tTopic:      topicName,\n\t\tRoute:      fmt.Sprintf(\"/%s\", topicName),\n\t}\n\n\tif err := s.AddTopicEventHandler(subscription, eventHandler); err != nil {\n\t\tlogger.Fatalf(\"error adding topic subscription: %v\", err)\n\t}\n\n\t// start the service\n\tif err := s.Start(); err != nil && err != http.ErrServerClosed {\n\t\tlogger.Fatalf(\"error starting service: %v\", err)\n\t}\n}\n\nfunc wsHandler(w http.ResponseWriter, r *http.Request) {\n\tbroadcaster.HandleRequest(w, r)\n}\n\nfunc rootHandler(w http.ResponseWriter, r *http.Request) {\n\tproto := r.Header.Get(\"x-forwarded-proto\")\n\tif proto == \"\" {\n\t\tproto = \"http\"\n\t}\n\n\tdata := map[string]string{\n\t\t\"host\":    r.Host,\n\t\t\"proto\":   proto,\n\t\t\"version\": AppVersion,\n\t}\n\n\terr := templates.ExecuteTemplate(w, \"index\", data)\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n}\n\nfunc faviconHandler(w http.ResponseWriter, r *http.Request) {\n\thttp.ServeFile(w, r, \"./resource/static/img/favicon.ico\")\n}\n\nfunc eventHandler(ctx context.Context, e *common.TopicEvent) (retry bool, err error) {\n\tlogger.Printf(\n\t\t\"event - PubsubName:%s, Topic:%s, ID:%s, Data: %v\",\n\t\te.PubsubName, e.Topic, e.ID, e.Data,\n\t)\n\n\tb, err := json.Marshal(e.Data)\n\tif err != nil {\n\t\treturn false, errors.Wrap(err, \"error marshaling data\")\n\t}\n\n\tbroadcaster.Broadcast(b)\n\treturn false, nil\n}\n\nfunc getEnvVar(key, fallbackValue string) string {\n\tif val, ok := os.LookupEnv(key); ok {\n\t\treturn strings.TrimSpace(val)\n\t}\n\treturn fallbackValue\n}\n"
  },
  {
    "path": "order-cancellation/src/viewer/resource/static/css/app.css",
    "content": "\r\nhtml,\r\nbody {\r\n\theight: 100%;\r\n\tmargin: 0;\r\n\tpadding: 0;\r\n\tbackground-color: #fff;\r\n\tfont-family: Geneva, Verdana, sans-serif;\r\n}\r\n\r\n#wrapper {\r\n\tpadding: 0;\r\n\tmargin: 0;\r\n\theight: 100%;\r\n\ttext-align: center;\r\n\twidows: 100%;\r\n}\r\n\r\n#page-header {\r\n\tpadding: 0;\r\n\tmargin: 10px 0 0 0;\r\n\tclear: both;\r\n\tbreak-after: always;\r\n\theight: 100px;\r\n}\r\n\r\n#page-header-image {\r\n\tborder: 0;\r\n\tmargin-left: 25px;\r\n}\r\n\r\n#page-header-image img {\r\n\twidth: 100px;\r\n\theight: 100px;\r\n\tfloat: left;\r\n}\r\n\r\n#connection {\r\n\tfloat: right;\r\n\tmargin: 10px;\r\n\t\r\n\tfont-size: 1em;\r\n}\r\n\r\n#middle-section {\r\n\tmargin: 0;\r\n\tpadding: 10px;\r\n\theight: 700px;\r\n\toverflow: auto;\r\n}\r\n\r\n#middle-section div {\r\n\tmargin: 5px;\r\n}\r\n\r\n#page-footer {\r\n\twidows: 80%;\r\n\tmargin: 30px 0 0 0;\r\n\tfont-size: 1em;\r\n}\r\n\r\n#items {\r\n\tbackground-color: #20329b;\r\n\tpadding: 0;\r\n\tmargin: 5px;\r\n\theight: 690px;\r\n\toverflow: auto;\r\n}\r\n\r\n.item {\r\n\tpadding: 3px;\r\n}\r\n\r\n.meta-item {\r\n\tfont-size: 0.9em;\r\n\tpadding: 0;\r\n}\r\n\r\n.meta-item b {\r\n\tfont-weight: bolder;\r\n}\r\n\r\n.meta-item i {\r\n\tfont-style: normal;\r\n}\r\n\r\n.error {\r\n\tcolor: red;\r\n}\r\n\r\n.item {\r\n\tclear:both;\r\n\tpadding: 5px;\r\n\ttext-align: left;\r\n\tbackground-color: #fff;\r\n\toverflow: auto;\r\n}"
  },
  {
    "path": "order-cancellation/src/viewer/resource/static/js/app.js",
    "content": "window.onload = function () {\n\n    console.log(\"Protocol: \" + location.protocol);\n    var wsURL = \"ws://\" + document.location.host + \"/ws\"\n    if (location.protocol == 'https:') {\n        wsURL = \"wss://\" + document.location.host + \"/ws\"\n    }\n    console.log(\"WS URL: \" + wsURL);\n\n    var log = document.getElementById(\"items\");\n\n    function appendLog(item) {\n        var doScroll = log.scrollTop > log.scrollHeight - log.clientHeight - 1;\n        log.appendChild(item);\n        if (doScroll) {\n            log.scrollTop = log.scrollHeight - log.clientHeight;\n        }\n\n    }\n\n    if (log) {\n\n        sock = new WebSocket(wsURL);\n\n        var temp = document.getElementsByTagName(\"template\")[0];\n\n        var connDiv = document.getElementById(\"connection-status\");\n        connDiv.innerText = \"closed\";\n\n        sock.onopen = function () {\n            console.log(\"connected to \" + wsURL);\n            connDiv.innerText = \"open\";\n        };\n\n        sock.onclose = function (e) {\n            console.log(\"connection closed (\" + e.code + \")\");\n            connDiv.innerText = \"closed\";\n        };\n\n        sock.onmessage = function (e) {\n            console.log(e);\n            var order = JSON.parse(e.data);\n            console.log(order);\n\n            var item = temp.content.cloneNode(true);\n            var divs = item.querySelectorAll(\"div\");\n\n            divs[1].querySelector(\"a\").textContent = order.id;\n            divs[2].querySelector(\"a\").textContent = order.submitted_on;\n            divs[3].textContent = order.note;\n\n            appendLog(item);\n        };\n    } // if log\n};"
  },
  {
    "path": "order-cancellation/src/viewer/resource/template/footer.html",
    "content": "{{ define \"footer\" }}\n<!-- Footer -->\n<div id=\"page-footer\">\n    <p>\n        <a href=\"https://github.com/mchmarny/dapr-demos/order-cancellation\">\n            order-cancellation\n        </a>\n    </p>\n    <p>\n        {{ .version }}\n    </p>\n</div>\n\n</div>\n\n</body>\n\n</html>\n{{ end }}"
  },
  {
    "path": "order-cancellation/src/viewer/resource/template/header.html",
    "content": "{{ define \"header\" }}\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <title>Dapr Event Viewer</title>\n    <meta charset=\"UTF-8\">\n    <meta name=\"description\" content=\"dapr event viewer\">\n    <meta name=\"keywords\" content=\"events, dapr, websocket, golang\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <link href=\"static/img/favicon.ico\" rel=\"shortcut icon\" />\n    <link rel=\"stylesheet\" href=\"static/css/app.css\" />\n    <script type=\"text/javascript\" src=\"static/js/app.js\"></script>\n</head>\n\n<body>\n\n    <div id=\"wrapper\">\n\n        <!-- Header -->\n        <div id=\"page-header\">\n            <div id=\"page-header-image\">\n                <img src=\"static/img/dapr.svg\" />\n            </div>\n            <div id=\"connection\">connection: <b id=\"connection-status\"></b></div>\n        </div>\n\n        {{end}}"
  },
  {
    "path": "order-cancellation/src/viewer/resource/template/index.html",
    "content": "{{ define \"index\" }}\n\n{{ template \"header\" . }}\n\n<div id=\"middle-section\">\n    <div id=\"items\"></div>\n</div>\n\n<template>\n    <div class=\"item\">\n        <div class=\"meta-item\">\n            <b>Order ID:</b>\n            <a href=\"#noop\"></a>\n        </div>\n        <div class=\"meta-item\">\n            <b>Cancelled On:</b>\n            <a href=\"#noop\"></a>\n        </div>\n        <div class=\"meta-item\"></div>\n    </div>\n</template>\n\n{{ template \"footer\" . }}\n\n{{ end }}"
  },
  {
    "path": "pipeline/README.md",
    "content": "# Dapr pipeline demo \n\nDapr supports a wide array of state and pubsub building blocks across multiple OSS, Cloud, and on-prem services. This demo shows how to use a few of these components to build tweet sentiment processing pipeline.\n\n![alt text](./img/overview.png \"Pipeline Overview\")\n\n\n## Run in standalone mode\n\n### Setup \n\nTo run these demos locally, you will have first create a secret file (`pipeline/secrets.json`). These will be used by Dapr components at runtime. To get the Twitter API secretes you will need to register your app [here](https://developer.twitter.com/en/apps/create).\n\n```json\n{\n    \"Twitter\": {\n        \"ConsumerKey\": \"\",\n        \"ConsumerSecret\": \"\",\n        \"AccessToken\": \"\",\n        \"AccessSecret\": \"\"\n    },\n    \"Azure\": {\n        \"CognitiveAPIKey\": \"\"\n    }\n}\n```\n\n\n### Start tweet viewer app \n\nNavigate to the [tweet-viewer](./tweet-viewer) directory and run:\n\n```shell\ncd tweet-viewer\ndapr run \\\n    --app-id tweet-viewer \\\n    --app-port 8084 \\\n    --app-protocol http \\\n    --components-path ./config \\\n    go run main.go\n```\n\nOnce the app starts, you should be able to navigate to http://localhost:8084/. There won't be anything there yet, but if you see `connection: open` in the top right corner that means the WebSocket connection to the back-end is established. \n\n\n### Start sentiment scoring service \n\nNavigate to the [sentiment-scorer](./sentiment-scorer) directory and run:\n\n```shell\ncd sentiment-scorer\ndapr run \\\n    --app-id sentiment-scorer \\\n    --app-port 60005 \\\n    --app-protocol grpc \\\n    --components-path ./config \\\n    go run main.go\n```\n\nThe last line from the above command should be\n\n```shell\n✅  You're up and running! Both Dapr and your app logs will appear here.\n```\n\n### Start tweet processing service \n\nNavigate to the [tweet-processor](./tweet-processor) directory and run:\n\n```shell\ncd tweet-processor\ndapr run \\\n    --app-id tweet-processor \\\n    --app-port 60002 \\\n    --app-protocol grpc \\\n    --components-path ./config \\\n    go run main.go\n```\n\nThe last line from the above command should be\n\n```shell\n✅  You're up and running! Both Dapr and your app logs will appear here.\n```\n\n\n### Start tweet provider\n\nNavigate to the [tweet-provider](./tweet-provider) directory and run:\n\n```shell\ncd tweet-provider\ndapr run \\\n\t\t--app-id tweet-provider \\\n    --app-port 8080 \\\n    --app-protocol http \\\n    --components-path ./config \\\n    go run main.go\n```\n\nThe last line from the above command should be\n\n```shell\n✅  You're up and running! Both Dapr and your app logs will appear here.\n```\n\n### View sentiment scored tweets in the UI \n\nNavigate once more to http://localhost:8084/ and provided there were tweets matching your query you should now see tweets displayed in the UI. \n\n![](./img/ui.png)\n\n\n## Run on Kubernetes \n\n> Note, these instructions assume cluster created using the [demo setup](../setup).\n\n### namespace \n\nStart by setting up the namespace for the `pipeline`:\n\n```shell\nkubectl apply -f k8s/space.yaml\n```\n\n### tweet-processor\n\nNext, deploy `tweet-processor` and wait for it to be ready\n\n```shell\nkubectl apply -f k8s/process-pubsub.yaml\nkubectl apply -f k8s/tweeter-pubsub.yaml\nkubectl apply -f k8s/processor.yaml\nkubectl rollout status deployment/tweet-processor -n pipeline\n```\n\nCheck the Dapr logs to make sure the components were registered correctly \n\n```shell\nkubectl logs -l app=tweet-processor -c daprd -n pipeline --tail 200\n```\n\n### sentiment-scorer\n\nCreate a secret for Azure Cognitive Services\n\n```shell\nkubectl create secret generic sentiment-secret \\\n    -n pipeline \\\n    --from-literal=token=\"your-azure-cognitive-service-token\"\n```\n\nDeploy `sentiment-scorer` and wait for it to be ready \n\n```shell\nkubectl apply -f k8s/scorer.yaml\nkubectl rollout status deployment/sentiment-scorer -n pipeline\n```\n\nCheck the logs to make sure Dapr was started correctly \n\n```shell\nkubectl logs -l app=sentiment-scorer -c daprd -n pipeline --tail 200\n```\n\nTo test the service, you can first export the API token\n\n```shell\nexport API_TOKEN=$(kubectl get secret dapr-api-token -o jsonpath=\"{.data.token}\" -n nginx | base64 --decode)\n```\n\nAnd then invoke the service manually\n\n```shell\ncurl -i -d '{ \"text\": \"dapr is the best\" }' \\\n     -H \"Content-type: application/json\" \\\n     -H \"dapr-api-token: ${API_TOKEN}\" \\\n     \"https://api.demo.dapr.team/v1.0/invoke/sentiment-scorer.pipeline/method/sentiment\"\n```\n\nResponse should look something like this \n\n```json \n{ \"sentiment\":\"positive\", \"confidence\":1 }\n```\n\n\n### tweet-viewer\n\nCreate the TLS certs for this domain \n\n> `demo.dapr.team` is the domain I'm using for this demo\n\n```shell\nkubectl create secret tls tls-secret \\\n    -n pipeline \\\n    --key ../setup/certs/demo.dapr.team/cert-pk.pem \\\n    --cert ../setup/certs/demo.dapr.team/cert-ca.pem\n```\n\nDeploy `tweet-viewer` along with its component\n\n```shell\nkubectl apply -f k8s/viewer.yaml\nkubectl apply -f k8s/ingress.yaml\nkubectl rollout status deployment/tweet-viewer -n pipeline\n```\n\nCheck that the ingress was updated \n\n```shell\nkubectl get ingress -n pipeline\n```\n\nShould include `viewer.`\n\n```shell\nNAME            HOSTS                   ADDRESS   PORTS     AGE\ningress-rules   tweets.demo.dapr.team   x.x.x.x   80, 443   1m\n```\n\nCheck in browser: https://tweets.demo.dapr.team\n\n### tweet-provider\n\nCreate secret for `tweet-provider` to connect to Twitter API \n\n> Check [twitter developer portal](https://developer.twitter.com/en/portal/dashboard) if you need this info\n\n```shell\nkubectl create secret generic twitter-secret -n pipeline \\\n  --from-literal=consumerKey=\"\" \\\n  --from-literal=consumerSecret=\"\" \\\n  --from-literal=accessToken=\"\" \\\n  --from-literal=accessSecret=\"\"\n```\n\nDeploy the `tweet-provider` service and its components\n\n```shell\nkubectl apply -f k8s/state.yaml\nkubectl apply -f k8s/twitter.yaml\nkubectl apply -f k8s/provider.yaml\nkubectl rollout status deployment/tweet-provider -n pipeline\n```\n\nCheck Dapr to make sure components were registered correctly \n\n```shell\nkubectl logs -l app=tweet-provider -c daprd -n pipeline --tail 200\n```\n\n## View\n\nNavigate back to: https://tweets.demo.dapr.team\n\nIf everything went well, you should see some tweets appear. \n\n> Note, this demo shows only tweets meeting your query posted since the viewer was started. If you chosen an unpopular search term you may have to be patient\n\n## Restart deployments \n\nIf you change the components you need to apply rolling upgrades to the deployments \n\n```shell\nkubectl rollout restart deployment/sentiment-scorer -n pipeline\nkubectl rollout restart deployment/tweet-processor -n pipeline\nkubectl rollout restart deployment/tweet-provider -n pipeline\nkubectl rollout restart deployment/tweet-viewer -n pipeline\nkubectl rollout status deployment/sentiment-scorer -n pipeline\nkubectl rollout status deployment/tweet-processor -n pipeline\nkubectl rollout status deployment/tweet-provider -n pipeline\nkubectl rollout status deployment/tweet-viewer -n pipeline\n```\n\n## Debug\n\nStart by reviewing that all the pods are running:\n\n```shell\nkubectl get pods -n pipeline\n```\n\nThe response should look something like this. Notice the `2/2` container readiness in each pod and the status `Running`\n\n```shell\nNAME                                READY   STATUS    RESTARTS   AGE\nsentiment-scorer-64b9b8fb48-czn5h   2/2     Running   0          10m\ntweet-processor-75cff98984-skmx8    2/2     Running   0          10m\ntweet-provider-b47b566b5-7vs9r      2/2     Running   0          10m\ntweet-viewer-5cf465d4d8-crfk2       2/2     Running   0          10m\n```\n\nNext check the `daprd` and `service` logs on each deployment to make sure all the components loaded correctly. The `tweet-provider` for example would be: \n\n```shell\nkubectl logs -l app=tweet-provider -c daprd -n pipeline --tail 300\nkubectl logs -l app=tweet-provider -c service -n pipeline --tail 300\n```\n\n## Disclaimer\n\nThis is my personal project and it does not represent my employer. While I do my best to ensure that everything works, I take no responsibility for issues caused by this code.\n\n## License\n\nThis software is released under the [MIT](../LICENSE)\n"
  },
  {
    "path": "pipeline/k8s/ingress.yaml",
    "content": "apiVersion: extensions/v1beta1\nkind: Ingress\nmetadata:\n  name: tweets-ingress-rules\n  namespace: pipeline\n  annotations:\n    kubernetes.io/ingress.class: nginx\n    nginx.ingress.kubernetes.io/rewrite-target: /\nspec:\n  tls:\n    - hosts:\n      - tweets.demo.dapr.team\n      secretName: tls-secret\n  rules:\n    - host: tweets.demo.dapr.team\n      http:\n        paths:\n          - path: /\n            backend:\n              serviceName: tweet-viewer\n              servicePort: 80"
  },
  {
    "path": "pipeline/k8s/process-pubsub.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: processed-tweets-pubsub\n  namespace: pipeline\nspec:\n  type: pubsub.redis\n  metadata:\n  - name: redisHost\n    value: redis-master.redis.svc.cluster.local:6379\n  - name: redisPassword\n    secretKeyRef:\n      name: redis-secret\n      key: password\n  - name: allowedTopics\n    value: \"processed-tweets\"\nscopes:\n- tweet-processor\n- tweet-viewer"
  },
  {
    "path": "pipeline/k8s/processor.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: tweet-processor\n  namespace: pipeline\n  labels:\n    app: tweet-processor\n    demo: pipeline\nspec:\n  selector:\n    matchLabels:\n      app: tweet-processor\n  template:\n    metadata:\n      labels:\n        app: tweet-processor\n        demo: pipeline\n      annotations:\n        dapr.io/enabled: \"true\"\n        dapr.io/app-id: \"tweet-processor\"\n        dapr.io/app-protocol: \"grpc\"\n        dapr.io/app-port: \"60002\"\n        dapr.io/config: \"space-config\"\n        dapr.io/log-as-json: \"true\"\n        dapr.io/log-level: \"debug\"\n    spec:\n      containers:\n      - name: service\n        image: mchmarny/tweet-processor:v0.11.1\n        ports:\n        - containerPort: 60002\n        env:\n        - name: SOURCE_PUBSUB_NAME\n          value: \"tweeter-pubsub\"\n        - name: SOURCE_TOPIC_NAME\n          value: \"tweets\"\n        - name: RESULT_PUBSUB_NAME\n          value: \"processed-tweets-pubsub\"\n        - name: RESULT_TOPIC_NAME\n          value: \"processed-tweets\"\n        - name: SENTIMENT_SERVICE_NAME\n          value: \"sentiment-scorer\""
  },
  {
    "path": "pipeline/k8s/provider.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: tweet-provider\n  namespace: pipeline\n  labels:\n    app: tweet-provider\n    demo: pipeline\nspec:\n  selector:\n    matchLabels:\n      app: tweet-provider\n  template:\n    metadata:\n      labels:\n        app: tweet-provider\n        demo: pipeline\n      annotations:\n        dapr.io/enabled: \"true\"\n        dapr.io/app-id: \"tweet-provider\"\n        dapr.io/app-port: \"8080\"\n        dapr.io/config: \"space-config\" \n        dapr.io/log-as-json: \"true\"\n        dapr.io/log-level: \"debug\"\n    spec:\n      containers:\n      - name: service\n        image: mchmarny/tweet-provider:v0.11.1\n        ports:\n        - containerPort: 8080\n        env:\n        - name: ADDRESS\n          value: \":8080\"\n        - name: PUBSUB_NAME\n          value: \"tweeter-pubsub\"\n        - name: TOPIC_NAME\n          value: \"tweets\""
  },
  {
    "path": "pipeline/k8s/scorer.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: sentiment-scorer\n  namespace: pipeline\n  labels:\n    app: sentiment-scorer\n    demo: pipeline\nspec:\n  selector:\n    matchLabels:\n      app: sentiment-scorer\n  template:\n    metadata:\n      labels:\n        app: sentiment-scorer\n        demo: pipeline\n      annotations:\n        dapr.io/enabled: \"true\"\n        dapr.io/app-id: \"sentiment-scorer\"\n        dapr.io/app-protocol: \"grpc\"\n        dapr.io/app-port: \"60002\"\n        dapr.io/config: \"space-config\"\n        dapr.io/log-as-json: \"true\"\n        dapr.io/log-level: \"debug\"\n    spec:\n      containers:\n      - name: service\n        image: mchmarny/sentiment-scorer:v0.11.1\n        ports:\n        - containerPort: 60002\n        env:\n        - name: ADDRESS\n          value: \":60002\"\n        - name: API_DOMAIN\n          value: \"tweet-sentiment\"\n        - name: API_TOKEN\n          valueFrom:\n            secretKeyRef:\n              name: sentiment-secret\n              key: token\n"
  },
  {
    "path": "pipeline/k8s/space.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  name: pipeline\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: secret-reader\n  namespace: pipeline\nrules:\n- apiGroups: [\"\"]\n  resources: [\"secrets\"]\n  verbs: [\"get\"]\n---\nkind: RoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: dapr-secret-reader\n  namespace: pipeline\nsubjects:\n- kind: ServiceAccount\n  name: default\nroleRef:\n  kind: Role\n  name: secret-reader\n  apiGroup: rbac.authorization.k8s.io\n---\napiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: zipkin\n  namespace: pipeline\nspec:\n  type: exporters.zipkin\n  metadata:\n  - name: enabled\n    value: \"true\"\n  - name: exporterAddress\n    value: \"http://zipkin.dapr-monitoring.svc.cluster.local:9411/api/v2/spans\"\n---\napiVersion: dapr.io/v1alpha1\nkind: Configuration\nmetadata:\n  name: space-config\n  namespace: pipeline\nspec:\n  tracing:\n    samplingRate: \"1\""
  },
  {
    "path": "pipeline/k8s/state.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: tweet-store\n  namespace: pipeline\nspec:\n  type: state.mongodb\n  metadata:\n  - name: host\n    value: mongo-mongodb-headless.mongo.svc.cluster.local:27017\n  - name: username\n    value: dapr\n  - name: password\n    secretKeyRef:\n      name: mongo-secret\n      key: password\n  - name: databaseName\n    value: dapr\n  - name: collectionName\n    value: audit\nscopes:\n- tweet-provider"
  },
  {
    "path": "pipeline/k8s/tweeter-pubsub.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: tweeter-pubsub\n  namespace: pipeline\nspec:\n  type: pubsub.redis\n  metadata:\n  - name: redisHost\n    value: redis-master.redis.svc.cluster.local:6379\n  - name: redisPassword\n    secretKeyRef:\n      name: redis-secret\n      key: password\n  - name: allowedTopics\n    value: \"tweets\"\nscopes:\n- tweet-provider\n- tweet-processor"
  },
  {
    "path": "pipeline/k8s/twitter.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: tweets\n  namespace: pipeline\nspec:\n  type: bindings.twitter\n  metadata:\n  - name: consumerKey\n    secretKeyRef:\n      name: twitter-secret\n      key: consumerKey\n  - name: consumerSecret\n    secretKeyRef:\n      name: twitter-secret\n      key: consumerSecret\n  - name: accessToken\n    secretKeyRef:\n      name: twitter-secret\n      key: accessToken\n  - name: accessSecret\n    secretKeyRef:\n      name: twitter-secret\n      key: accessSecret\n  - name: query\n    value: \"dapr\" # use more common term for demo\nscopes:\n- tweet-provider"
  },
  {
    "path": "pipeline/k8s/viewer.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: tweet-viewer\n  namespace: pipeline\n  labels:\n    app: tweet-viewer\n    demo: pipeline\nspec:\n  selector:\n    matchLabels:\n      app: tweet-viewer\n  template:\n    metadata:\n      labels:\n        app: tweet-viewer\n        demo: pipeline\n      annotations:\n        dapr.io/enabled: \"true\"\n        dapr.io/app-id: \"tweet-viewer\"\n        dapr.io/app-protocol: \"http\"\n        dapr.io/app-port: \"8084\"\n        dapr.io/config: \"space-config\"\n        dapr.io/log-as-json: \"true\"\n        dapr.io/log-level: \"debug\"\n    spec:\n      containers:\n      - name: service\n        image: mchmarny/tweet-viewer:v0.11.1\n        ports:\n        - containerPort: 8084\n        env:\n        - name: ADDRESS\n          value: \":8084\"\n        - name: PUBSUB_NAME\n          value: \"processed-tweets-pubsub\"\n        - name: TOPIC_NAME\n          value: \"processed-tweets\"\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: tweet-viewer\n  namespace: pipeline\nspec:\n  selector:\n    app: tweet-viewer\n  ports:\n    - protocol: TCP\n      port: 80\n      targetPort: 8084"
  },
  {
    "path": "pipeline/sentiment-scorer/Dockerfile",
    "content": "FROM golang:1.15.0 as builder\n\nWORKDIR /src/\nCOPY . /src/\n\nENV GO111MODULE=on\n\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \\\n    go build -a -tags netgo -mod vendor -o ./service .\n\nFROM gcr.io/distroless/static:nonroot\nCOPY --from=builder /src/service .\n\nENTRYPOINT [\"./service\"]\n"
  },
  {
    "path": "pipeline/sentiment-scorer/Makefile",
    "content": "RELEASE_VERSION  =v0.11.1\nSERVICE_NAME    ?=sentiment-scorer\nDOCKER_USERNAME ?=$(DOCKER_USER)\n\n.PHONY: tidy debug invoke image deploy call lint clean tag\nall: help\n\ntidy: ## Updates the go modules and vendors all dependencies \n\tgo mod tidy\n\tgo mod vendor\n\nrun: tidy ## Runs uncompiled code in Dapr\n\tdapr run \\\n\t\t--app-id $(SERVICE_NAME) \\\n\t\t--app-port 60005 \\\n\t\t--app-protocol grpc \\\n\t\t--dapr-http-port 3505 \\\n        --components-path ./config \\\n\t\t--log-level debug \\\n        go run main.go\n\ninvoke: ## Invokes service through Dapr API \n\tcurl -i -d '{ \"text\": \"dapr is the best\" }' \\\n     -H \"Content-type: application/json\" \\\n     \"http://localhost:3505/v1.0/invoke/$(SERVICE_NAME)/method/sentiment\"\n\tcurl -i -d '{ \"text\": \"spinach is the worst\" }' \\\n     -H \"Content-type: application/json\" \\\n     \"http://localhost:3505/v1.0/invoke/$(SERVICE_NAME)/method/sentiment\"\n\tcurl -i -d '{ \"text\": \"happy alram make me sad\" }' \\\n     -H \"Content-type: application/json\" \\\n     \"http://localhost:3505/v1.0/invoke/$(SERVICE_NAME)/method/sentiment\"\n\nimage: tidy ## Builds and publish docker image \n\tdocker build -t \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\" .\n\tdocker push \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\"\n\ndeploy: ## Deploys prebuild image to k8s using currently selected context \n\tkubectl apply -f deployment.yaml\n\tkubectl rollout restart deployment/sentiment-scorer\n\tkubectl rollout restart deployment/nginx-ingress-nginx-controller\n\tkubectl rollout status deployment/nginx-ingress-nginx-controller\n\napi-invoke: ## Invokes service through Dapr API \n\t$(eval API_TOKEN=$(shell kubectl get secret dapr-api-token -o jsonpath=\"{.data.token}\" | base64 --decode))\n\tcurl -d '{ \"text\": \"dapr is the best\" }' \\\n     -H \"Content-type: application/json\" \\\n     -H \"dapr-api-token: $(API_TOKEN)\" \\\n     \"https://api.cloudylabs.dev/v1.0/invoke/$(SERVICE_NAME)/method/sentiment\"\n\nlint: ## Lints the entire project \n\tgolangci-lint run --timeout=3m\n\ntag: ## Creates release tag \n\tgit tag $(RELEASE_VERSION)\n\tgit push origin $(RELEASE_VERSION)\n\nclean: ## Cleans up generated files \n\tgo clean\n\trm -fr ./bin\n\trm -fr ./vendor\n\nhelp: ## Display available commands\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \\\n\t\t'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n"
  },
  {
    "path": "pipeline/sentiment-scorer/README.md",
    "content": "# grpc-service\n\nFor more information about service invocation see the [Dapr docs](https://github.com/dapr/docs/tree/master/concepts/service-invocation)\n\n## Run \n\nTo run this demo in Dapr, run:\n\n```shell\nAPI_TOKEN=\"your-azure-cognitive-service-token\" dapr run \\\n    --app-id sentiment-scorer \\\n    --app-port 60001 \\\n    --app-protocol grpc \\\n    --dapr-http-port 3500 \\\n    --components-path ./config \\\n    go run main.go\n```\n\n## Deploy\n\nCreate a `sentiment-secret`\n\n```shell\nkubectl create secret generic sentiment-secret --from-literal=token=\"your-azure-cognitive-service-token\"\n```\n\nDeploy and wait for the pod to be ready \n\n```shell\nkubectl apply -f deployment.yaml\nkubectl rollout restart deployment/sentiment-scorer\nkubectl rollout restart deployment/nginx-ingress-nginx-controller\nkubectl rollout status deployment/nginx-ingress-nginx-controller\n```\n\nFollow logs\n\n```shell\nkubectl logs -l demo=sentiment -c service -f\n```\n\nIn a separate terminal session export API token\n\n```shell\nexport API_TOKEN=$(kubectl get secret dapr-api-token -o jsonpath=\"{.data.token}\" | base64 --decode)\n```\n\nAnd invoke the service\n\n```shell\ncurl -d '{ \"text\": \"dapr is the best\" }' \\\n    -H \"Content-type: application/json\" \\\n    -H \"dapr-api-token: ${API_TOKEN}\" \\\n    \"https://api.cloudylabs.dev/v1.0/invoke/sentiment-scorer/method/sentiment\"\n```\n\nResponse should look something like this \n\n```json \n{ \"sentiment\":\"positive\", \"confidence\":1 }\n```\n\n## Disclaimer\n\nThis is my personal project and it does not represent my employer. While I do my best to ensure that everything works, I take no responsibility for issues caused by this code.\n\n## License\n\nThis software is released under the [MIT](./LICENSE)\n"
  },
  {
    "path": "pipeline/sentiment-scorer/config/secret.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: pipeline-secrets\nspec:\n  type: secretstores.local.file\n  metadata:\n  - name: secretsFile\n    value: \"/etc/dapr/secrets.json\""
  },
  {
    "path": "pipeline/sentiment-scorer/go.mod",
    "content": "module github.com/mchmarny/dapr-demos/pipeline/sentiment-scorer\n\ngo 1.15\n\nrequire (\n\tgithub.com/dapr/go-sdk v0.11.0\n\tgithub.com/pkg/errors v0.9.1\n\tgolang.org/x/net v0.0.0-20200930145003-4acb6c075d10 // indirect\n\tgolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f // indirect\n\tgoogle.golang.org/genproto v0.0.0-20201002142447-3860012362da // indirect\n)\n"
  },
  {
    "path": "pipeline/sentiment-scorer/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo=\ngithub.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=\ngolang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200930145003-4acb6c075d10 h1:YfxMZzv3PjGonQYNUaeU2+DhAdqOxerQ30JFB6WgAXo=\ngolang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 h1:4X356008q5SA3YXu8PiRap39KFmy4Lf6sGlceJKZQsU=\ngolang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0 h1:uslsjIdqvZYANxSBQjTI47vZfwMaTN3mLELkMnMIY/A=\ngoogle.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201002142447-3860012362da h1:DTQYk4u7nICKkkVZsBv0/0po0ChISxAJ5CTAfUhO0PQ=\ngoogle.golang.org/genproto v0.0.0-20201002142447-3860012362da/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=\ngoogle.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "pipeline/sentiment-scorer/main.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\tdapr \"github.com/dapr/go-sdk/client\"\n\t\"github.com/dapr/go-sdk/service/common\"\n\tdaprd \"github.com/dapr/go-sdk/service/grpc\"\n\t\"github.com/pkg/errors\"\n)\n\nconst (\n\tlanguageDefault = \"en\"\n\tsecretStoreName = \"pipeline-secrets\"\n\tsecretStoreKey  = \"Azure:CognitiveAPIKey\"\n)\n\nvar (\n\tlogger = log.New(os.Stdout, \"\", 0)\n\n\tserviceAddress = getEnvVar(\"ADDRESS\", \":60005\")\n\tapiToken       = getEnvVar(\"API_TOKEN\", \"\")\n\tapiDomain      = getEnvVar(\"API_DOMAIN\", \"tweet-sentiment\")\n\n\tapiURL = fmt.Sprintf(\"https://%s.cognitiveservices.azure.com/text/analytics/v3.0/sentiment\", apiDomain)\n)\n\nfunc main() {\n\n\t// create serving server\n\ts, err := daprd.NewService(serviceAddress)\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to start the server: %v\", err)\n\t}\n\n\t// add handler to the service\n\ts.AddServiceInvocationHandler(\"sentiment\", sentimentHandler)\n\n\t// start the server to handle incoming events\n\tlog.Printf(\"starting server at %s...\", serviceAddress)\n\tif err := s.Start(); err != nil {\n\t\tlog.Fatalf(\"server error: %v\", err)\n\t}\n}\n\nfunc sentimentHandler(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) {\n\tlogger.Printf(\"Processing: %s\", in.Data)\n\tvar req map[string]string\n\tif err := json.Unmarshal(in.Data, &req); err != nil {\n\t\treturn nil, errors.Wrapf(err, \"error deserializing data: %s\", in.Data)\n\t}\n\n\tscore, err := getSentiment(ctx, req[\"language\"], req[\"text\"])\n\tif err != nil {\n\t\tlogger.Printf(\"error scoring sentiment: %v\", err)\n\t\treturn nil, errors.Wrapf(err, \"error scoring sentiment: %s\", in.Data)\n\t}\n\n\tb, err := json.Marshal(score)\n\tif err != nil {\n\t\treturn nil, errors.Wrapf(err, \"error serializing score: %v\", score)\n\t}\n\n\tlogger.Printf(\"Processed: %s\", b)\n\treturn &common.Content{\n\t\tContentType: \"application/json\",\n\t\tData:        b,\n\t}, nil\n}\n\n// SentimentScore represents sentiment result\ntype SentimentScore struct {\n\tSentiment  string  `json:\"sentiment\"`\n\tConfidence float64 `json:\"confidence\"`\n}\n\nfunc getSentiment(ctx context.Context, lang, text string) (out *SentimentScore, err error) {\n\tif text == \"\" {\n\t\treturn nil, errors.New(\"text required\")\n\t}\n\n\tif lang == \"\" {\n\t\tlang = languageDefault\n\t}\n\n\tif apiToken == \"\" {\n\t\tapiToken = getSecret(secretStoreName, secretStoreKey)\n\t}\n\n\tr := fmt.Sprintf(`{\n        \"documents\": [{\n\t\t\t\"language\": \"%s\",\n            \"id\": \"1\",\n\t\t\t\"text\": \"%s\"\n          }]\n      }`, lang, text)\n\n\treq, err := http.NewRequest(http.MethodPost, apiURL, bytes.NewBuffer([]byte(r)))\n\tif err != nil {\n\t\treturn nil, errors.Wrapf(err, \"error creating request from: %v\", r)\n\t}\n\n\treq = req.WithContext(ctx)\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"Ocp-Apim-Subscription-Key\", apiToken)\n\n\tclient := http.Client{Timeout: time.Second * 5}\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, errors.Wrapf(err, \"error posting to: %s\", apiURL)\n\t}\n\n\tif res.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"invalid API response status: %d\", res.StatusCode)\n\t}\n\tdefer res.Body.Close()\n\n\t// dump, _ := httputil.DumpResponse(res, true)\n\t// logger.Printf(\"response: %s\", dump)\n\n\tvar rez struct {\n\t\tDocuments []struct {\n\t\t\tSentiment string `json:\"sentiment\"`\n\t\t\tScores    struct {\n\t\t\t\tPositive float64 `json:\"positive\"`\n\t\t\t\tNeutral  float64 `json:\"neutral\"`\n\t\t\t\tNegative float64 `json:\"negative\"`\n\t\t\t\tMixed    float64 `json:\"mixed\"`\n\t\t\t} `json:\"confidenceScores\"`\n\t\t} `json:\"documents\"`\n\t}\n\n\tif err := json.NewDecoder(res.Body).Decode(&rez); err != nil {\n\t\treturn nil, errors.Wrap(err, \"error decoding API response\")\n\t}\n\n\tif len(rez.Documents) != 1 {\n\t\treturn nil, errors.Wrapf(err, \"invalid response, expected 1 document, got %d\", len(rez.Documents))\n\t}\n\n\tdoc := rez.Documents[0]\n\tout = &SentimentScore{\n\t\tSentiment: doc.Sentiment,\n\t}\n\n\tswitch out.Sentiment {\n\tcase \"positive\":\n\t\tout.Confidence = rez.Documents[0].Scores.Positive\n\tcase \"negative\":\n\t\tout.Confidence = rez.Documents[0].Scores.Negative\n\tcase \"neutral\":\n\t\tout.Confidence = rez.Documents[0].Scores.Neutral\n\tcase \"mixed\":\n\t\tout.Confidence = rez.Documents[0].Scores.Mixed\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"invalid sentiment: %s\", out.Sentiment)\n\t}\n\n\treturn\n}\n\nfunc getEnvVar(key, fallbackValue string) string {\n\tif val, ok := os.LookupEnv(key); ok {\n\t\treturn strings.TrimSpace(val)\n\t}\n\treturn fallbackValue\n}\n\nfunc getSecret(store, key string) string {\n\t// try to find it in Dapr secret store\n\tc, err := dapr.NewClient()\n\tif err != nil {\n\t\tlogger.Fatal(\"unable to create Dapr client\")\n\t}\n\tif m, err := c.GetSecret(context.Background(), store, key, map[string]string{}); err == nil {\n\t\treturn m[key]\n\t}\n\tlogger.Fatalf(\"no item found in Dapr secret store for %s\", key)\n\treturn \"\"\n}\n"
  },
  {
    "path": "pipeline/tweet-processor/Dockerfile",
    "content": "FROM golang:1.15.0 as builder\n\nWORKDIR /src/\nCOPY . /src/\n\nENV GO111MODULE=on\n\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \\\n    go build -a -tags netgo -mod vendor -o ./service .\n\nFROM gcr.io/distroless/static:nonroot\nCOPY --from=builder /src/service .\n\nENTRYPOINT [\"./service\"]\n"
  },
  {
    "path": "pipeline/tweet-processor/Makefile",
    "content": "RELEASE_VERSION  =v0.11.1\nSERVICE_NAME    ?=tweet-processor\nDOCKER_USERNAME ?=$(DOCKER_USER)\n\n.PHONY: tidy test debug build run jsonevent xmlevent binevent image lint clean tag\nall: help\n\ntidy: ## Updates the go modules and vendors all dependencies \n\tgo mod tidy\n\tgo mod vendor\n\ndebug: tidy ## Runs uncompiled code in Dapr\n\tdapr run \\\n        --app-id $(SERVICE_NAME) \\\n        --app-port 60002 \\\n        --app-protocol grpc \\\n        --dapr-http-port 3500 \\\n        --components-path ./config \\\n\t\t--log-level debug \\\n        go run main.go\n\nimage: tidy ## Builds and publish docker image \n\tdocker build -t \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\" .\n\tdocker push \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\"\n\ndeploy: ## Deploys prebuild image to k8s using currently selected context \n\tkubectl apply -f k8s/source-pubsub.yaml\n\tkubectl apply -f k8s/result-pubsub.yaml\n\tkubectl apply -f k8s/deployment.yaml\n\tkubectl rollout restart deployment/tweet-processor\n\tkubectl rollout status deployment/tweet-processor\n\nevent: ## Publishes sample JSON message to Dapr pubsub API \n\t$(eval API_TOKEN=$(shell kubectl get secret dapr-api-token -o jsonpath=\"{.data.token}\" | base64 --decode))\n\tcurl -d '{ \"from\": \"John\", \"to\": \"Lary\", \"message\": \"hi\" }' \\\n     -H \"Content-type: application/json\" \\\n     -H \"dapr-api-token: $(API_TOKEN)\" \\\n     \"https://api.cloudylabs.dev/v1.0/publish/grpc-events/messages\"\n\nlint: ## Lints the entire project \n\tgolangci-lint run --timeout=3m\n\ntag: ## Creates release tag \n\tgit tag $(RELEASE_VERSION)\n\tgit push origin $(RELEASE_VERSION)\n\nclean: ## Cleans up generated files \n\tgo clean\n\trm -fr ./bin\n\trm -fr ./vendor\n\nhelp: ## Display available commands\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \\\n\t\t'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n"
  },
  {
    "path": "pipeline/tweet-processor/config/result-pubsub.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: processed-tweets-pubsub\nspec:\n  type: pubsub.redis\n  metadata:\n  - name: redisHost\n    value: localhost:6379\n  - name: redisPassword\n    value: \"\""
  },
  {
    "path": "pipeline/tweet-processor/config/source-pubsub.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: tweeter-pubsub\nspec:\n  type: pubsub.redis\n  metadata:\n  - name: redisHost\n    value: localhost:6379\n  - name: redisPassword\n    value: \"\""
  },
  {
    "path": "pipeline/tweet-processor/go.mod",
    "content": "module github.com/mchmarny/dapr-demos/pipeline/tweet-processor\n\ngo 1.15\n\nrequire (\n\tgithub.com/dapr/go-sdk v0.11.0\n\tgithub.com/pkg/errors v0.9.1\n\tgolang.org/x/net v0.0.0-20200930145003-4acb6c075d10 // indirect\n\tgolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f // indirect\n\tgoogle.golang.org/genproto v0.0.0-20201002142447-3860012362da // indirect\n)\n"
  },
  {
    "path": "pipeline/tweet-processor/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo=\ngithub.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=\ngolang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200930145003-4acb6c075d10 h1:YfxMZzv3PjGonQYNUaeU2+DhAdqOxerQ30JFB6WgAXo=\ngolang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 h1:4X356008q5SA3YXu8PiRap39KFmy4Lf6sGlceJKZQsU=\ngolang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0 h1:uslsjIdqvZYANxSBQjTI47vZfwMaTN3mLELkMnMIY/A=\ngoogle.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201002142447-3860012362da h1:DTQYk4u7nICKkkVZsBv0/0po0ChISxAJ5CTAfUhO0PQ=\ngoogle.golang.org/genproto v0.0.0-20201002142447-3860012362da/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=\ngoogle.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "pipeline/tweet-processor/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\n\tdapr \"github.com/dapr/go-sdk/client\"\n\t\"github.com/dapr/go-sdk/service/common\"\n\tdaprd \"github.com/dapr/go-sdk/service/grpc\"\n\t\"github.com/pkg/errors\"\n)\n\nvar (\n\tlogger         = log.New(os.Stdout, \"\", 0)\n\tserviceAddress = getEnvVar(\"ADDRESS\", \":60002\")\n\n\tsrcPubSubName = getEnvVar(\"SOURCE_PUBSUB_NAME\", \"tweeter-pubsub\")\n\tsrcTopicName  = getEnvVar(\"SOURCE_TOPIC_NAME\", \"tweets\")\n\n\tresultPubSubName = getEnvVar(\"RESULT_PUBSUB_NAME\", \"processed-tweets-pubsub\")\n\tresultTopicName  = getEnvVar(\"RESULT_TOPIC_NAME\", \"processed-tweets\")\n\n\tsentimentServiceName = getEnvVar(\"SENTIMENT_SERVICE_NAME\", \"sentiment-scorer\")\n\n\tclient dapr.Client\n)\n\nfunc main() {\n\t// create Dapr service\n\ts, err := daprd.NewService(serviceAddress)\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to start the server: %v\", err)\n\t}\n\n\tc, err := dapr.NewClient()\n\tif err != nil {\n\t\tlog.Fatalf(\"error creating Dapr client: %v\", err)\n\t}\n\tclient = c\n\tdefer client.Close()\n\n\t// add handler to the service\n\tsubscription := &common.Subscription{\n\t\tPubsubName: srcPubSubName,\n\t\tTopic:      srcTopicName,\n\t}\n\ts.AddTopicEventHandler(subscription, tweetHandler)\n\n\t// start the server to handle incoming events\n\tif err := s.Start(); err != nil {\n\t\tlog.Fatalf(\"server error: %v\", err)\n\t}\n}\n\nfunc topicDataToSentimentRequest(b []byte) (s *SentimentRequest, err error) {\n\tvar t TweetText\n\tif err := json.Unmarshal(b, &t); err != nil {\n\t\treturn nil, errors.Wrapf(err, \"error deserializing tweet into request: %s\", b)\n\t}\n\n\ts = &SentimentRequest{\n\t\tText:     t.Text,\n\t\tLanguage: t.Lang,\n\t}\n\n\tif t.Extended.Text != \"\" {\n\t\ts.Text = t.Extended.Text\n\t}\n\n\treturn\n}\n\nfunc getSentimentScore(ctx context.Context, req *SentimentRequest) (score *SentimentScore, err error) {\n\tcb, err := json.Marshal(req)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"unable to serialize sentiment request\")\n\t}\n\n\tc := &dapr.DataContent{ContentType: \"application/json\", Data: cb}\n\n\tb, err := client.InvokeServiceWithContent(ctx, sentimentServiceName, \"sentiment\", c)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"error invoking sentiment service\")\n\t}\n\n\tvar s SentimentScore\n\tif err := json.Unmarshal(b, &s); err != nil {\n\t\treturn nil, errors.Wrap(err, \"error deserializing sentiment\")\n\t}\n\n\treturn &s, nil\n}\n\nfunc tweetHandler(ctx context.Context, e *common.TopicEvent) (retry bool, err error) {\n\tlogger.Printf(\"Processing pubsub:%s/topic:%s id:%s\", e.PubsubName, e.Topic, e.ID)\n\n\tb, ok := e.Data.([]byte)\n\tif !ok {\n\t\treturn false, fmt.Errorf(\"invalid data type, expected []bytes: %T\", e.Data)\n\t}\n\n\tsentReq, err := topicDataToSentimentRequest(b)\n\tif err != nil {\n\t\treturn false, errors.Wrap(err, \"error getting tweet text\")\n\t}\n\n\tsentScore, err := getSentimentScore(ctx, sentReq)\n\tif err != nil {\n\t\treturn true, errors.Wrap(err, \"error getting sentiment score\")\n\t}\n\n\tvar tweetMap map[string]interface{}\n\tif err := json.Unmarshal(b, &tweetMap); err != nil {\n\t\treturn true, errors.Wrap(err, \"error deserializing content into map\")\n\t}\n\n\ttweetMap[\"sentiment\"] = sentScore\n\tcontent, err := json.Marshal(tweetMap)\n\tif err != nil {\n\t\treturn true, errors.Wrap(err, \"unable to serialize tweet map content\")\n\t}\n\n\tif err := client.PublishEvent(ctx, resultPubSubName, resultTopicName, content); err != nil {\n\t\treturn true, errors.Wrapf(err, \"error publishing to %s/%s\", resultPubSubName, resultTopicName)\n\t}\n\n\tlogger.Printf(\"Processed tweet:%s - %v\", e.ID, sentScore)\n\treturn false, nil\n}\n\n// TweetText represents only the text of tweet for sentiment\ntype TweetText struct {\n\tText     string `json:\"text\"`\n\tLang     string `json:\"lang\"`\n\tExtended struct {\n\t\tText string `json:\"full_text\"`\n\t} `json:\"extended_tweet\"`\n}\n\n// SentimentRequest represents the sentiment request\ntype SentimentRequest struct {\n\tText     string `json:\"text\"`\n\tLanguage string `json:\"language\"`\n}\n\n// SentimentScore represents sentiment result\ntype SentimentScore struct {\n\tSentiment  string  `json:\"sentiment\"`\n\tConfidence float64 `json:\"confidence\"`\n}\n\nfunc getEnvVar(key, fallbackValue string) string {\n\tif val, ok := os.LookupEnv(key); ok {\n\t\treturn strings.TrimSpace(val)\n\t}\n\treturn fallbackValue\n}\n"
  },
  {
    "path": "pipeline/tweet-provider/Dockerfile",
    "content": "FROM golang:1.15.0 as builder\n\nWORKDIR /src/\nCOPY . /src/\n\nENV GO111MODULE=on\n\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \\\n    go build -a -tags netgo -mod vendor -o ./service .\n\nFROM gcr.io/distroless/static:nonroot\nCOPY --from=builder /src/service .\n\nENTRYPOINT [\"./service\"]\n"
  },
  {
    "path": "pipeline/tweet-provider/Makefile",
    "content": "RELEASE_VERSION  =v0.11.1\nSERVICE_NAME    ?=tweet-provider\nDOCKER_USERNAME ?=$(DOCKER_USER)\n\n.PHONY: help tidy build run image lint tag clean\nall: help\n\ntidy: ## Updates the go modules and vendors all dependencies \n\tgo mod tidy\n\tgo mod vendor\n\nrun: ## Runs uncompiled code it in Dapr in debug mode\n\tdapr run \\\n\t\t--app-id $(SERVICE_NAME) \\\n        --app-port 8080 \\\n        --app-protocol http \\\n        --components-path ./config \\\n        go run main.go\n\nimage: tidy ## Builds and publish docker image \n\tdocker build -t \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\" .\n\tdocker push \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\"\n\ndeploy: ## Deploys prebuild image to k8s using currently selected context \n\tkubectl apply -f k8s/pubsub.yaml\n\tkubectl apply -f k8s/twitter.yaml\n\tkubectl apply -f k8s/deployment.yaml\n\tkubectl rollout restart deployment/tweet-provider\n\tkubectl rollout status deployment/tweet-provider\n\nlint: ## Lints the entire project \n\tgolangci-lint run --timeout=3m\n\ntag: ## Creates release tag \n\tgit tag $(RELEASE_VERSION)\n\tgit push origin $(RELEASE_VERSION)\n\nclean: ## Cleans up generated files \n\tgo clean\n\trm -fr ./bin\n\trm -fr ./vendor\n\nhelp: ## Display available commands\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \\\n\t\t'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n"
  },
  {
    "path": "pipeline/tweet-provider/README.md",
    "content": "# Tweet Provider Demo \n\n## Setup \n\nTo run these demos locally, you will have first create a secret file (`pipeline/secrets.json`). These will be used by Dapr components at runtime. To get the Twitter API secretes you will need to register your app [here](https://developer.twitter.com/en/apps/create).\n\n```json\n{\n    \"Twitter\": {\n        \"ConsumerKey\": \"\",\n        \"ConsumerSecret\": \"\",\n        \"AccessToken\": \"\",\n        \"AccessSecret\": \"\"\n    }\n}\n```\n\n## Run it\n\n### Standalone Mode\n\nNavigate to the [tweet-provider](./tweet-provider) directory and run:\n\n```shell\ncd tweet-provider\ndapr run \\\n    --app-id tweet-provider \\\n    --app-port 8080 \\\n    --app-protocol http \\\n    --components-path ./config \\\n    go run main.go\n```\n\nThe last line from the above command should be\n\n```shell\n✅  You're up and running! Both Dapr and your app logs will appear here.\n```\n\nYour tweets should appear in the logs now\n\n\n### Kubernetes \n\n\nCreate secret for `tweet-provider` to connect to Twitter API \n\n```shell\nkubectl create secret generic twitter-secret \\\n  --from-literal=consumerKey=\"\" \\\n  --from-literal=consumerSecret=\"\" \\\n  --from-literal=accessToken=\"\" \\\n  --from-literal=accessSecret=\"\"\n```\n\nDeploy the `tweet-provider` service and its components\n\n```shell\nkubectl apply -f tweet-provider/k8s/state.yaml\nkubectl apply -f tweet-provider/k8s/pubsub.yaml\nkubectl apply -f tweet-provider/k8s/twitter.yaml\nkubectl apply -f tweet-provider/k8s/deployment.yaml\nkubectl rollout status deployment/tweet-provider\n```\n\nIf you have changed an existing component, make sure to reload the deployment and wait until the new version is ready\n\n```shell\nkubectl rollout restart deployment/tweet-provider\nkubectl rollout status deployment/tweet-provider\n```\n\nCheck Dapr to make sure components were registered correctly \n\n```shell\nkubectl logs -l app=tweet-provider -c daprd --tail 200\n```\n\n## Disclaimer\n\nThis is my personal project and it does not represent my employer. While I do my best to ensure that everything works, I take no responsibility for issues caused by this code.\n\n## License\n\nThis software is released under the [MIT](../LICENSE)\n"
  },
  {
    "path": "pipeline/tweet-provider/config/pubsub.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: tweeter-pubsub\nspec:\n  type: pubsub.redis\n  metadata:\n  - name: redisHost\n    value: localhost:6379\n  - name: redisPassword\n    value: \"\""
  },
  {
    "path": "pipeline/tweet-provider/config/secret.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: pipeline-secrets\nspec:\n  type: secretstores.local.file\n  metadata:\n  - name: secretsFile\n    value: \"/etc/dapr/secrets.json\""
  },
  {
    "path": "pipeline/tweet-provider/config/state.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: tweet-store\nspec:\n  type: state.redis\n  metadata:\n  - name: redisHost\n    value: localhost:6379\n  - name: redisPassword\n    value: \"\""
  },
  {
    "path": "pipeline/tweet-provider/config/twitter.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: tweets\nspec:\n  type: bindings.twitter\n  metadata:\n  - name: consumerKey\n    secretKeyRef:\n      name: Twitter:ConsumerKey\n  - name: consumerSecret\n    secretKeyRef:\n      name: Twitter:ConsumerSecret\n  - name: accessToken\n    secretKeyRef:\n      name: Twitter:AccessToken\n  - name: accessSecret\n    secretKeyRef:\n      name: Twitter:AccessSecret\n  - name: query\n    value: \"football\" # common term for demo\nauth:\n    secretStore: pipeline-secrets"
  },
  {
    "path": "pipeline/tweet-provider/go.mod",
    "content": "module github.com/mchmarny/dapr-demos/pipeline/tweet-provider\n\ngo 1.15\n\nrequire (\n\tgithub.com/dapr/go-sdk v0.11.0\n\tgithub.com/pkg/errors v0.9.1\n\tgolang.org/x/net v0.0.0-20200930145003-4acb6c075d10 // indirect\n\tgolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f // indirect\n\tgoogle.golang.org/genproto v0.0.0-20201002142447-3860012362da // indirect\n)\n"
  },
  {
    "path": "pipeline/tweet-provider/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo=\ngithub.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=\ngolang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200930145003-4acb6c075d10 h1:YfxMZzv3PjGonQYNUaeU2+DhAdqOxerQ30JFB6WgAXo=\ngolang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 h1:4X356008q5SA3YXu8PiRap39KFmy4Lf6sGlceJKZQsU=\ngolang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0 h1:uslsjIdqvZYANxSBQjTI47vZfwMaTN3mLELkMnMIY/A=\ngoogle.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201002142447-3860012362da h1:DTQYk4u7nICKkkVZsBv0/0po0ChISxAJ5CTAfUhO0PQ=\ngoogle.golang.org/genproto v0.0.0-20201002142447-3860012362da/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=\ngoogle.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "pipeline/tweet-provider/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log\"\n\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\n\tdapr \"github.com/dapr/go-sdk/client\"\n\t\"github.com/dapr/go-sdk/service/common\"\n\tdaprd \"github.com/dapr/go-sdk/service/http\"\n\t\"github.com/pkg/errors\"\n)\n\nvar (\n\tlogger     = log.New(os.Stdout, \"\", 0)\n\taddress    = getEnvVar(\"ADDRESS\", \":8080\")\n\tpubSubName = getEnvVar(\"PUBSUB_NAME\", \"tweeter-pubsub\")\n\ttopicName  = getEnvVar(\"TOPIC_NAME\", \"tweets\")\n\tstoreName  = getEnvVar(\"STORE_NAME\", \"tweet-store\")\n\tclient     dapr.Client\n)\n\nfunc main() {\n\t// create a Dapr service\n\ts := daprd.NewService(address)\n\n\t// create a Dapr client\n\tc, err := dapr.NewClient()\n\tif err != nil {\n\t\tlogger.Fatalf(\"error creating Dapr client: %v\", err)\n\t}\n\tclient = c\n\tdefer client.Close()\n\n\t// add twitter input binding handler\n\tif err := s.AddBindingInvocationHandler(\"tweets\", tweetHandler); err != nil {\n\t\tlogger.Fatalf(\"error adding binding handler: %v\", err)\n\t}\n\n\t// start the service\n\tif err := s.Start(); err != nil && err != http.ErrServerClosed {\n\t\tlogger.Fatalf(\"error starting service: %v\", err)\n\t}\n}\n\nfunc tweetHandler(ctx context.Context, in *common.BindingEvent) (out []byte, err error) {\n\tlogger.Printf(\"Tweet (query: %s, traceID: %s)\", in.Metadata[\"Query\"], in.Metadata[\"Traceparent\"])\n\tvar m map[string]interface{}\n\tif err := json.Unmarshal(in.Data, &m); err != nil {\n\t\treturn nil, errors.Wrap(err, \"error deserializing event data\")\n\t}\n\n\tk := fmt.Sprintf(\"tw-%s\", m[\"id_str\"])\n\tif err := client.SaveState(ctx, storeName, k, in.Data); err != nil {\n\t\treturn nil, errors.Wrapf(err, \"error saving to store:%s with key:%s\", storeName, k)\n\t}\n\n\tlogger.Printf(\"Tweet saved in store: %s: %s\", storeName, k)\n\tif err := client.PublishEvent(ctx, pubSubName, topicName, in.Data); err != nil {\n\t\treturn nil, errors.Wrapf(err, \"error publishing to %s/%s\", pubSubName, topicName)\n\t}\n\tlogger.Printf(\"Tweet published to %s/%s\", pubSubName, topicName)\n\treturn\n}\n\nfunc getEnvVar(key, fallbackValue string) string {\n\tif val, ok := os.LookupEnv(key); ok {\n\t\treturn strings.TrimSpace(val)\n\t}\n\treturn fallbackValue\n}\n"
  },
  {
    "path": "pipeline/tweet-viewer/Dockerfile",
    "content": "FROM golang:1.15.0 as builder\n\nWORKDIR /src/\nCOPY . /src/\n\nARG APP_VERSION=v0.0.1-default\n\nENV APP_VERSION=$APP_VERSION\nENV GO111MODULE=on\n\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \\\n    go build -a -tags netgo -ldflags \\\n    \"-w -extldflags '-static' -X main.AppVersion=${APP_VERSION}\" \\\n    -mod vendor -o ./service .\n\nFROM gcr.io/distroless/static:nonroot\nCOPY --from=builder /src/service .\nCOPY --from=builder /src/resource ./resource/\n\nENTRYPOINT [\"./service\"]\n"
  },
  {
    "path": "pipeline/tweet-viewer/Makefile",
    "content": "RELEASE_VERSION  =v0.11.1\nSERVICE_NAME    ?=tweet-viewer\nDOCKER_USERNAME ?=$(DOCKER_USER)\n\n.PHONY: tidy test debug build run jsonevent xmlevent binevent image lint clean tag\nall: help\n\ntidy: ## Updates the go modules and vendors all dependencies \n\tgo mod tidy\n\tgo mod vendor\n\ntest: tidy ## Tests the entire project \n\tgo test -count=1 -race ./...\n\ndebug: tidy ## Runs uncompiled code in Dapr\n\tdapr run \\\n        --app-id $(SERVICE_NAME) \\\n        --app-port 8084 \\\n        --app-protocol http \\\n        --dapr-http-port 3500 \\\n        --components-path ./config \\\n\t\t--log-level debug \\\n        go run main.go\n\npost: ## Posts sample tweet \n\tcurl -v -d @tweet.json \\\n\t\t -H \"Content-type: application/json\" \\\n\t\t http://localhost:3500/v1.0/publish/processed-tweets-pubsub/processed-tweets\n\nimage: tidy ## Builds and publish docker image \n\tdocker build --build-arg APP_VERSION=$(RELEASE_VERSION) -t \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\" .\n\tdocker push \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\"\n\ndeploy: ## Deploys prebuild image to k8s using currently selected context \n\tkubectl apply -f k8s/source-pubsub.yaml\n\tkubectl apply -f k8s/deployment.yaml\n\tkubectl rollout restart deployment/tweet-viewer\n\tkubectl rollout status deployment/tweet-viewer\n\napi-patch: ## Patch API gatewaty to add route map\n\tkubectl patch ingress ingress-rules --type json -p \"$(cat k8s/ingress.json)\"\n\n\nlint: ## Lints the entire project \n\tgolangci-lint run --timeout=3m\n\ntag: ## Creates release tag \n\tgit tag $(RELEASE_VERSION)\n\tgit push origin $(RELEASE_VERSION)\n\nclean: ## Cleans up generated files \n\tgo clean\n\trm -fr ./bin\n\trm -fr ./vendor\n\nhelp: ## Display available commands\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \\\n\t\t'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n"
  },
  {
    "path": "pipeline/tweet-viewer/config/source-pubsub.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: processed-tweets-pubsub\nspec:\n  type: pubsub.redis\n  metadata:\n  - name: redisHost\n    value: localhost:6379\n  - name: redisPassword\n    value: \"\""
  },
  {
    "path": "pipeline/tweet-viewer/go.mod",
    "content": "module github.com/mchmarny/dapr-demos/pipeline/tweet-viewer\n\ngo 1.15\n\nrequire (\n\tgithub.com/dapr/go-sdk v0.11.0\n\tgithub.com/gorilla/websocket v1.4.2 // indirect\n\tgithub.com/pkg/errors v0.9.1\n\tgopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376\n)\n"
  },
  {
    "path": "pipeline/tweet-viewer/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo=\ngithub.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=\ngithub.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376 h1:sY2a+y0j4iDrajJcorb+a0hJIQ6uakU5gybjfLWHlXo=\ngopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376/go.mod h1:BHKOc1m5wm8WwQkMqYBoo4vNxhmF7xg8+xhG8L+Cy3M=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "pipeline/tweet-viewer/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"html/template\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\n\t\"gopkg.in/olahol/melody.v1\"\n\n\t\"github.com/dapr/go-sdk/service/common\"\n\tdaprd \"github.com/dapr/go-sdk/service/http\"\n\t\"github.com/pkg/errors\"\n)\n\nvar (\n\t// AppVersion will be overritten during build\n\tAppVersion = \"v0.0.1-default\"\n\n\t// service\n\tlogger     = log.New(os.Stdout, \"\", 0)\n\taddress    = getEnvVar(\"ADDRESS\", \":8084\")\n\tpubSubName = getEnvVar(\"PUBSUB_NAME\", \"processed-tweets-pubsub\")\n\ttopicName  = getEnvVar(\"TOPIC_NAME\", \"processed-tweets\")\n\n\tbroadcaster *melody.Melody\n\ttemplates   *template.Template\n)\n\nfunc main() {\n\n\t// server mux\n\tmux := http.NewServeMux()\n\n\t// static content\n\tmux.Handle(\"/static/\", http.StripPrefix(\"/static/\", http.FileServer(http.Dir(\"resource/static\"))))\n\tmux.HandleFunc(\"/favicon.ico\", faviconHandler)\n\n\t// tempalates\n\ttemplates = template.Must(template.ParseGlob(\"resource/template/*\"))\n\n\t// websocket upgrade\n\tbroadcaster = melody.New()\n\tbroadcaster.Upgrader.CheckOrigin = func(r *http.Request) bool { return true }\n\n\t// other handlers\n\tmux.HandleFunc(\"/\", rootHandler)\n\tmux.HandleFunc(\"/ws\", wsHandler)\n\n\t// create a Dapr service\n\ts := daprd.NewServiceWithMux(address, mux)\n\n\t// add some topic subscriptions\n\tsubscription := &common.Subscription{\n\t\tPubsubName: pubSubName,\n\t\tTopic:      topicName,\n\t\tRoute:      fmt.Sprintf(\"/%s\", topicName),\n\t}\n\n\tif err := s.AddTopicEventHandler(subscription, eventHandler); err != nil {\n\t\tlogger.Fatalf(\"error adding topic subscription: %v\", err)\n\t}\n\n\t// start the service\n\tif err := s.Start(); err != nil && err != http.ErrServerClosed {\n\t\tlogger.Fatalf(\"error starting service: %v\", err)\n\t}\n}\n\nfunc wsHandler(w http.ResponseWriter, r *http.Request) {\n\tbroadcaster.HandleRequest(w, r)\n}\n\nfunc rootHandler(w http.ResponseWriter, r *http.Request) {\n\tproto := r.Header.Get(\"x-forwarded-proto\")\n\tif proto == \"\" {\n\t\tproto = \"http\"\n\t}\n\n\tdata := map[string]string{\n\t\t\"host\":    r.Host,\n\t\t\"proto\":   proto,\n\t\t\"version\": AppVersion,\n\t}\n\n\terr := templates.ExecuteTemplate(w, \"index\", data)\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n}\n\nfunc faviconHandler(w http.ResponseWriter, r *http.Request) {\n\thttp.ServeFile(w, r, \"./resource/static/img/favicon.ico\")\n}\n\nfunc eventHandler(ctx context.Context, e *common.TopicEvent) (retry bool, err error) {\n\tb, err := json.Marshal(e.Data)\n\tif err != nil {\n\t\treturn false, errors.Wrap(err, \"error marshaling data\")\n\t}\n\tbroadcaster.Broadcast(b)\n\treturn false, nil\n}\n\nfunc getEnvVar(key, fallbackValue string) string {\n\tif val, ok := os.LookupEnv(key); ok {\n\t\treturn strings.TrimSpace(val)\n\t}\n\treturn fallbackValue\n}\n"
  },
  {
    "path": "pipeline/tweet-viewer/resource/static/css/app.css",
    "content": "/*\r\n\tKnative colors\r\n\tDark Blue: #0865ad\r\n\tLight Blue: #6695ca\r\n\r\n*/\r\n\r\n\r\nhtml,\r\nbody {\r\n\theight: 100%;\r\n\tmargin: 0;\r\n\tpadding: 0;\r\n\tbackground-color: #fff;\r\n}\r\n\r\n#wrapper {\r\n\tpadding: 0;\r\n\tmargin: 0;\r\n\theight: 100%;\r\n\ttext-align: center;\r\n\twidows: 100%;\r\n}\r\n\r\n#page-header {\r\n\tpadding: 0;\r\n\tmargin: 10px 0 0 0;\r\n\tclear: both;\r\n\tbreak-after: always;\r\n\theight: 100px;\r\n}\r\n\r\n#page-header-image {\r\n\tborder: 0;\r\n\tmargin-left: 25px;\r\n}\r\n\r\n#page-header-image img {\r\n\twidth: 100px;\r\n\theight: 100px;\r\n\tfloat: left;\r\n}\r\n\r\n#connection {\r\n\tfloat: right;\r\n\tmargin: 10px;\r\n\tfont-family: Geneva, Verdana, sans-serif;\r\n\tfont-size: 1em;\r\n}\r\n\r\n#middle-section {\r\n\tmargin: 0;\r\n\tpadding: 10px;\r\n\theight: 700px;\r\n\toverflow: auto;\r\n}\r\n\r\n#middle-section div {\r\n\tmargin: 5px;\r\n}\r\n\r\n#page-footer {\r\n\twidows: 80%;\r\n\tmargin: 30px 0 0 0;\r\n\tfont-family: Geneva, Verdana, sans-serif;\r\n\tfont-size: 1em;\r\n}\r\n\r\n#tweets {\r\n\tbackground-color: #20329b;\r\n\tpadding: 0;\r\n\tmargin: 5px;\r\n\theight: 690px;\r\n\toverflow: auto;\r\n}\r\n\r\nimg.profile-pic {\r\n\twidth: 72px;\r\n\theight: 72px;\r\n\tmargin: 0 5px 5px 0;\r\n\tfloat: left;\r\n}\r\n\r\ndiv.item-text {\r\n\tmargin-left: 10px;\r\n\tpadding-left: 10px;\r\n}\r\n\r\n#tweets b {\r\n\tfont-family: Geneva, Verdana, sans-serif;\r\n\tfont-size: 1em;\r\n\tcolor: #20329b;\r\n\tmargin: 0;\r\n}\r\n\r\n#tweets i {\r\n\tfont-family: Geneva, Verdana, sans-serif;\r\n\tfont-size: 0.8em;\r\n\tfont-style: normal;\r\n}\r\n\r\n#tweets i.small {\r\n\tfont-size: 0.7em;\r\n\tcolor: #666666;\r\n}\r\n\r\nimg.sentiment {\r\n\twidth: 18px;\r\n\theight: 18px;\r\n\tmargin: 0 3px 0 0;\r\n\tvertical-align: baseline;\r\n}\r\n\r\nimg.tweet-link {\r\n\twidth: 25px;\r\n\theight: 25px;\r\n\tmargin: 0 0 0 3px;\r\n\tvertical-align: baseline;\r\n}\r\n\r\n.error {\r\n\tcolor: red;\r\n}\r\n\r\n.item {\r\n\tclear:both;\r\n\tpadding: 5px;\r\n\ttext-align: left;\r\n\tbackground-color: #fff;\r\n\toverflow: auto;\r\n}"
  },
  {
    "path": "pipeline/tweet-viewer/resource/static/js/app.js",
    "content": "window.onload = function () {\n\n    console.log(\"Protocol: \" + location.protocol);\n    var wsURL = \"ws://\" + document.location.host + \"/ws\"\n    if (location.protocol == 'https:') {\n        wsURL = \"wss://\" + document.location.host + \"/ws\"\n    }\n    console.log(\"WS URL: \" + wsURL);\n\n    var log = document.getElementById(\"tweets\");\n\n    function appendLog(item) {\n        var doScroll = log.scrollTop > log.scrollHeight - log.clientHeight - 1;\n        log.appendChild(item);\n        if (doScroll) {\n            log.scrollTop = log.scrollHeight - log.clientHeight;\n        }\n    }\n\n    if (log) {\n\n        sock = new WebSocket(wsURL);\n\n        var connDiv = document.getElementById(\"connection-status\");\n        connDiv.innerText = \"closed\";\n\n        sock.onopen = function () {\n            console.log(\"connected to \" + wsURL);\n            connDiv.innerText = \"open\";\n        };\n\n        sock.onclose = function (e) {\n            console.log(\"connection closed (\" + e.code + \")\");\n            connDiv.innerText = \"closed\";\n        };\n\n        sock.onmessage = function (e) {\n            console.log(e);\n            var t = JSON.parse(e.data);\n            console.log(t);\n\n            \n            var scoreStr = \"neutral\";\n            var scoreAlt = \"neutral: 0\"\n            if (t.hasOwnProperty(\"sentiment\")) { \n                console.log(t.sentiment);\n                if (t.sentiment.sentiment.length > 0) {\n                    scoreStr = t.sentiment.sentiment;\n                    scoreAlt = scoreStr + \": \" + t.sentiment.confidence;\n                }\n            } \n\n            var tweetText = t.text;\n            if(t.extended_tweet != null) {\n                tweetText = t.extended_tweet.full_text;\n            }\n\n            var item = document.createElement(\"div\");\n            item.className = \"item\";\n            // TODO: template this\n            var tmsg = \"<img src='\" + t.user.profile_image_url_https + \"' class='profile-pic' />\" +\n                \"<div class='item-text'><b><img src='static/img/\" + scoreStr +\n                \".svg' title='\" + scoreAlt + \"' class='sentiment' />\" + t.user.screen_name +\n                \"<a href='https://twitter.com/\" + t.user.screen_name + \"/status/\" + t.id_str +\n                \"' target='_blank'><img src='static/img/tw.svg' class='tweet-link' /></a></b>\" +\n                \"<br /><i>\" + tweetText + \"</i></div>\";\n            item.innerHTML = tmsg\n            appendLog(item);\n        };\n\n    } // if log\n\n\n};"
  },
  {
    "path": "pipeline/tweet-viewer/resource/template/footer.html",
    "content": "{{ define \"footer\" }}\n<!-- Footer -->\n<div id=\"page-footer\">\n    <p>\n        <a href=\"https://github.com/mchmarny/dapr-demos\">\n            dapr-demos\n        </a>\n    </p>\n    <p>\n        {{ .version }}\n    </p>\n</div>\n\n</div>\n\n</body>\n\n</html>\n{{ end }}"
  },
  {
    "path": "pipeline/tweet-viewer/resource/template/header.html",
    "content": "{{ define \"header\" }}\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <title>dapr event viewer</title>\n    <meta charset=\"UTF-8\">\n    <meta name=\"description\" content=\"dapr event viewer\">\n    <meta name=\"keywords\" content=\"events, dapr, websocket, golang\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <link href=\"static/img/favicon.ico\" rel=\"shortcut icon\" />\n    <link rel=\"stylesheet\" href=\"static/css/app.css\" />\n    <script type=\"text/javascript\" src=\"static/js/app.js\"></script>\n</head>\n\n<body>\n\n    <div id=\"wrapper\">\n\n        <!-- Header -->\n        <div id=\"page-header\">\n            <div id=\"page-header-image\">\n                <img src=\"static/img/dapr.svg\" />\n            </div>\n            <div id=\"connection\">connection: <b id=\"connection-status\"></b></div>\n        </div>\n\n        {{end}}"
  },
  {
    "path": "pipeline/tweet-viewer/resource/template/index.html",
    "content": "{{ define \"index\" }}\n\n{{ template \"header\" . }}\n\n\n<div id=\"middle-section\">\n    <div id=\"tweets\"></div>\n</div>\n\n{{ template \"footer\" . }}\n\n{{ end }}"
  },
  {
    "path": "pipeline/tweet-viewer/tweet.json",
    "content": "{\n  \"created_at\": \"Sun Feb 25 18:11:01 +0000 2018\",\n  \"id\": 967824267948773377,\n  \"id_str\": \"967824267948773377\",\n  \"text\": \"From pilot to astronaut, Robert H. Lawrence was the first African-American to be selected as an astronaut by any na… https://t.co/FjPEWnh804\",\n  \"truncated\": false,\n  \"entities\": {\n      \"hashtags\": [],\n      \"symbols\": [],\n      \"user_mentions\": [],\n      \"urls\": [\n          {\n              \"url\": \"https://t.co/FjPEWnh804\",\n              \"expanded_url\": \"https://twitter.com/i/web/status/967824267948773377\",\n              \"display_url\": \"twitter.com/i/web/status/9…\",\n              \"indices\": [\n                  117,\n                  140\n              ]\n          }\n      ]\n  },\n  \"metadata\": {\n      \"result_type\": \"popular\",\n      \"iso_language_code\": \"en\"\n  },\n  \"user\": {\n      \"id\": 11348282,\n      \"id_str\": \"11348282\",\n      \"name\": \"NASA\",\n      \"screen_name\": \"NASA\",\n      \"location\": \"\",\n      \"description\": \"Explore the universe and discover our home planet with @NASA. We usually post in EST (UTC-5)\",\n      \"url\": \"https://t.co/TcEE6NS8nD\",\n      \"entities\": {\n          \"url\": {\n              \"urls\": [\n                  {\n                      \"url\": \"https://t.co/TcEE6NS8nD\",\n                      \"expanded_url\": \"http://www.nasa.gov\",\n                      \"display_url\": \"nasa.gov\",\n                      \"indices\": [\n                          0,\n                          23\n                      ]\n                  }\n              ]\n          },\n          \"description\": {\n              \"urls\": []\n          }\n      },\n      \"protected\": false,\n      \"followers_count\": 28605561,\n      \"friends_count\": 270,\n      \"listed_count\": 90405,\n      \"created_at\": \"Wed Dec 19 20:20:32 +0000 2007\",\n      \"favourites_count\": 2960,\n      \"utc_offset\": -18000,\n      \"time_zone\": \"Eastern Time (US & Canada)\",\n      \"geo_enabled\": false,\n      \"verified\": true,\n      \"statuses_count\": 50713,\n      \"lang\": \"en\",\n      \"contributors_enabled\": false,\n      \"is_translator\": false,\n      \"is_translation_enabled\": false,\n      \"profile_background_color\": \"000000\",\n      \"profile_background_image_url\": \"http://pbs.twimg.com/profile_background_images/590922434682880000/3byPYvqe.jpg\",\n      \"profile_background_image_url_https\": \"https://pbs.twimg.com/profile_background_images/590922434682880000/3byPYvqe.jpg\",\n      \"profile_background_tile\": false,\n      \"profile_image_url\": \"http://pbs.twimg.com/profile_images/188302352/nasalogo_twitter_normal.jpg\",\n      \"profile_image_url_https\": \"https://pbs.twimg.com/profile_images/188302352/nasalogo_twitter_normal.jpg\",\n      \"profile_banner_url\": \"https://pbs.twimg.com/profile_banners/11348282/1518798395\",\n      \"profile_link_color\": \"205BA7\",\n      \"profile_sidebar_border_color\": \"000000\",\n      \"profile_sidebar_fill_color\": \"F3F2F2\",\n      \"profile_text_color\": \"000000\",\n      \"profile_use_background_image\": true,\n      \"has_extended_profile\": true,\n      \"default_profile\": false,\n      \"default_profile_image\": false,\n      \"following\": null,\n      \"follow_request_sent\": null,\n      \"notifications\": null,\n      \"translator_type\": \"regular\"\n  },\n  \"geo\": null,\n  \"coordinates\": null,\n  \"place\": null,\n  \"contributors\": null,\n  \"is_quote_status\": false,\n  \"retweet_count\": 988,\n  \"favorite_count\": 3875,\n  \"favorited\": false,\n  \"retweeted\": false,\n  \"possibly_sensitive\": false,\n  \"lang\": \"en\",\n  \"sentiment\": {\n    \"sentiment\": \"positive\",\n    \"confidence\": 0.98\n  }\n}\n\n\n\n  \n"
  },
  {
    "path": "setup/Makefile",
    "content": "DOMAIN           ?=example.com\nCLUSTER_NAME     ?=demo\nDAPR_RELEASE     ?=1.0.0-rc.1\nDAPR_HA          ?=true\nDAPR_LOG_AS_JSON ?=true\n\n.PHONY: all\nall:\n\t@echo === ACTIVE CONFIGURATION ===\n\t@echo \"DOMAIN:           ${DOMAIN}\"\n\t@echo \"CLUSTER_NAME:     ${CLUSTER_NAME}\"\n\t@echo \"DAPR_RELEASE:     ${DAPR_RELEASE}\"\n\t@echo \"DAPR_HA:          ${DAPR_HA}\"\n\t@echo \"DAPR_LOG_AS_JSON: ${DAPR_LOG_AS_JSON}\"\n\t@echo \n\t@echo \"Export these as environment variables to change their values\"\n\t@echo \n\n.PHONY: node-list\nnode-list: ## Print node resources and their usage\n\tkubectl top nodes\n\n.PHONY: certs\ncerts: ## Create wildcard TLS certificates using letsencrypt\n\tmkdir -p certs/$(DOMAIN)\n\tsudo certbot certonly --manual --preferred-challenges dns -d \"*.$(DOMAIN)\"\n\tsudo cp \"/etc/letsencrypt/live/$(DOMAIN)/fullchain.pem\" certs/$(DOMAIN)/cert-ca.pem\t\n\tsudo cp \"/etc/letsencrypt/live/$(DOMAIN)/privkey.pem\" certs/$(DOMAIN)/cert-pk.pem\n\tsudo chmod 644 certs/$(DOMAIN)/*.pem\n\n.PHONY: dapr\ndapr: dapr-install keda-install observe-install ## Install dapr, keda, and observability \n\n.PHONY: dapr-install\ndapr-install: ## Install and configures Dapr\n\t# Updating Help repos...\n\thelm repo add dapr https://dapr.github.io/helm-charts/\n\thelm repo update\n\t# Installing Dapr...\n\tkubectl create ns dapr-system\n\thelm install dapr dapr/dapr -n dapr-system \\\n\t\t--version $(DAPR_RELEASE) \\\n\t\t--set global.logAsJson=$(DAPR_LOG_AS_JSON) \\\n\t\t--set global.ha.enabled=$(DAPR_HA)\n\t# Wait for everything to finish installing \n\tkubectl rollout status deployment/dapr-operator -n dapr-system\n\tkubectl rollout status deployment/dapr-dashboard -n dapr-system\n\tkubectl rollout status deployment/dapr-sentry -n dapr-system\n\tkubectl rollout status deployment/dapr-sidecar-injector -n dapr-system\n\n.PHONY: keda-install\nkeda-install: ## Install and configures Keda\n\t# Updating Help repos...\n\thelm repo add kedacore https://kedacore.github.io/charts\n\thelm repo update\n\t# Installing Keda\n\tkubectl create ns keda\n\thelm install keda kedacore/keda -n keda --set logLevel=debug\n\t# Wait for everything to finish installing \n\tkubectl rollout status deployment/keda-operator -n keda\n\tkubectl rollout status deployment/keda-operator-metrics-apiserver -n keda\n\n.PHONY: observe-install\nobserve-install: ## Install observability stack\n\t# Updating Help repos...\n\thelm repo add stable https://charts.helm.sh/stable\n\thelm repo add elastic https://helm.elastic.co\n\thelm repo update\n\t# Installing observabiliity...\n\tkubectl create ns dapr-monitoring\n\tkubectl apply -f config/fluentd-config.yaml -f config/fluentd.yaml\n\tkubectl apply -f config/zipkin.yaml -n dapr-monitoring\n\thelm install elasticsearch elastic/elasticsearch -n dapr-monitoring\n\thelm install dapr-prom stable/prometheus -n dapr-monitoring\n\thelm install grafana stable/grafana -n dapr-monitoring \\\n\t\t--set persistence.enabled=true \\\n\t\t--set persistence.accessModes={ReadWriteOnce} \\\n\t\t--set persistence.size=8Gi\n\thelm install kibana elastic/kibana -n dapr-monitoring\n\t# Wait for everything to be ready...\n\tkubectl rollout status deployment/dapr-prom-kube-state-metrics -n dapr-monitoring\n\tkubectl rollout status deployment/dapr-prom-prometheus-alertmanager -n dapr-monitoring\n\tkubectl rollout status deployment/dapr-prom-prometheus-pushgateway -n dapr-monitoring\n\tkubectl rollout status deployment/dapr-prom-prometheus-server -n dapr-monitoring\n\tkubectl rollout status deployment/grafana -n dapr-monitoring\n\tkubectl rollout status deployment/kibana-kibana -n dapr-monitoring\n\t\n.PHONY: config\nconfig: ports ## Configure Dapr after install\n\t$(eval GRAFANA_PASS=$(shell kubectl get secret -n dapr-monitoring grafana -o jsonpath=\"{.data.admin-password}\" | base64 --decode))\n\t# Check that everything is ready...\n\tkubectl rollout status deployment/dapr-prom-kube-state-metrics -n dapr-monitoring\n\tkubectl rollout status deployment/dapr-prom-prometheus-alertmanager -n dapr-monitoring\n\tkubectl rollout status deployment/dapr-prom-prometheus-pushgateway -n dapr-monitoring\n\tkubectl rollout status deployment/dapr-prom-prometheus-server -n dapr-monitoring\n\tkubectl rollout status deployment/grafana -n dapr-monitoring\n\tkubectl rollout status deployment/kibana-kibana -n dapr-monitoring\n\t# Configure grafana\n\tcurl -X POST -s -k -u \"admin:$(GRAFANA_PASS)\" \\\n\t\t -H \"Content-Type: application/json\" \\\n\t\t -d '{ \"name\":\"Dapr\", \"type\":\"prometheus\", \"url\":\"http://dapr-prom-prometheus-server.dapr-monitoring\", \"access\":\"proxy\", \"basicAuth\":false }' \\\n\t     http://localhost:8888/api/datasources\n\tcurl -X POST -s -k -u \"admin:$(GRAFANA_PASS)\" \\\n\t\t -H \"Content-Type: application/json\" \\\n\t\t -d @config/system-services-dashboard.json \\\n\t     http://localhost:8888/api/dashboards/db\n\tcurl -X POST -s -k -u \"admin:$(GRAFANA_PASS)\" \\\n\t\t -H \"Content-Type: application/json\" \\\n\t\t -d @config/sidecar-dashboard.json \\\n\t     http://localhost:8888/api/dashboards/db\n\tcurl -X POST -s -k -u \"admin:$(GRAFANA_PASS)\" \\\n\t\t -H \"Content-Type: application/json\" \\\n\t\t -d @config/actor-dashboard.json \\\n\t     http://localhost:8888/api/dashboards/db\n\t# Configure kibana\n\tcurl -X POST -H \"kbn-xsrf: true\" \\\n\t\t -H \"Content-Type: application/json\" \\\n\t\t -d '{\"attributes\":{\"title\":\"dapr*\",\"timeFieldName\":\"@timestamp\"}}' \\\n\t\t \"http://localhost:5601/api/saved_objects/index-pattern/dapr\"\n\tcurl -X POST -H \"kbn-xsrf: true\" \\\n\t\t -H \"Content-Type: application/json\" \\\n\t\t -d '{\"value\":\"dapr\"}' \\\n\t\t \"http://localhost:5601/api/kibana/settings/defaultIndex\"\n\n.PHONY: ingress\ningress: ## Install and configures Ngnx ingress, configure SSL termination, Dapr API auth\n\t# Updating Help repos...\n\thelm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx\n\thelm repo update\n\t# Configure nginx namespace \n\tsed \"s/NSNAME/nginx/g\" config/namespace-template.yml > config/namespace.yml\n\tkubectl apply -f config/namespace.yml\n\t# Gen Dapr API token \n\t$(eval API_TOKEN=$(shell openssl rand -base64 32))\n\tkubectl create secret generic dapr-api-token --from-literal=token=\"$(API_TOKEN)\" -n nginx\n\t# Apply ingress config \n\tkubectl apply -f config/ingress-config.yaml -n nginx\n\t# Deploy nginx...\n\thelm install nginx ingress-nginx/ingress-nginx \\\n\t\t--set controller.replicaCount=2 \\\n\t\t--set controller.metrics.enabled=true \\\n\t\t-f config/ingress-annotations.yaml \\\n\t\t-n nginx\n\tkubectl rollout status deployment/nginx-ingress-nginx-controller -n nginx\n\t# Install cert secrets\n\tkubectl create secret tls tls-secret \\\n\t\t--key certs/$(DOMAIN)/cert-pk.pem \\\n\t\t--cert certs/$(DOMAIN)/cert-ca.pem \\\n\t\t-n nginx\n\tsed \"s/DOMAINNAME/${DOMAIN}/g\" config/ingress-template.yaml > config/ingress.yaml\n\t# Apply configured ingress \n\tkubectl apply -f config/ingress.yaml -n nginx\n\n.PHONY: dns\ndns: ## Check DNS resolution for cluster IP\n\tdig api.$(DOMAIN)\n\t$(eval LB_IP=$(shell kubectl get svc nginx-ingress-nginx-controller -n nginx -o jsonpath='{.status.loadBalancer.ingress[0].ip}'))\n\t@echo === DNS CHECK ===\n\t@echo \n\t@echo \"Ensure the A record for 'api.${DOMAIN}' in the ANSWER SECTION resolves to:\"\n\t@echo \n\t@echo \"  ${LB_IP}\"\n\t@echo \n\t@echo If not, update DNS with below entry and re-run this test before moving to the next step  \n\t@echo\n\t@echo \"  Hostname:   *\"\n\t@echo \"  IP address: ${LB_IP}\"\n\t@echo \"  TTL:        1m\"\n\t@echo\n\n.PHONY: test\ntest: ## Test deployment and execute Dapr API health checks\n\t$(eval API_TOKEN=$(shell kubectl get secret dapr-api-token -n nginx -o jsonpath=\"{.data.token}\" | base64 --decode))\n\tcurl -v \\\n\t\t -H \"Content-type: application/json\" \\\n\t\t -H \"dapr-api-token: $(API_TOKEN)\" \\\n\t\t \"https://api.$(DOMAIN)/v1.0/healthz\"\n\t@echo \n\t@echo === DNS CHECK ===\n\t@echo Ensure server certificate has:\n\t@echo \n\t@echo \"  subject: CN=*.${DOMAIN}\"\n\t@echo \"  subjectAltName: host 'api.${DOMAIN}' matched cert's '*.${DOMAIN}'\"\n\t@echo \"  SSL certificate verify ok\"\n\t@echo \n\t@echo And that the response status from Dapr health checks was '200'\n\t@echo \n\t@echo \"  HTTP/2 200\"\n\t@echo\n\n.PHONY: token\ntoken: ## Print Dapr API token \n\t$(eval API_TOKEN=$(shell kubectl get secret dapr-api-token -n nginx -o jsonpath=\"{.data.token}\" | base64 --decode))\n\t@echo\n\t@echo Dapr API token is: \n\t@echo\n\t@echo \"  ${API_TOKEN}\"\n\t@echo\n\n.PHONY: pass\npass: ## Print Grafana admin password\n\t$(eval GPASS=$(shell kubectl get secret -n dapr-monitoring grafana -o jsonpath=\"{.data.admin-password}\" | base64 --decode))\n\t@echo\n\t@echo Grafana admin password is: \n\t@echo \n\t@echo \"  ${GPASS}\"\n\t@echo \n\n.PHONY: ports\nports: portstop ## Forward observability ports\n\tkubectl port-forward svc/kibana-kibana 5601 -n dapr-monitoring &\n\tkubectl port-forward svc/grafana 8888:80 -n dapr-monitoring &\n\tkubectl port-forward svc/zipkin 9411 -n dapr-monitoring &\n\t@echo Ports forwarded:\n\t@echo\n\t@echo   kibana  - http://localhost:5601\n\t@echo   grafana - http://localhost:8888\n\t@echo   zipkin  - http://localhost:9411\n\t@echo\n\t@echo \"To stop forwarding run 'make portstop'\"\n\t@echo\n\n.PHONY: reload\nreload: ## Reloads API to pickup new components \n\tkubectl rollout restart deployment/nginx-ingress-nginx-controller -n nginx\n\tkubectl rollout status deployment/nginx-ingress-nginx-controller -n nginx\n\n.PHONY: redis\nredis: ## Install Redis into the cluster\n\t# Updating Help repos...\n\thelm repo add bitnami https://charts.bitnami.com/bitnami\n\thelm repo update\n\tkubectl create ns redis\n\t# redis \n\thelm install redis bitnami/redis -n redis \n\t# Waiting for redis to be ready...\n\tkubectl rollout status statefulset.apps/redis-master -n redis\n\tkubectl rollout status statefulset.apps/redis-slave -n redis\n\t\n.PHONY: mongo\nmongo: ## Install Mongo into the cluster\n\t# Updating Help repos...\n\thelm repo add bitnami https://charts.bitnami.com/bitnami\n\thelm repo update\n\tkubectl create ns mongo\n\t# mongo \n\thelm install mongo \\\n\t\t--set architecture=replicaset \\\n\t\t--set auth.username=dapr \\\n\t\t--set auth.database=dapr \\\n\t\t--set replicaSetName=staters0 \\\n\t\t--set replicaCount=3 \\\n\t\tbitnami/mongodb \\\n\t\t-n mongo\n\t# Waiting for mongo to be ready...\n\tkubectl rollout status statefulset.apps/mongo-mongodb -n mongo\n\tkubectl rollout status statefulset.apps/mongo-mongodb-arbiter -n mongo\n\t\n.PHONY: kafka\nkafka: ## Install Kafka into the cluster\n\t# Updating Help repos...\n\thelm repo add confluentinc https://confluentinc.github.io/cp-helm-charts/\t\n\thelm repo update\n\tkubectl create ns kafka\n\t# kafka \n\thelm install kafka confluentinc/cp-helm-charts -n kafka \\\n\t\t--set cp-schema-registry.enabled=false \\\n\t\t--set cp-kafka-rest.enabled=false \\\n\t\t--set cp-kafka-connect.enabled=false\n\t# wait for the deployment \n\tkubectl rollout status deployment.apps/kafka-cp-control-center -n kafka\n\tkubectl rollout status deployment.apps/kafka-cp-ksql-server -n kafka\n\tkubectl rollout status statefulset.apps/kafka-cp-kafka -n kafka\n\tkubectl rollout status statefulset.apps/kafka-cp-zookeeper -n kafka\n\n.PHONY: namespace\nnamespace: ## Configures namespace (make namespace NSNAME=default)\n\t# Create namespace if one doesn't exists \n\tsed \"s/NSNAME/${NSNAME}/g\" config/namespace-template.yml > config/namespace.yml\n\tkubectl apply -f config/namespace.yml\n\n.PHONY: namespace-pass\nnamespace-pass: ## Configures Mongo and Redis passwords in namespace (make namespace-pass NSNAME=default)\n\t# Configure Redis password\n\t$(eval REDIS_PASSWORD=$(shell kubectl get secret -n redis redis -o jsonpath=\"{.data.redis-password}\" | base64 --decode))\n\tkubectl create secret generic redis-secret --from-literal=password=\"$(REDIS_PASSWORD)\" -n $(NSNAME)\n\t# Configre Mongo password\n\t$(eval MONGO_PASSWORD=$(shell kubectl get secret -n mongo mongo-mongodb -o jsonpath=\"{.data.mongodb-password}\" | base64 --decode))\n\tkubectl create secret generic mongo-secret --from-literal=password=\"$(MONGO_PASSWORD)\" -n $(NSNAME)\n\n.PHONY: portstop\nportstop: ## Stop previously forwarded observability ports \n\tif pgrep kubectl &> /dev/null ; then pkill kubectl -9 ; fi\n\n.PHONY: upgrade\nupgrade: ## Upgrades Dapr to specific release version (make upgrade DAPR_RELEASE=\"0.11.0-rc.3\")\n\tkubectl delete clusterrolebinding dapr-operator\n\tdapr mtls export -o ./certs\n\thelm upgrade dapr -n=dapr-system \\\n\t\t--set-string global.tag=$(DAPR_RELEASE) \\\n\t\t--set-string global.registry=docker.io/daprio \\\n\t\t--set-file dapr_sentry.tls.root.certPEM=./certs/ca.crt \\\n\t\t--set-file dapr_sentry.tls.issuer.certPEM=./certs/issuer.crt \\\n\t\t--set-file dapr_sentry.tls.issuer.keyPEM=./certs/issuer.key \\\n\t\t--reset-values ./charts/dapr\n\n.PHONY: help\nhelp: ## Display available commands\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \\\n\t\t'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n"
  },
  {
    "path": "setup/README.md",
    "content": "# Dapr Cluster Setup\n\nAn opinionated deployment of Dapr on Kubernetes, configured with:\n\n* Ingress with custom domain and TLS termination\n  * [NGINX](https://nginx.org/en/) for ingress controller and TLS to service mapping \n  * [letsencrypt](https://letsencrypt.org/) as certificate provider\n* [KEDA](https://keda.sh/) for autoscaling\n* Metrics Monitoring\n  * [Prometheus](https://prometheus.io/) for metrics aggregation\n  * [Grafana](https://grafana.com/) for metrics visualization with Dapr monitoring dashboards\n* Log Management\n  * [Fluentd](https://www.fluentd.org/) for log collection and forwarding\n  * [Elasticsearch](https://www.elastic.co/) for log aggregation and query execution\n  * [Kibana](https://www.elastic.co/products/kibana) for full-text log query and visualization\n* Distributed Tracing\n  * [Jaeger](https://www.jaegertracing.io/) for capturing traces, latency and dependency viewing\n\n> All demos in the [dapr-demo](../) repository are validated on this deployment\n  \n## Prerequisites\n\n* 1.15+ Kubernates cluster. If needed, you can setup cluster on:\n  * [AKS](./aks/)\n  * [GKE](./gke/)\n  * AKS (coming)\n* Tooling on the machine where you will be running this setup:\n  * [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) to do k8s stuff (`brew install kubectl`)\n  * [Helm 3](https://helm.sh/docs/intro/install/) to install Dapr and its dependencies (`brew install helm`)\n  * [certbot](https://certbot.eff.org/lets-encrypt/osx-other.html) to generate wildcard cert (`brew install certbot`)\n* Domain name and access to the DNS service where you can manage that domain (required for letsencrypt challenge during cert generation and the `A` record creation to pont to the ingress gateway IP for custom domain support)\n\n## Setup \n\nThe following parameters can be used to configure your deployment. Define these as environment variables to set or override the default value:\n\n```shell\nDOMAIN            # default: example.com\nDAPR_HA           # default: true\nDAPR_LOG_AS_JSON  # default: true\n```\n\n> Note, make sure the correct \"target\" cluster is set kubectl context (`kubectl config current-context`). You can lists all registered contexts using: `kubectl config get-contexts`, and if needed, set it using `kubectl config use-context demo`.\n\n## Usage\n\nStart by navigate to the [setup](./setup) directory\n\n> Run `make` by itself to see the active configuration \n\nTo deploy and configure Dapr \n\n* `make dapr` to install Dapr, KEDA, and the entire observability stack\n* `make config` to perform post-install configurations\n\n> Optionally you can use `make upgrade` to in place upgrade Dapr to specific version\n\nTo configure external access \n\n* `make ip` (optional) to create static IP in the cluster resource group\n* `make certs` to create TLS certs using letsencrypt\n* `make ingress` to configures NGINX ingress, SSL termination, Dapr API auth\n* `make dns` to configure your DNS service for custom domain support \n* `make test` to test deployment\n\nTo deploy in-cluster data services\n\n* `make redis` to install Redis into the cluster \n* `make mongo` to install Mongo into the cluster \n* `make kafka` to install Kafka into the cluster \n\nAnd few cluster operations helpers\n\n* `node-list` to print node resources and their usage\n* `make ports` to forward observability dashboards ports \n* `make pass` to print the Grafana password (username: admin)\n\nThen for each namespace you want to deploy Dapr apps to\n\n* `make namespace` to create/configure namespace with service secrets\n\n## Accessing observability dashboards \n\nTo get access to the Kibana, Grafana, Zipkin dashboards run:\n\n```shell\nmake ports\n```\n\nThis will forward the necessary ports so you can access the dashboards using: \n\n* kibana - http://localhost:5601\n* grafana - http://localhost:8888\n* zipkin - http://localhost:9411\n\nTo stop port forwarding run \n\n```shell\nmake portstop\n```\n\n\n## Help\n\nTo find the list of all the commands with their short descriptions run: \n\n```shell\nmake help\n```\n\n## Disclaimer\n\nThis is my personal project and it does not represent my employer. While I do my best to ensure that everything works, I take no responsibility for issues caused by this code.\n\n## License\n\nThis software is released under the [MIT](../LICENSE)"
  },
  {
    "path": "setup/aks/Makefile",
    "content": "CLUSTER_NAME     ?=demo\nCLUSTER_VERSION  ?=1.18.10\nNODE_COUNT       ?=3\nNODE_TYPE        ?=Standard_D4_v2\n\n.PHONY: all\nall:\n\t@echo === ACTIVE CONFIGURATION ===\n\t@echo \"CLUSTER_NAME:     ${CLUSTER_NAME}\"\n\t@echo \"CLUSTER_VERSION:  ${CLUSTER_VERSION}\"\n\t@echo \"NODE_COUNT:       ${NODE_COUNT}\"\n\t@echo \"NODE_TYPE:        ${NODE_TYPE}\"\n\t@echo \n\t@echo \"Export these as environment variables to change their values\"\n\t@echo \n\n\n.PHONY: cluster-list\ncluster-list: ## List AKS clusters\n\taz aks list -o table\n\n.PHONY: version-list\nversion-list: ## List Kubernetes versions supported in AKS\n\taz aks get-versions -o json --query 'orchestrators[].orchestratorVersion'\n\n.PHONY: cluster\ncluster: ## Create AKS cluster\n\taz aks create \\\n\t\t--name $(CLUSTER_NAME) \\\n\t\t--node-count $(NODE_COUNT) \\\n\t\t--node-vm-size $(NODE_TYPE) \\\n\t\t--kubernetes-version $(CLUSTER_VERSION)\n\taz aks get-credentials --name $(CLUSTER_NAME)\n\n.PHONY: cluster-down\ncluster-down: cluster-list ## Delete previously created AKS cluster (make clusterdown CLUSTER_NAME=demo)\n\taz aks delete --name $(CLUSTER_NAME)\n\n.PHONY: ip\nip: ## Create Static IP for the existing AKS cluster\n\t$(eval CLUSTERRC=$(shell az aks show -n $(CLUSTER_NAME) -o tsv --query nodeResourceGroup))\n\taz network public-ip create \\\n\t\t-g $(CLUSTERRC) \\\n\t\t-n \"${CLUSTER_NAME}IP\" \\\n\t\t--sku STANDARD\n\taz network public-ip show \\\n\t\t-g $(CLUSTERRC) \\\n\t\t-n \"${CLUSTER_NAME}IP\" \\\n\t\t-o tsv --query ipAddress\n\n.PHONY: node-pool\nnode-pool: ## Add new AKS node pool\n\t$(eval NODE_SUFIX=$(shell date +\"%d%m\"))\n\taz aks nodepool add \\\n\t\t--cluster-name $(CLUSTER_NAME) \\\n\t\t--name nodepool$(NODE_SUFIX) \\\n\t\t--node-count $(NODE_COUNT) \\\n\t\t--node-vm-size $(NODE_TYPE) \\\n\t\t--mode System \\\n\t\t--no-wait\n\n\n.PHONY: help\nhelp: ## Display available commands\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \\\n\t\t'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n"
  },
  {
    "path": "setup/aks/README.md",
    "content": "# AKS Cluster Setup\n\nThe following parameters can be used to configure your deployment. Define these as environment variables to set or override the default value:\n\n```shell\nCLUSTER_NAME      # default: demo\nCLUSTER_VERSION   # default: 1.18.8\nNODE_COUNT        # default: 3\nNODE_TYPE         # default: Standard_D4_v2\n```\n\n> Note, this assumes your default Azure resource group and location are already defined. If not, run\n\n```shell\naz account set --subscription <id or name>\naz configure --defaults location=<preferred location> group=<preferred resource group>\n```\n\n## Usage\n\nStart by navigating to the [setup/aks](./setup/aks) directory\n\n> Run `make` by itself to see the active configuration \n\n* `make cluster` to create a cluster on AKS (make cluster CLUSTER_NAME=demo)\n* `make ip` (optional) to create static IP in the cluster resource group\n* `make node-pool` (optional) to add new AKS node pool\n* `make node-list` to print node resource usage\n* `make cluster-list` to list your AKS clusters\n* `make version-list` to list Kubernetes versions supported on AKS\n\nOnce cluster is created, you can follow [these instructions](../) to configure Dapr.\n\n## Cleanup\n\nTo lists previously created clusters run \n\n```shell\nmake cluster-list\n```\n\nTo delete any of the previously created clusters run \n\n> yes, there will be a prompt to confirm before deleting\n\n```shell\nmake cluster-down CLUSTER_NAME=name\n```\n\n## Help\n\nTo find the list of all the commands with their short descriptions run: \n\n```shell\nmake help\n```\n\n## Disclaimer\n\nThis is my personal project and it does not represent my employer. While I do my best to ensure that everything works, I take no responsibility for issues caused by this code.\n\n## License\n\nThis software is released under the [MIT](../../LICENSE)"
  },
  {
    "path": "setup/config/actor-dashboard.json",
    "content": "{\n  \"dashboard\": {\n    \"__inputs\": [\n      {\n        \"name\": \"DS_DAPR\",\n        \"label\": \"Dapr\",\n        \"description\": \"\",\n        \"type\": \"datasource\",\n        \"pluginId\": \"prometheus\",\n        \"pluginName\": \"Prometheus\"\n      }\n    ],\n    \"__requires\": [\n      {\n        \"type\": \"grafana\",\n        \"id\": \"grafana\",\n        \"name\": \"Grafana\",\n        \"version\": \"6.7.3\"\n      },\n      {\n        \"type\": \"panel\",\n        \"id\": \"graph\",\n        \"name\": \"Graph\",\n        \"version\": \"\"\n      },\n      {\n        \"type\": \"datasource\",\n        \"id\": \"prometheus\",\n        \"name\": \"Prometheus\",\n        \"version\": \"1.0.0\"\n      }\n    ],\n    \"annotations\": {\n      \"list\": [\n        {\n          \"$$hashKey\": \"object:487\",\n          \"builtIn\": 1,\n          \"datasource\": \"Dapr\",\n          \"enable\": true,\n          \"hide\": true,\n          \"iconColor\": \"rgba(0, 211, 255, 1)\",\n          \"limit\": 100,\n          \"name\": \"Annotations & Alerts\",\n          \"showIn\": 0,\n          \"type\": \"dashboard\"\n        }\n      ]\n    },\n    \"description\": \"This is the dashboard for Dapr Actor\",\n    \"editable\": true,\n    \"gnetId\": null,\n    \"graphTooltip\": 0,\n    \"id\": null,\n    \"iteration\": 1591551276071,\n    \"links\": [],\n    \"panels\": [\n      {\n        \"collapsed\": true,\n        \"datasource\": \"Dapr\",\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 0\n        },\n        \"id\": 21,\n        \"panels\": [\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": \"Dapr\",\n            \"description\": \"This charts shows the turn-around latency when user app calls Dapr Actor API. For example, you can understand each statestore and service invocation performance.\",\n            \"fill\": 1,\n            \"fillGradient\": 0,\n            \"gridPos\": {\n              \"h\": 8,\n              \"w\": 12,\n              \"x\": 0,\n              \"y\": 1\n            },\n            \"hiddenSeries\": false,\n            \"id\": 19,\n            \"legend\": {\n              \"alignAsTable\": false,\n              \"avg\": false,\n              \"current\": true,\n              \"max\": false,\n              \"min\": false,\n              \"rightSide\": false,\n              \"show\": true,\n              \"sideWidth\": 150,\n              \"total\": false,\n              \"values\": true\n            },\n            \"lines\": true,\n            \"linewidth\": 1,\n            \"nullPointMode\": \"null\",\n            \"options\": {\n              \"dataLinks\": []\n            },\n            \"percentage\": false,\n            \"pluginVersion\": \"6.6.2\",\n            \"pointradius\": 2,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"histogram_quantile(0.95, sum(rate(dapr_http_server_latency_bucket{path=~\\\"/v1.0/actors/$dapr_actor_type/.*\\\", kubernetes_namespace=\\\"$namespace\\\"}[5m])) by (le, app_id, method, path))\",\n                \"format\": \"time_series\",\n                \"instant\": false,\n                \"interval\": \"\",\n                \"legendFormat\": \"[95p] {{method}} {{path}} ({{app_id}})\",\n                \"refId\": \"A\"\n              },\n              {\n                \"expr\": \"histogram_quantile(0.75, sum(rate(dapr_http_server_latency_bucket{path=~\\\"/v1.0/actors/$dapr_actor_type/.*\\\", kubernetes_namespace=\\\"$namespace\\\"}[5m])) by (le, app_id, method, path))\",\n                \"format\": \"time_series\",\n                \"instant\": false,\n                \"interval\": \"\",\n                \"legendFormat\": \"[75p] {{method}} {{path}} ({{app_id}})\",\n                \"refId\": \"B\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"Actor API Latency (App->Dapr) (95p, 75p)\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 0,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"$$hashKey\": \"object:1089\",\n                \"decimals\": 2,\n                \"format\": \"ms\",\n                \"label\": \"\",\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              },\n              {\n                \"$$hashKey\": \"object:1090\",\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": false\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          },\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": \"Dapr\",\n            \"description\": \"This charts shows completed request rate (rps) when User app calls Dapr Actor APIs.\",\n            \"fill\": 1,\n            \"fillGradient\": 0,\n            \"gridPos\": {\n              \"h\": 8,\n              \"w\": 12,\n              \"x\": 12,\n              \"y\": 1\n            },\n            \"hiddenSeries\": false,\n            \"id\": 31,\n            \"legend\": {\n              \"alignAsTable\": false,\n              \"avg\": false,\n              \"current\": false,\n              \"max\": false,\n              \"min\": false,\n              \"rightSide\": false,\n              \"show\": true,\n              \"sideWidth\": 150,\n              \"total\": false,\n              \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 1,\n            \"nullPointMode\": \"null\",\n            \"options\": {\n              \"dataLinks\": []\n            },\n            \"percentage\": false,\n            \"pluginVersion\": \"6.6.2\",\n            \"pointradius\": 2,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"sum by (app_id, method, path) (rate(dapr_http_server_response_count{path=~\\\"/v1.0/actors/$dapr_actor_type/.*\\\", kubernetes_namespace=\\\"$namespace\\\"}[5m]))\",\n                \"format\": \"time_series\",\n                \"instant\": false,\n                \"interval\": \"\",\n                \"legendFormat\": \"{{method}} {{path}} ({{app_id}})\",\n                \"refId\": \"A\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"Actor API request rate (App -> Dapr) (RPS)\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 0,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"$$hashKey\": \"object:191\",\n                \"decimals\": 2,\n                \"format\": \"reqps\",\n                \"label\": \"\",\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              },\n              {\n                \"$$hashKey\": \"object:192\",\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": false\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          },\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": \"Dapr\",\n            \"description\": \"This charts shows the inbound request latency from Dapr to App. You can analyze App's API endpoint performance.\",\n            \"fill\": 1,\n            \"fillGradient\": 0,\n            \"gridPos\": {\n              \"h\": 8,\n              \"w\": 12,\n              \"x\": 0,\n              \"y\": 9\n            },\n            \"hiddenSeries\": false,\n            \"id\": 18,\n            \"legend\": {\n              \"avg\": false,\n              \"current\": true,\n              \"max\": false,\n              \"min\": false,\n              \"rightSide\": false,\n              \"show\": true,\n              \"sideWidth\": 150,\n              \"total\": false,\n              \"values\": true\n            },\n            \"lines\": true,\n            \"linewidth\": 1,\n            \"nullPointMode\": \"null\",\n            \"options\": {\n              \"dataLinks\": []\n            },\n            \"percentage\": false,\n            \"pluginVersion\": \"6.6.2\",\n            \"pointradius\": 2,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"histogram_quantile(0.95, sum(rate(dapr_http_client_roundtrip_latency_bucket{path=~\\\"actors/$dapr_actor_type/.*/method/.*\\\", kubernetes_namespace=\\\"$namespace\\\"}[5m])) by (le, app_id, method, path))\",\n                \"format\": \"time_series\",\n                \"instant\": false,\n                \"interval\": \"\",\n                \"legendFormat\": \"[95p] {{method}} /{{path}} ({{app_id}})\",\n                \"refId\": \"A\"\n              },\n              {\n                \"expr\": \"histogram_quantile(0.75, sum(rate(dapr_http_client_roundtrip_latency_bucket{path=~\\\"actors/$dapr_actor_type/.*/method/.*\\\", kubernetes_namespace=\\\"$namespace\\\"}[5m])) by (le, app_id, method, path))\",\n                \"format\": \"time_series\",\n                \"instant\": false,\n                \"interval\": \"\",\n                \"legendFormat\": \"[75p] {{method}} /{{path}} ({{app_id}})\",\n                \"refId\": \"B\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"Actor Callback latency (Dapr -> App) (95p, 75p)\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 0,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"$$hashKey\": \"object:334\",\n                \"decimals\": 2,\n                \"format\": \"ms\",\n                \"label\": \"\",\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              },\n              {\n                \"$$hashKey\": \"object:335\",\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": false\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          },\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": \"Dapr\",\n            \"description\": \"This charts shows request rate (rps) when Dapr runtime calls User app HTTP endpoints.\",\n            \"fill\": 1,\n            \"fillGradient\": 0,\n            \"gridPos\": {\n              \"h\": 8,\n              \"w\": 12,\n              \"x\": 12,\n              \"y\": 9\n            },\n            \"hiddenSeries\": false,\n            \"id\": 32,\n            \"interval\": \"\",\n            \"legend\": {\n              \"alignAsTable\": false,\n              \"avg\": false,\n              \"current\": false,\n              \"max\": false,\n              \"min\": false,\n              \"rightSide\": false,\n              \"show\": true,\n              \"sideWidth\": 150,\n              \"total\": false,\n              \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 1,\n            \"nullPointMode\": \"null\",\n            \"options\": {\n              \"dataLinks\": []\n            },\n            \"percentage\": false,\n            \"pluginVersion\": \"6.6.2\",\n            \"pointradius\": 2,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"sum by (app_id, method, path) (rate(dapr_http_client_completed_count{path=~\\\"actors/$dapr_actor_type/.*/method/.*\\\", kubernetes_namespace=\\\"$namespace\\\"}[5m]))\",\n                \"format\": \"time_series\",\n                \"instant\": false,\n                \"interval\": \"\",\n                \"legendFormat\": \"{{method}} {{path}} ({{app_id}})\",\n                \"refId\": \"A\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"Actor Callback request rate (Dapr -> App) (RPS)\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 0,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"$$hashKey\": \"object:411\",\n                \"decimals\": 2,\n                \"format\": \"reqps\",\n                \"label\": \"\",\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              },\n              {\n                \"$$hashKey\": \"object:412\",\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": false\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          }\n        ],\n        \"title\": \"Throughput/latency\",\n        \"type\": \"row\"\n      },\n      {\n        \"collapsed\": false,\n        \"datasource\": \"Dapr\",\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 1\n        },\n        \"id\": 71,\n        \"panels\": [],\n        \"title\": \"Timer & Reminder\",\n        \"type\": \"row\"\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Dapr\",\n        \"description\": \"Timer trigger status\",\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 2\n        },\n        \"hiddenSeries\": false,\n        \"id\": 72,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"rightSide\": false,\n          \"show\": true,\n          \"sideWidth\": 150,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"dataLinks\": []\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"6.6.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(rate(dapr_http_client_roundtrip_latency_bucket{path=~\\\"actors/$dapr_actor_type/.*/method/timer/.*\\\", kubernetes_namespace=\\\"$namespace\\\"}[5m])) by (path)\",\n            \"format\": \"time_series\",\n            \"instant\": false,\n            \"interval\": \"\",\n            \"legendFormat\": \"{{path}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"Actor timer trigger\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"$$hashKey\": \"object:1552\",\n            \"decimals\": 1,\n            \"format\": \"none\",\n            \"label\": \"\",\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"$$hashKey\": \"object:1553\",\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": false\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Dapr\",\n        \"description\": \"Reminder trigger status\",\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 2\n        },\n        \"hiddenSeries\": false,\n        \"id\": 73,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"rightSide\": false,\n          \"show\": true,\n          \"sideWidth\": 150,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"dataLinks\": []\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"6.6.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(rate(dapr_http_client_roundtrip_latency_bucket{path=~\\\"actors/$dapr_actor_type/.*/method/remind/.*\\\", kubernetes_namespace=\\\"$namespace\\\"}[5m])) by (path)\",\n            \"format\": \"time_series\",\n            \"instant\": false,\n            \"interval\": \"\",\n            \"legendFormat\": \"{{path}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"Actor reminder trigger\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"$$hashKey\": \"object:1552\",\n            \"decimals\": 1,\n            \"format\": \"none\",\n            \"label\": \"\",\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"$$hashKey\": \"object:1553\",\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": false\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"collapsed\": false,\n        \"datasource\": \"Dapr\",\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 10\n        },\n        \"id\": 69,\n        \"panels\": [],\n        \"title\": \"Concurrency\",\n        \"type\": \"row\"\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Dapr\",\n        \"description\": \"It contains the number of pending actor calls that are waiting to acquire the per-actor lock that enforces turn-based concurrency.\",\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 11\n        },\n        \"hiddenSeries\": false,\n        \"id\": 67,\n        \"legend\": {\n          \"alignAsTable\": false,\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"dataLinks\": []\n        },\n        \"percentage\": false,\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"dapr_runtime_actor_pending_actor_calls{actor_type=~\\\"$dapr_actor_type\\\"}\",\n            \"interval\": \"\",\n            \"legendFormat\": \"{{actor_type}}.{{actor_id}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"Pending locks Per Actor\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"$$hashKey\": \"object:977\",\n            \"format\": \"short\",\n            \"label\": \"Pending Locks\",\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"$$hashKey\": \"object:978\",\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      }\n    ],\n    \"refresh\": \"10s\",\n    \"schemaVersion\": 22,\n    \"style\": \"dark\",\n    \"tags\": [ \"autosetup\" ],\n    \"templating\": {\n      \"list\": [\n        {\n          \"allValue\": null,\n          \"current\": {},\n          \"datasource\": \"Dapr\",\n          \"definition\": \"label_values(dapr_runtime_component_loaded,kubernetes_namespace)\",\n          \"hide\": 0,\n          \"includeAll\": false,\n          \"index\": -1,\n          \"label\": \"NAMESPACE\",\n          \"multi\": false,\n          \"name\": \"namespace\",\n          \"options\": [],\n          \"query\": \"label_values(dapr_runtime_component_loaded,kubernetes_namespace)\",\n          \"refresh\": 1,\n          \"regex\": \"\",\n          \"skipUrlSync\": false,\n          \"sort\": 0,\n          \"tagValuesQuery\": \"\",\n          \"tags\": [],\n          \"tagsQuery\": \"\",\n          \"type\": \"query\",\n          \"useTags\": false\n        },\n        {\n          \"allValue\": null,\n          \"current\": {},\n          \"datasource\": \"Dapr\",\n          \"definition\": \"label_values(dapr_runtime_actor_pending_actor_calls,actor_type)\",\n          \"hide\": 0,\n          \"includeAll\": false,\n          \"index\": -1,\n          \"label\": \"ACTOR TYPE\",\n          \"multi\": true,\n          \"name\": \"dapr_actor_type\",\n          \"options\": [],\n          \"query\": \"label_values(dapr_runtime_actor_pending_actor_calls,actor_type)\",\n          \"refresh\": 1,\n          \"regex\": \"\",\n          \"skipUrlSync\": false,\n          \"sort\": 1,\n          \"tagValuesQuery\": \"\",\n          \"tags\": [],\n          \"tagsQuery\": \"\",\n          \"type\": \"query\",\n          \"useTags\": false\n        }\n      ]\n    },\n    \"time\": {\n      \"from\": \"now-3h\",\n      \"to\": \"now\"\n    },\n    \"timepicker\": {\n      \"refresh_intervals\": [\n        \"5s\",\n        \"10s\",\n        \"30s\",\n        \"1m\",\n        \"5m\",\n        \"15m\",\n        \"30m\",\n        \"1h\",\n        \"2h\",\n        \"1d\"\n      ]\n    },\n    \"timezone\": \"browser\",\n    \"title\": \"Dapr Actor Dashboard\",\n    \"variables\": {\n      \"list\": []\n    },\n    \"version\": 0\n  },\n  \"overwrite\": true\n}"
  },
  {
    "path": "setup/config/fluentd-config.yaml",
    "content": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: fluentd-config\n  namespace: kube-system\ndata:\n  fluent.conf: |\n    <match fluent.**>\n      @type null\n    </match>\n\n    <match kubernetes.var.log.containers.**fluentd**.log>\n      @type null\n    </match>\n\n    <match kubernetes.var.log.containers.**kube-system**.log>\n      @type null\n    </match>\n\n    <match kubernetes.var.log.containers.**kibana**.log>\n      @type null\n    </match>\n\n    <source>\n      @type tail\n      path /var/log/containers/*.log\n      pos_file fluentd-docker.pos\n      time_format %Y-%m-%dT%H:%M:%S\n      tag kubernetes.*\n      <parse>\n        @type multi_format\n        <pattern>\n          format json\n          time_key time\n          time_type string\n          time_format \"%Y-%m-%dT%H:%M:%S.%NZ\"\n          keep_time_key false\n        </pattern>\n        <pattern>\n          format regexp\n          expression /^(?<time>.+) (?<stream>stdout|stderr)( (?<logtag>.))? (?<log>.*)$/\n          time_format '%Y-%m-%dT%H:%M:%S.%N%:z'\n          keep_time_key false\n        </pattern>\n      </parse>\n    </source>\n\n    <filter kubernetes.**>\n      @type kubernetes_metadata\n      @id filter_kube_metadata\n    </filter>\n\n    <filter kubernetes.var.log.containers.**>\n      @type parser\n      <parse>\n        @type json\n        format json\n        time_key time\n        time_type string\n        time_format \"%Y-%m-%dT%H:%M:%S.%NZ\"\n        keep_time_key false\n      </parse>\n      key_name log\n      replace_invalid_sequence true\n      emit_invalid_record_to_error true\n      reserve_data true\n    </filter>\n\n    <match **>\n      @type elasticsearch\n      @id out_es\n      @log_level info\n      include_tag_key true\n      host \"#{ENV['FLUENT_ELASTICSEARCH_HOST']}\"\n      port \"#{ENV['FLUENT_ELASTICSEARCH_PORT']}\"\n      path \"#{ENV['FLUENT_ELASTICSEARCH_PATH']}\"\n      scheme \"#{ENV['FLUENT_ELASTICSEARCH_SCHEME'] || 'http'}\"\n      ssl_verify \"#{ENV['FLUENT_ELASTICSEARCH_SSL_VERIFY'] || 'true'}\"\n      ssl_version \"#{ENV['FLUENT_ELASTICSEARCH_SSL_VERSION'] || 'TLSv1_2'}\"\n      user \"#{ENV['FLUENT_ELASTICSEARCH_USER'] || use_default}\"\n      password \"#{ENV['FLUENT_ELASTICSEARCH_PASSWORD'] || use_default}\"\n      reload_connections \"#{ENV['FLUENT_ELASTICSEARCH_RELOAD_CONNECTIONS'] || 'false'}\"\n      reconnect_on_error \"#{ENV['FLUENT_ELASTICSEARCH_RECONNECT_ON_ERROR'] || 'true'}\"\n      reload_on_failure \"#{ENV['FLUENT_ELASTICSEARCH_RELOAD_ON_FAILURE'] || 'true'}\"\n      log_es_400_reason \"#{ENV['FLUENT_ELASTICSEARCH_LOG_ES_400_REASON'] || 'false'}\"\n      logstash_prefix \"#{ENV['FLUENT_ELASTICSEARCH_LOGSTASH_PREFIX'] || 'dapr'}\"\n      logstash_dateformat \"#{ENV['FLUENT_ELASTICSEARCH_LOGSTASH_DATEFORMAT'] || '%Y.%m.%d'}\"\n      logstash_format \"#{ENV['FLUENT_ELASTICSEARCH_LOGSTASH_FORMAT'] || 'true'}\"\n      index_name \"#{ENV['FLUENT_ELASTICSEARCH_LOGSTASH_INDEX_NAME'] || 'dapr'}\"\n      type_name \"#{ENV['FLUENT_ELASTICSEARCH_LOGSTASH_TYPE_NAME'] || 'fluentd'}\"\n      include_timestamp \"#{ENV['FLUENT_ELASTICSEARCH_INCLUDE_TIMESTAMP'] || 'false'}\"\n      template_name \"#{ENV['FLUENT_ELASTICSEARCH_TEMPLATE_NAME'] || use_nil}\"\n      template_file \"#{ENV['FLUENT_ELASTICSEARCH_TEMPLATE_FILE'] || use_nil}\"\n      template_overwrite \"#{ENV['FLUENT_ELASTICSEARCH_TEMPLATE_OVERWRITE'] || use_default}\"\n      sniffer_class_name \"#{ENV['FLUENT_SNIFFER_CLASS_NAME'] || 'Fluent::Plugin::ElasticsearchSimpleSniffer'}\"\n      request_timeout \"#{ENV['FLUENT_ELASTICSEARCH_REQUEST_TIMEOUT'] || '5s'}\"\n      <buffer>\n        flush_thread_count \"#{ENV['FLUENT_ELASTICSEARCH_BUFFER_FLUSH_THREAD_COUNT'] || '8'}\"\n        flush_interval \"#{ENV['FLUENT_ELASTICSEARCH_BUFFER_FLUSH_INTERVAL'] || '5s'}\"\n        chunk_limit_size \"#{ENV['FLUENT_ELASTICSEARCH_BUFFER_CHUNK_LIMIT_SIZE'] || '2M'}\"\n        queue_limit_length \"#{ENV['FLUENT_ELASTICSEARCH_BUFFER_QUEUE_LIMIT_LENGTH'] || '32'}\"\n        retry_max_interval \"#{ENV['FLUENT_ELASTICSEARCH_BUFFER_RETRY_MAX_INTERVAL'] || '30'}\"\n        retry_forever true\n      </buffer>\n    </match>"
  },
  {
    "path": "setup/config/fluentd.yaml",
    "content": "---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: fluentd\n  namespace: kube-system\n---\napiVersion: rbac.authorization.k8s.io/v1beta1\nkind: ClusterRole\nmetadata:\n  name: fluentd\n  namespace: kube-system\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - pods\n  - namespaces\n  verbs:\n  - get\n  - list\n  - watch\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1beta1\nmetadata:\n  name: fluentd\n  namespace: default\nroleRef:\n  kind: ClusterRole\n  name: fluentd\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: ServiceAccount\n  name: fluentd\n  namespace: kube-system\n---\napiVersion: apps/v1\nkind: DaemonSet\nmetadata:\n  name: fluentd\n  namespace: kube-system\n  labels:\n    k8s-app: fluentd-logging\n    version: v1\nspec:\n  selector:\n    matchLabels:\n      k8s-app: fluentd-logging\n      version: v1\n  template:\n    metadata:\n      labels:\n        k8s-app: fluentd-logging\n        version: v1\n    spec:\n      serviceAccount: fluentd\n      serviceAccountName: fluentd\n      tolerations:\n      - key: node-role.kubernetes.io/master\n        effect: NoSchedule\n      containers:\n      - name: fluentd\n        image: fluent/fluentd-kubernetes-daemonset:v1.9.2-debian-elasticsearch7-1.0\n        env:\n          - name:  FLUENT_ELASTICSEARCH_HOST\n            value: \"elasticsearch-master.dapr-monitoring\"\n          - name:  FLUENT_ELASTICSEARCH_PORT\n            value: \"9200\"\n          - name: FLUENT_ELASTICSEARCH_SCHEME\n            value: \"http\"\n          - name: FLUENT_UID\n            value: \"0\"\n        resources:\n          limits:\n            memory: 200Mi\n          requests:\n            cpu: 100m\n            memory: 200Mi\n        volumeMounts:\n        - name: varlog\n          mountPath: /var/log\n        - name: varlibdockercontainers\n          mountPath: /var/lib/docker/containers\n          readOnly: true\n        - name: fluentd-config\n          mountPath: /fluentd/etc\n      terminationGracePeriodSeconds: 30\n      volumes:\n      - name: varlog\n        hostPath:\n          path: /var/log\n      - name: varlibdockercontainers\n        hostPath:\n          path: /var/lib/docker/containers\n      - name: fluentd-config\n        configMap:\n          name: fluentd-config"
  },
  {
    "path": "setup/config/ingress-annotations.yaml",
    "content": "controller:\n  podAnnotations:\n    dapr.io/enabled: \"true\"\n    dapr.io/app-id: \"nginx-ingress\"\n    dapr.io/app-protocol: \"http\"\n    dapr.io/app-port: \"80\"\n    dapr.io/api-token-secret: \"dapr-api-token\"\n    dapr.io/config: \"ingress-config\"\n    dapr.io/log-as-json: \"true\""
  },
  {
    "path": "setup/config/ingress-config.yaml",
    "content": "---\napiVersion: dapr.io/v1alpha1\nkind: Configuration\nmetadata:\n  name: ingress-config\nspec:\n  tracing:\n    samplingRate: \"1\"\n  secrets:\n    scopes:\n      - storeName: kubernetes\n        defaultAccess: deny\n        allowedSecrets: [\"dapr-api-token\"]\n---\napiVersion: dapr.io/v1alpha1\nkind: Configuration\nmetadata:\n  name: tracing\nspec:\n  tracing:\n    samplingRate: \"1\""
  },
  {
    "path": "setup/config/ingress-template.yaml",
    "content": "apiVersion: extensions/v1beta1\nkind: Ingress\nmetadata:\n  name: ingress-rules\n  annotations:\n    kubernetes.io/ingress.class: nginx\n    nginx.ingress.kubernetes.io/rewrite-target: /\nspec:\n  tls:\n    - hosts:\n      - '*.DOMAINNAME'  \n      - DOMAINNAME\n      secretName: tls-secret\n  rules:\n    - host: api.DOMAINNAME\n      http:\n        paths:\n          - path: /\n            backend:\n              serviceName: nginx-ingress-dapr\n              servicePort: 80\n      "
  },
  {
    "path": "setup/config/namespace-template.yml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  name: NSNAME\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: secret-reader\n  namespace: NSNAME\nrules:\n- apiGroups: [\"\"]\n  resources: [\"secrets\"]\n  verbs: [\"get\"]\n---\nkind: RoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: dapr-secret-reader\n  namespace: NSNAME\nsubjects:\n- kind: ServiceAccount\n  name: default\nroleRef:\n  kind: Role\n  name: secret-reader\n  apiGroup: rbac.authorization.k8s.io\n---\napiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: zipkin\n  namespace: NSNAME\nspec:\n  type: exporters.zipkin\n  metadata:\n  - name: enabled\n    value: \"true\"\n  - name: exporterAddress\n    value: \"http://zipkin.dapr-monitoring.svc.cluster.local:9411/api/v2/spans\""
  },
  {
    "path": "setup/config/sidecar-dashboard.json",
    "content": "{\n  \"dashboard\": {\n    \"__inputs\": [\n      {\n        \"name\": \"DS_DAPR\",\n        \"label\": \"Dapr\",\n        \"description\": \"\",\n        \"type\": \"datasource\",\n        \"pluginId\": \"prometheus\",\n        \"pluginName\": \"Prometheus\"\n      }\n    ],\n    \"__requires\": [\n      {\n        \"type\": \"panel\",\n        \"id\": \"bargauge\",\n        \"name\": \"Bar Gauge\",\n        \"version\": \"\"\n      },\n      {\n        \"type\": \"grafana\",\n        \"id\": \"grafana\",\n        \"name\": \"Grafana\",\n        \"version\": \"6.7.3\"\n      },\n      {\n        \"type\": \"panel\",\n        \"id\": \"graph\",\n        \"name\": \"Graph\",\n        \"version\": \"\"\n      },\n      {\n        \"type\": \"datasource\",\n        \"id\": \"prometheus\",\n        \"name\": \"Prometheus\",\n        \"version\": \"1.0.0\"\n      },\n      {\n        \"type\": \"panel\",\n        \"id\": \"stat\",\n        \"name\": \"Stat\",\n        \"version\": \"\"\n      }\n    ],\n    \"annotations\": {\n      \"list\": [\n        {\n          \"$$hashKey\": \"object:356\",\n          \"builtIn\": 1,\n          \"datasource\": \"Dapr\",\n          \"enable\": true,\n          \"hide\": true,\n          \"iconColor\": \"rgba(0, 211, 255, 1)\",\n          \"limit\": 100,\n          \"name\": \"Annotations & Alerts\",\n          \"showIn\": 0,\n          \"type\": \"dashboard\"\n        }\n      ]\n    },\n    \"editable\": true,\n    \"gnetId\": null,\n    \"graphTooltip\": 0,\n    \"id\": null,\n    \"iteration\": 1591551058714,\n    \"links\": [],\n    \"panels\": [\n      {\n        \"collapsed\": false,\n        \"datasource\": \"Dapr\",\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 0\n        },\n        \"id\": 16,\n        \"panels\": [],\n        \"title\": \"Health monitor\",\n        \"type\": \"row\"\n      },\n      {\n        \"cacheTimeout\": null,\n        \"datasource\": \"Dapr\",\n        \"description\": \"This shows the uptime of dapr runtime sidecar. If the sidecar is running for less than 10 minutes, the color will turn into RED.\",\n        \"gridPos\": {\n          \"h\": 4,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 1\n        },\n        \"id\": 70,\n        \"links\": [],\n        \"options\": {\n          \"colorMode\": \"value\",\n          \"fieldOptions\": {\n            \"calcs\": [\n              \"last\"\n            ],\n            \"defaults\": {\n              \"decimals\": 2,\n              \"mappings\": [\n                {\n                  \"id\": 0,\n                  \"op\": \"=\",\n                  \"text\": \"N/A\",\n                  \"type\": 1,\n                  \"value\": \"null\"\n                }\n              ],\n              \"nullValueMode\": \"connected\",\n              \"thresholds\": {\n                \"mode\": \"absolute\",\n                \"steps\": [\n                  {\n                    \"color\": \"red\",\n                    \"value\": null\n                  },\n                  {\n                    \"color\": \"green\",\n                    \"value\": 600\n                  }\n                ]\n              },\n              \"title\": \"\",\n              \"unit\": \"s\"\n            },\n            \"overrides\": [],\n            \"values\": false\n          },\n          \"graphMode\": \"area\",\n          \"justifyMode\": \"auto\",\n          \"orientation\": \"vertical\"\n        },\n        \"pluginVersion\": \"6.7.3\",\n        \"targets\": [\n          {\n            \"expr\": \"time() - max(process_start_time_seconds{kubernetes_name=~\\\"($dapr_app_id)-dapr\\\", kubernetes_namespace=\\\"$namespace\\\"}) by (kubernetes_name)\",\n            \"legendFormat\": \"{{kubernetes_name}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"timeFrom\": \"5m\",\n        \"timeShift\": null,\n        \"title\": \"Sidecar uptime\",\n        \"type\": \"stat\"\n      },\n      {\n        \"collapsed\": false,\n        \"datasource\": \"Dapr\",\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 5\n        },\n        \"id\": 72,\n        \"panels\": [],\n        \"title\": \"Resource usage\",\n        \"type\": \"row\"\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Dapr\",\n        \"description\": \"This shows total amount of Kernel and user CPU usage for Daprd.\",\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 6,\n          \"x\": 0,\n          \"y\": 6\n        },\n        \"hiddenSeries\": false,\n        \"id\": 9,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"dataLinks\": []\n        },\n        \"percentage\": false,\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(rate(container_cpu_usage_seconds_total{pod_name=~\\\"($dapr_app_id).*\\\", namespace=\\\"$namespace\\\"}[5m])) by (pod_name)\",\n            \"legendFormat\": \"{{pod_name}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"Total CPU usage (Kernel and User)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"s\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Dapr\",\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 6,\n          \"x\": 6,\n          \"y\": 6\n        },\n        \"hiddenSeries\": false,\n        \"id\": 11,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"dataLinks\": []\n        },\n        \"percentage\": false,\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(go_goroutines{kubernetes_name=~\\\"($dapr_app_id)-dapr.*\\\", kubernetes_namespace=\\\"$namespace\\\"}) by (kubernetes_name)\",\n            \"legendFormat\": \"{{kubernetes_name}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"Number of GO routines\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Dapr\",\n        \"description\": \"The amount of memory that belongs specifically to that process in bytes. This excludes swapped out memory pages.\",\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 6,\n          \"x\": 12,\n          \"y\": 6\n        },\n        \"hiddenSeries\": false,\n        \"id\": 10,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"dataLinks\": []\n        },\n        \"percentage\": false,\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(process_resident_memory_bytes{kubernetes_name=~\\\"($dapr_app_id)-dapr\\\", kubernetes_namespace=\\\"$namespace\\\"}) by (kubernetes_name)\",\n            \"legendFormat\": \"{{kubernetes_name}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"Memory usage in bytes\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"decbytes\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Dapr\",\n        \"description\": \"The amount of address space that a process is managing. This includes all types of memory, both in RAM and swapped out.\",\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 6,\n          \"x\": 18,\n          \"y\": 6\n        },\n        \"hiddenSeries\": false,\n        \"id\": 12,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"dataLinks\": []\n        },\n        \"percentage\": false,\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(process_virtual_memory_bytes{kubernetes_name=~\\\"($dapr_app_id)-dapr\\\", kubernetes_namespace=\\\"$namespace\\\"}) by (kubernetes_name)\",\n            \"legendFormat\": \"{{kubernetes_name}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"Managed virtual memory in bytes\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"decimals\": 1,\n            \"format\": \"decbytes\",\n            \"label\": \"\",\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": false\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"collapsed\": true,\n        \"datasource\": \"Dapr\",\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 14\n        },\n        \"id\": 21,\n        \"panels\": [\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": \"Dapr\",\n            \"description\": \"This charts shows the turn-around latency when user app calls Dapr API. For example, you can understand each statestore and service invocation performance.\",\n            \"fill\": 1,\n            \"fillGradient\": 0,\n            \"gridPos\": {\n              \"h\": 8,\n              \"w\": 12,\n              \"x\": 0,\n              \"y\": 7\n            },\n            \"hiddenSeries\": false,\n            \"id\": 19,\n            \"legend\": {\n              \"alignAsTable\": false,\n              \"avg\": false,\n              \"current\": true,\n              \"max\": false,\n              \"min\": false,\n              \"rightSide\": false,\n              \"show\": true,\n              \"sideWidth\": 150,\n              \"total\": false,\n              \"values\": true\n            },\n            \"lines\": true,\n            \"linewidth\": 1,\n            \"nullPointMode\": \"null\",\n            \"options\": {\n              \"dataLinks\": []\n            },\n            \"percentage\": false,\n            \"pluginVersion\": \"6.6.2\",\n            \"pointradius\": 2,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"histogram_quantile(0.95, sum(rate(dapr_http_server_latency_bucket{app_id=~\\\"$dapr_app_id\\\", kubernetes_namespace=\\\"$namespace\\\"}[5m])) by (le, app_id, method, path))\",\n                \"format\": \"time_series\",\n                \"instant\": false,\n                \"legendFormat\": \"[95p] {{method}} {{path}} ({{app_id}})\",\n                \"refId\": \"A\"\n              },\n              {\n                \"expr\": \"histogram_quantile(0.75, sum(rate(dapr_http_server_latency_bucket{app_id=~\\\"$dapr_app_id\\\", kubernetes_namespace=\\\"$namespace\\\"}[5m])) by (le, app_id, method, path))\",\n                \"format\": \"time_series\",\n                \"instant\": false,\n                \"legendFormat\": \"[75p] {{method}} {{path}} ({{app_id}})\",\n                \"refId\": \"B\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"Outbound request latency (App->Dapr) (95p, 75p)\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 0,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"decimals\": 2,\n                \"format\": \"ms\",\n                \"label\": \"\",\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": false\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          },\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": \"Dapr\",\n            \"description\": \"This charts shows completed request rate (rps) when User app calls Dapr.\",\n            \"fill\": 1,\n            \"fillGradient\": 0,\n            \"gridPos\": {\n              \"h\": 8,\n              \"w\": 12,\n              \"x\": 12,\n              \"y\": 7\n            },\n            \"hiddenSeries\": false,\n            \"id\": 31,\n            \"legend\": {\n              \"alignAsTable\": false,\n              \"avg\": false,\n              \"current\": false,\n              \"max\": false,\n              \"min\": false,\n              \"rightSide\": false,\n              \"show\": true,\n              \"sideWidth\": 150,\n              \"total\": false,\n              \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 1,\n            \"nullPointMode\": \"null\",\n            \"options\": {\n              \"dataLinks\": []\n            },\n            \"percentage\": false,\n            \"pluginVersion\": \"6.6.2\",\n            \"pointradius\": 2,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"sum by (app_id, method, path) (rate(dapr_http_server_response_count{app_id=~\\\"$dapr_app_id\\\", kubernetes_namespace=\\\"$namespace\\\"}[5m]))\",\n                \"format\": \"time_series\",\n                \"instant\": false,\n                \"legendFormat\": \"{{method}} {{path}} ({{app_id}})\",\n                \"refId\": \"A\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"Outbound request rate (App -> Dapr) (RPS)\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 0,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"decimals\": 2,\n                \"format\": \"reqps\",\n                \"label\": \"\",\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": false\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          },\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": \"Dapr\",\n            \"description\": \"This charts shows the inbound request latency from Dapr to App. You can analyze App's API endpoint performance.\",\n            \"fill\": 1,\n            \"fillGradient\": 0,\n            \"gridPos\": {\n              \"h\": 8,\n              \"w\": 12,\n              \"x\": 0,\n              \"y\": 15\n            },\n            \"hiddenSeries\": false,\n            \"id\": 18,\n            \"legend\": {\n              \"avg\": false,\n              \"current\": true,\n              \"max\": false,\n              \"min\": false,\n              \"rightSide\": false,\n              \"show\": true,\n              \"sideWidth\": 150,\n              \"total\": false,\n              \"values\": true\n            },\n            \"lines\": true,\n            \"linewidth\": 1,\n            \"nullPointMode\": \"null\",\n            \"options\": {\n              \"dataLinks\": []\n            },\n            \"percentage\": false,\n            \"pluginVersion\": \"6.6.2\",\n            \"pointradius\": 2,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"histogram_quantile(0.95, sum(rate(dapr_http_client_roundtrip_latency_bucket{app_id=~\\\"$dapr_app_id\\\", kubernetes_namespace=\\\"$namespace\\\"}[5m])) by (le, app_id, method, path))\",\n                \"format\": \"time_series\",\n                \"instant\": false,\n                \"legendFormat\": \"[95p] {{method}} /{{path}} ({{app_id}})\",\n                \"refId\": \"A\"\n              },\n              {\n                \"expr\": \"histogram_quantile(0.75, sum(rate(dapr_http_client_roundtrip_latency_bucket{app_id=~\\\"$dapr_app_id\\\", kubernetes_namespace=\\\"$namespace\\\"}[5m])) by (le, app_id, method, path))\",\n                \"format\": \"time_series\",\n                \"instant\": false,\n                \"legendFormat\": \"[75p] {{method}} /{{path}} ({{app_id}})\",\n                \"refId\": \"B\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"Inbound request latency (Dapr -> App) (95p, 75p)\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 0,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"decimals\": 2,\n                \"format\": \"ms\",\n                \"label\": \"\",\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": false\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          },\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": \"Dapr\",\n            \"description\": \"This charts shows request rate (rps) when Dapr runtime calls User app HTTP endpoints.\",\n            \"fill\": 1,\n            \"fillGradient\": 0,\n            \"gridPos\": {\n              \"h\": 8,\n              \"w\": 12,\n              \"x\": 12,\n              \"y\": 15\n            },\n            \"hiddenSeries\": false,\n            \"id\": 32,\n            \"legend\": {\n              \"alignAsTable\": false,\n              \"avg\": false,\n              \"current\": false,\n              \"max\": false,\n              \"min\": false,\n              \"rightSide\": false,\n              \"show\": true,\n              \"sideWidth\": 150,\n              \"total\": false,\n              \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 1,\n            \"nullPointMode\": \"null\",\n            \"options\": {\n              \"dataLinks\": []\n            },\n            \"percentage\": false,\n            \"pluginVersion\": \"6.6.2\",\n            \"pointradius\": 2,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"sum by (app_id, method, path) (rate(dapr_http_client_completed_count{app_id=~\\\"$dapr_app_id\\\", kubernetes_namespace=\\\"$namespace\\\"}[5m]))\",\n                \"format\": \"time_series\",\n                \"instant\": false,\n                \"legendFormat\": \"{{method}} {{path}} ({{app_id}})\",\n                \"refId\": \"A\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"Inbound request rate (Dapr -> App) (RPS)\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 0,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"decimals\": 2,\n                \"format\": \"reqps\",\n                \"label\": \"\",\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": false\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          }\n        ],\n        \"title\": \"Throughput/Latency - HTTP\",\n        \"type\": \"row\"\n      },\n      {\n        \"collapsed\": true,\n        \"datasource\": \"Dapr\",\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 15\n        },\n        \"id\": 34,\n        \"panels\": [\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": \"Dapr\",\n            \"description\": \"This charts shows the turn-around latency when user app calls Dapr API. For example, you can understand each statestore and service invocation performance.\",\n            \"fill\": 1,\n            \"fillGradient\": 0,\n            \"gridPos\": {\n              \"h\": 8,\n              \"w\": 12,\n              \"x\": 0,\n              \"y\": 8\n            },\n            \"hiddenSeries\": false,\n            \"id\": 36,\n            \"legend\": {\n              \"avg\": false,\n              \"current\": true,\n              \"max\": false,\n              \"min\": false,\n              \"show\": true,\n              \"total\": false,\n              \"values\": true\n            },\n            \"lines\": true,\n            \"linewidth\": 1,\n            \"nullPointMode\": \"null\",\n            \"options\": {\n              \"dataLinks\": []\n            },\n            \"percentage\": false,\n            \"pointradius\": 2,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"histogram_quantile(0.95, sum(rate(dapr_grpc_io_server_server_latency_bucket{app_id=~\\\"$dapr_app_id\\\", grpc_server_method=~\\\"dapr.Dapr/.*\\\", kubernetes_namespace=\\\"$namespace\\\"}[5m])) by (le, app_id, grpc_server_method))\",\n                \"legendFormat\": \"[95p] - {{grpc_server_method}} ({{app_id}})\",\n                \"refId\": \"A\"\n              },\n              {\n                \"expr\": \"histogram_quantile(0.75, sum(rate(dapr_grpc_io_server_server_latency_bucket{app_id=~\\\"$dapr_app_id\\\", grpc_server_method=~\\\"dapr.Dapr/.*\\\", kubernetes_namespace=\\\"$namespace\\\"}[5m])) by (le, app_id, grpc_server_method))\",\n                \"legendFormat\": \"[75p] {{grpc_server_method}} ({{app_id}})\",\n                \"refId\": \"B\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"Outbound request latency (App->Dapr) (95p, 75p)\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 0,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"format\": \"ms\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          },\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": \"Dapr\",\n            \"description\": \"This charts shows completed request rate (rps) when User app calls Dapr.\",\n            \"fill\": 1,\n            \"fillGradient\": 0,\n            \"gridPos\": {\n              \"h\": 8,\n              \"w\": 12,\n              \"x\": 12,\n              \"y\": 8\n            },\n            \"hiddenSeries\": false,\n            \"id\": 38,\n            \"legend\": {\n              \"avg\": false,\n              \"current\": false,\n              \"max\": false,\n              \"min\": false,\n              \"show\": true,\n              \"total\": false,\n              \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 1,\n            \"nullPointMode\": \"null\",\n            \"options\": {\n              \"dataLinks\": []\n            },\n            \"percentage\": false,\n            \"pointradius\": 2,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": true,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"sum by (app_id, grpc_server_method, grpc_server_status) (rate(dapr_grpc_io_server_completed_rpcs{app_id=~\\\"$dapr_app_id\\\", grpc_server_method=~\\\"dapr.Dapr/.*\\\"}[5m]))\",\n                \"legendFormat\": \"{{grpc_server_status}} {{grpc_server_method}} ({{app_id}})\",\n                \"refId\": \"B\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"Outbound request rate (App -> Dapr) (RPS)\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 0,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"format\": \"reqps\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          },\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": \"Dapr\",\n            \"description\": \"This charts shows the inbound request latency from Dapr to App. You can analyze App's API endpoint performance.\",\n            \"fill\": 1,\n            \"fillGradient\": 0,\n            \"gridPos\": {\n              \"h\": 8,\n              \"w\": 12,\n              \"x\": 0,\n              \"y\": 16\n            },\n            \"hiddenSeries\": false,\n            \"id\": 40,\n            \"legend\": {\n              \"avg\": false,\n              \"current\": true,\n              \"max\": false,\n              \"min\": false,\n              \"show\": true,\n              \"total\": false,\n              \"values\": true\n            },\n            \"lines\": true,\n            \"linewidth\": 1,\n            \"nullPointMode\": \"null\",\n            \"options\": {\n              \"dataLinks\": []\n            },\n            \"percentage\": false,\n            \"pointradius\": 2,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"histogram_quantile(0.95, sum(rate(dapr_grpc_io_client_roundtrip_latency_bucket{app_id=~\\\"$dapr_app_id\\\", grpc_client_method=~\\\"daprclient.DaprClient/.*\\\", kubernetes_namespace=\\\"$namespace\\\"}[5m])) by (le, app_id, grpc_client_method))\",\n                \"legendFormat\": \"[95p] {{grpc_client_method}} ({{app_id}})\",\n                \"refId\": \"A\"\n              },\n              {\n                \"expr\": \"histogram_quantile(0.75, sum(rate(dapr_grpc_io_client_roundtrip_latency_bucket{app_id=~\\\"$dapr_app_id\\\", grpc_client_method=~\\\"daprclient.DaprClient/.*\\\", kubernetes_namespace=\\\"$namespace\\\"}[5m])) by (le, app_id, grpc_client_method))\",\n                \"legendFormat\": \"[75p] {{grpc_client_method}} ({{app_id}})\",\n                \"refId\": \"B\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"Inbound request latency (Dapr -> App) (95p, 75p)\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 0,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"format\": \"ms\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          },\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": \"Dapr\",\n            \"description\": \"This charts shows request rate (rps) when Dapr runtime calls User app gRPC endpoints.\",\n            \"fill\": 1,\n            \"fillGradient\": 0,\n            \"gridPos\": {\n              \"h\": 8,\n              \"w\": 12,\n              \"x\": 12,\n              \"y\": 16\n            },\n            \"hiddenSeries\": false,\n            \"id\": 42,\n            \"legend\": {\n              \"avg\": false,\n              \"current\": false,\n              \"max\": false,\n              \"min\": false,\n              \"show\": true,\n              \"total\": false,\n              \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 1,\n            \"nullPointMode\": \"null\",\n            \"options\": {\n              \"dataLinks\": []\n            },\n            \"percentage\": false,\n            \"pointradius\": 2,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": true,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"sum by (app_id, grpc_client_method, grpc_client_status) (rate(dapr_grpc_io_client_roundtrip_latency_bucket{app_id=~\\\"$dapr_app_id\\\", grpc_client_method=~\\\"daprclient.DaprClient/.*\\\", kubernetes_namespace=\\\"$namespace\\\"}[5m]))\",\n                \"legendFormat\": \"{{grpc_client_status}} {{grpc_client_method}} ({{app_id}})\",\n                \"refId\": \"B\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"Inbound request rate (Dapr -> App) (RPS)\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 0,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"format\": \"reqps\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          }\n        ],\n        \"title\": \"Throughput/Latency - gRPC\",\n        \"type\": \"row\"\n      },\n      {\n        \"collapsed\": true,\n        \"datasource\": \"Dapr\",\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 16\n        },\n        \"id\": 52,\n        \"panels\": [\n          {\n            \"datasource\": \"Dapr\",\n            \"description\": \"This shows the number of configured components. For example, it will shows \\\"2\\\" if you configure redis statestore and redis pubsub components. \",\n            \"gridPos\": {\n              \"h\": 8,\n              \"w\": 6,\n              \"x\": 0,\n              \"y\": 9\n            },\n            \"id\": 56,\n            \"options\": {\n              \"colorMode\": \"value\",\n              \"fieldOptions\": {\n                \"calcs\": [\n                  \"last\"\n                ],\n                \"defaults\": {\n                  \"mappings\": [],\n                  \"thresholds\": {\n                    \"mode\": \"absolute\",\n                    \"steps\": [\n                      {\n                        \"color\": \"green\",\n                        \"value\": null\n                      },\n                      {\n                        \"color\": \"red\",\n                        \"value\": 80\n                      }\n                    ]\n                  }\n                },\n                \"overrides\": [],\n                \"values\": false\n              },\n              \"graphMode\": \"area\",\n              \"justifyMode\": \"auto\",\n              \"orientation\": \"horizontal\"\n            },\n            \"pluginVersion\": \"6.6.2\",\n            \"targets\": [\n              {\n                \"expr\": \"sum(dapr_runtime_component_loaded{app_id=~\\\"$dapr_app_id\\\", kubernetes_namespace=\\\"$namespace\\\"}) by (app_id)\",\n                \"legendFormat\": \"{{app_id}}\",\n                \"refId\": \"A\"\n              }\n            ],\n            \"timeFrom\": null,\n            \"timeShift\": null,\n            \"title\": \"Loaded component configurations\",\n            \"type\": \"stat\"\n          },\n          {\n            \"cacheTimeout\": null,\n            \"datasource\": \"Dapr\",\n            \"description\": \"This shows the number of initialized components. This number must be the same as loaded components. Otherwise, Daprd fails to initialize some of components.\",\n            \"gridPos\": {\n              \"h\": 8,\n              \"w\": 6,\n              \"x\": 6,\n              \"y\": 9\n            },\n            \"id\": 54,\n            \"links\": [],\n            \"options\": {\n              \"colorMode\": \"value\",\n              \"fieldOptions\": {\n                \"calcs\": [\n                  \"last\"\n                ],\n                \"defaults\": {\n                  \"mappings\": [\n                    {\n                      \"id\": 0,\n                      \"op\": \"=\",\n                      \"text\": \"N/A\",\n                      \"type\": 1,\n                      \"value\": \"null\"\n                    }\n                  ],\n                  \"nullValueMode\": \"connected\",\n                  \"thresholds\": {\n                    \"mode\": \"absolute\",\n                    \"steps\": [\n                      {\n                        \"color\": \"green\",\n                        \"value\": null\n                      },\n                      {\n                        \"color\": \"red\",\n                        \"value\": 80\n                      }\n                    ]\n                  },\n                  \"unit\": \"none\"\n                },\n                \"overrides\": [],\n                \"values\": false\n              },\n              \"graphMode\": \"area\",\n              \"justifyMode\": \"auto\",\n              \"orientation\": \"horizontal\"\n            },\n            \"pluginVersion\": \"6.6.2\",\n            \"targets\": [\n              {\n                \"expr\": \"sum(dapr_runtime_component_init_total{app_id=~\\\"$dapr_app_id\\\", kubernetes_namespace=\\\"$namespace\\\"}) by (app_id)\",\n                \"legendFormat\": \"{{app_id}}\",\n                \"refId\": \"A\"\n              }\n            ],\n            \"timeFrom\": null,\n            \"timeShift\": null,\n            \"title\": \"Initialized components\",\n            \"type\": \"stat\"\n          },\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": \"Dapr\",\n            \"description\": \"This chart shows the number component initialization failures with the reasons.\",\n            \"fill\": 1,\n            \"fillGradient\": 0,\n            \"gridPos\": {\n              \"h\": 8,\n              \"w\": 12,\n              \"x\": 12,\n              \"y\": 9\n            },\n            \"hiddenSeries\": false,\n            \"id\": 58,\n            \"legend\": {\n              \"avg\": false,\n              \"current\": false,\n              \"max\": false,\n              \"min\": false,\n              \"rightSide\": false,\n              \"show\": true,\n              \"total\": false,\n              \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 1,\n            \"nullPointMode\": \"null\",\n            \"options\": {\n              \"dataLinks\": []\n            },\n            \"percentage\": false,\n            \"pointradius\": 2,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"sum(dapr_runtime_component_init_fail_total{app_id=~\\\"$dapr_app_id\\\", kubernetes_namespace=\\\"$namespace\\\"}) by (app_id, reason)\",\n                \"legendFormat\": \"{{app_id}} - {{reason}}\",\n                \"refId\": \"A\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"Component failures\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 0,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": false\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          }\n        ],\n        \"title\": \"Components\",\n        \"type\": \"row\"\n      },\n      {\n        \"collapsed\": true,\n        \"datasource\": \"Dapr\",\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 17\n        },\n        \"id\": 44,\n        \"panels\": [\n          {\n            \"aliasColors\": {},\n            \"bars\": false,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": \"Dapr\",\n            \"description\": \"This shows when Daprd reports host status to placement service. Non-Actor service reports host status in the beginning. Actor service reports host status to placement periodically. If there is a failure, virtual actors will not be distributed properly.\",\n            \"fill\": 1,\n            \"fillGradient\": 0,\n            \"gridPos\": {\n              \"h\": 7,\n              \"w\": 9,\n              \"x\": 0,\n              \"y\": 18\n            },\n            \"hiddenSeries\": false,\n            \"id\": 46,\n            \"legend\": {\n              \"avg\": false,\n              \"current\": false,\n              \"max\": false,\n              \"min\": false,\n              \"show\": true,\n              \"total\": false,\n              \"values\": false\n            },\n            \"lines\": true,\n            \"linewidth\": 1,\n            \"nullPointMode\": \"null\",\n            \"options\": {\n              \"dataLinks\": []\n            },\n            \"percentage\": false,\n            \"pointradius\": 2,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": true,\n            \"targets\": [\n              {\n                \"expr\": \"rate(dapr_runtime_actor_status_report_total{app_id=~\\\"$dapr_app_id\\\", kubernetes_namespace=\\\"$namespace\\\"}[5m])\",\n                \"legendFormat\": \"OK ({{app_id}})\",\n                \"refId\": \"A\"\n              },\n              {\n                \"expr\": \"rate(dapr_runtime_actor_status_report_fail_total{app_id=~\\\"$dapr_app_id\\\", kubernetes_namespace=\\\"$namespace\\\"}[5m])\",\n                \"legendFormat\": \"Error ({{app_id}})\",\n                \"refId\": \"B\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"Status report status\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 0,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"format\": \"none\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": false\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": false\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          },\n          {\n            \"cacheTimeout\": null,\n            \"datasource\": \"Dapr\",\n            \"description\": \"This shows the failures when Daprd reports host status to placement.\",\n            \"gridPos\": {\n              \"h\": 7,\n              \"w\": 5,\n              \"x\": 9,\n              \"y\": 18\n            },\n            \"id\": 59,\n            \"links\": [],\n            \"options\": {\n              \"displayMode\": \"basic\",\n              \"fieldOptions\": {\n                \"calcs\": [\n                  \"max\"\n                ],\n                \"defaults\": {\n                  \"mappings\": [],\n                  \"thresholds\": {\n                    \"mode\": \"absolute\",\n                    \"steps\": [\n                      {\n                        \"color\": \"green\",\n                        \"value\": null\n                      },\n                      {\n                        \"color\": \"red\",\n                        \"value\": 10\n                      }\n                    ]\n                  }\n                },\n                \"overrides\": [],\n                \"values\": false\n              },\n              \"orientation\": \"horizontal\",\n              \"showUnfilled\": true\n            },\n            \"pluginVersion\": \"6.7.3\",\n            \"targets\": [\n              {\n                \"expr\": \"sum(dapr_runtime_actor_status_report_fail_total{app_id=~\\\"$dapr_app_id\\\", kubernetes_namespace=\\\"$namespace\\\"}) by (app_id, operation)\",\n                \"legendFormat\": \"{{operation}} ({{app_id}})\",\n                \"refId\": \"A\"\n              }\n            ],\n            \"timeFrom\": null,\n            \"timeShift\": null,\n            \"title\": \"Report status error reasons\",\n            \"type\": \"bargauge\"\n          },\n          {\n            \"aliasColors\": {},\n            \"bars\": true,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": \"Dapr\",\n            \"description\": \"This shows the placement table update status based on the response from placement service. Daprd maintains the actor placement table in its memory. This table is periodically updated.\",\n            \"fill\": 1,\n            \"fillGradient\": 0,\n            \"gridPos\": {\n              \"h\": 7,\n              \"w\": 10,\n              \"x\": 14,\n              \"y\": 18\n            },\n            \"hiddenSeries\": false,\n            \"id\": 61,\n            \"legend\": {\n              \"avg\": false,\n              \"current\": false,\n              \"max\": false,\n              \"min\": false,\n              \"show\": true,\n              \"total\": false,\n              \"values\": false\n            },\n            \"lines\": false,\n            \"linewidth\": 1,\n            \"nullPointMode\": \"null\",\n            \"options\": {\n              \"dataLinks\": []\n            },\n            \"percentage\": false,\n            \"pointradius\": 1,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": true,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"sum(dapr_runtime_actor_table_operation_recv_total{app_id=~\\\"$dapr_app_id\\\", kubernetes_namespace=\\\"$namespace\\\"}) by (app_id, operation)\",\n                \"format\": \"time_series\",\n                \"intervalFactor\": 3,\n                \"legendFormat\": \"{{operation}} ({{app_id}})\",\n                \"refId\": \"A\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"Placement table operation\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 0,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          },\n          {\n            \"aliasColors\": {},\n            \"bars\": true,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": \"Dapr\",\n            \"description\": \"This chart shows how many actors are deactivated.\",\n            \"fill\": 1,\n            \"fillGradient\": 0,\n            \"gridPos\": {\n              \"h\": 7,\n              \"w\": 9,\n              \"x\": 0,\n              \"y\": 25\n            },\n            \"hiddenSeries\": false,\n            \"id\": 65,\n            \"legend\": {\n              \"avg\": false,\n              \"current\": false,\n              \"max\": false,\n              \"min\": false,\n              \"show\": true,\n              \"total\": false,\n              \"values\": false\n            },\n            \"lines\": false,\n            \"linewidth\": 1,\n            \"nullPointMode\": \"null\",\n            \"options\": {\n              \"dataLinks\": []\n            },\n            \"percentage\": false,\n            \"pointradius\": 1,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": true,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"dapr_runtime_actor_deactivated_total{app_id=~\\\"$dapr_app_id\\\", kubernetes_namespace=\\\"$namespace\\\"}\",\n                \"legendFormat\": \"OK {{actor_type}}\",\n                \"refId\": \"A\"\n              },\n              {\n                \"expr\": \"dapr_runtime_actor_deactivated_failed_total{app_id=~\\\"$dapr_app_id\\\", kubernetes_namespace=\\\"$namespace\\\"}\",\n                \"legendFormat\": \"Error {{actor_type}}\",\n                \"refId\": \"C\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"Actor Deactivation\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 0,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          },\n          {\n            \"aliasColors\": {},\n            \"bars\": true,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": \"Dapr\",\n            \"description\": \"This shows when actor is rebalanced. This operation can make the activated actors deactivated and move actor to the other hosts.\",\n            \"fill\": 1,\n            \"fillGradient\": 0,\n            \"gridPos\": {\n              \"h\": 7,\n              \"w\": 7,\n              \"x\": 9,\n              \"y\": 25\n            },\n            \"hiddenSeries\": false,\n            \"id\": 63,\n            \"legend\": {\n              \"avg\": false,\n              \"current\": false,\n              \"max\": false,\n              \"min\": false,\n              \"show\": true,\n              \"total\": false,\n              \"values\": false\n            },\n            \"lines\": false,\n            \"linewidth\": 1,\n            \"nullPointMode\": \"null\",\n            \"options\": {\n              \"dataLinks\": []\n            },\n            \"percentage\": false,\n            \"pointradius\": 1,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": true,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"dapr_runtime_actor_rebalanced_total{app_id=~\\\"$dapr_app_id\\\", kubernetes_namespace=\\\"$namespace\\\"}\",\n                \"intervalFactor\": 1,\n                \"legendFormat\": \"{{app_id}}\",\n                \"refId\": \"B\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"Actor table rebalancing\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 0,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": true\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          }\n        ],\n        \"title\": \"Actor\",\n        \"type\": \"row\"\n      },\n      {\n        \"collapsed\": true,\n        \"datasource\": \"Dapr\",\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 18\n        },\n        \"id\": 50,\n        \"panels\": [\n          {\n            \"aliasColors\": {},\n            \"bars\": true,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": \"Dapr\",\n            \"description\": \"This chart  shows when mTLS is initialized, which means root cert is loaded and workload cert is issued from sentry, when runtime starts.\",\n            \"fill\": 1,\n            \"fillGradient\": 0,\n            \"gridPos\": {\n              \"h\": 8,\n              \"w\": 7,\n              \"x\": 0,\n              \"y\": 11\n            },\n            \"hiddenSeries\": false,\n            \"id\": 48,\n            \"legend\": {\n              \"avg\": false,\n              \"current\": false,\n              \"max\": false,\n              \"min\": false,\n              \"show\": true,\n              \"total\": false,\n              \"values\": false\n            },\n            \"lines\": false,\n            \"linewidth\": 1,\n            \"nullPointMode\": \"null\",\n            \"options\": {\n              \"dataLinks\": []\n            },\n            \"percentage\": false,\n            \"pointradius\": 2,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"rate(dapr_runtime_mtls_init_total{app_id=~\\\"$dapr_app_id\\\", kubernetes_namespace=\\\"$namespace\\\"}[5m])\",\n                \"intervalFactor\": 2,\n                \"legendFormat\": \"OK {{app_id}}\",\n                \"refId\": \"A\"\n              },\n              {\n                \"expr\": \"rate(dapr_runtime_mtls_init_fail_total{app_id=~\\\"$dapr_app_id\\\", kubernetes_namespace=\\\"$namespace\\\"}[5m])\",\n                \"intervalFactor\": 2,\n                \"legendFormat\": \"Error {{app_id}}\",\n                \"refId\": \"B\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"mTLS initialization status\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 0,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": false\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": false\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          },\n          {\n            \"cacheTimeout\": null,\n            \"datasource\": \"Dapr\",\n            \"gridPos\": {\n              \"h\": 8,\n              \"w\": 5,\n              \"x\": 7,\n              \"y\": 11\n            },\n            \"id\": 67,\n            \"links\": [],\n            \"options\": {\n              \"displayMode\": \"basic\",\n              \"fieldOptions\": {\n                \"calcs\": [\n                  \"mean\"\n                ],\n                \"defaults\": {\n                  \"mappings\": [],\n                  \"thresholds\": {\n                    \"mode\": \"absolute\",\n                    \"steps\": [\n                      {\n                        \"color\": \"green\",\n                        \"value\": null\n                      },\n                      {\n                        \"color\": \"red\",\n                        \"value\": 10\n                      }\n                    ]\n                  }\n                },\n                \"overrides\": [],\n                \"values\": false\n              },\n              \"orientation\": \"horizontal\",\n              \"showUnfilled\": true\n            },\n            \"pluginVersion\": \"6.6.2\",\n            \"targets\": [\n              {\n                \"expr\": \"sum(dapr_runtime_mtls_init_fail_total{app_id=~\\\"$dapr_app_id\\\", kubernetes_namespace=\\\"$namespace\\\"}) by (app_id, reason)\",\n                \"legendFormat\": \"{{reason}} ({{app_id}})\",\n                \"refId\": \"A\"\n              }\n            ],\n            \"timeFrom\": null,\n            \"timeShift\": null,\n            \"title\": \"mTLS Initialization Errors\",\n            \"type\": \"bargauge\"\n          },\n          {\n            \"aliasColors\": {},\n            \"bars\": true,\n            \"dashLength\": 10,\n            \"dashes\": false,\n            \"datasource\": \"Dapr\",\n            \"description\": \"This chart shows when workload certificate is rotated.\",\n            \"fill\": 1,\n            \"fillGradient\": 0,\n            \"gridPos\": {\n              \"h\": 8,\n              \"w\": 7,\n              \"x\": 12,\n              \"y\": 11\n            },\n            \"hiddenSeries\": false,\n            \"id\": 66,\n            \"legend\": {\n              \"avg\": false,\n              \"current\": false,\n              \"max\": false,\n              \"min\": false,\n              \"show\": true,\n              \"total\": false,\n              \"values\": false\n            },\n            \"lines\": false,\n            \"linewidth\": 1,\n            \"nullPointMode\": \"null\",\n            \"options\": {\n              \"dataLinks\": []\n            },\n            \"percentage\": false,\n            \"pluginVersion\": \"6.6.2\",\n            \"pointradius\": 2,\n            \"points\": false,\n            \"renderer\": \"flot\",\n            \"seriesOverrides\": [],\n            \"spaceLength\": 10,\n            \"stack\": false,\n            \"steppedLine\": false,\n            \"targets\": [\n              {\n                \"expr\": \"rate(dapr_runtime_mtls_workload_cert_rotated_total{app_id=~\\\"$dapr_app_id\\\", kubernetes_namespace=\\\"$namespace\\\"}[5m])\",\n                \"format\": \"time_series\",\n                \"instant\": false,\n                \"intervalFactor\": 2,\n                \"legendFormat\": \"OK {{app_id}}\",\n                \"refId\": \"A\"\n              },\n              {\n                \"expr\": \"rate(dapr_runtime_mtls_workload_cert_rotated_fail_total{app_id=~\\\"$dapr_app_id\\\", kubernetes_namespace=\\\"$namespace\\\"}[5m])\",\n                \"intervalFactor\": 2,\n                \"legendFormat\": \"Error {{app_id}}\",\n                \"refId\": \"B\"\n              }\n            ],\n            \"thresholds\": [],\n            \"timeFrom\": null,\n            \"timeRegions\": [],\n            \"timeShift\": null,\n            \"title\": \"Workload certificate rotation status\",\n            \"tooltip\": {\n              \"shared\": true,\n              \"sort\": 0,\n              \"value_type\": \"individual\"\n            },\n            \"type\": \"graph\",\n            \"xaxis\": {\n              \"buckets\": null,\n              \"mode\": \"time\",\n              \"name\": null,\n              \"show\": true,\n              \"values\": []\n            },\n            \"yaxes\": [\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": false\n              },\n              {\n                \"format\": \"short\",\n                \"label\": null,\n                \"logBase\": 1,\n                \"max\": null,\n                \"min\": null,\n                \"show\": false\n              }\n            ],\n            \"yaxis\": {\n              \"align\": false,\n              \"alignLevel\": null\n            }\n          },\n          {\n            \"cacheTimeout\": null,\n            \"datasource\": \"Dapr\",\n            \"gridPos\": {\n              \"h\": 8,\n              \"w\": 5,\n              \"x\": 19,\n              \"y\": 11\n            },\n            \"id\": 68,\n            \"links\": [],\n            \"options\": {\n              \"displayMode\": \"basic\",\n              \"fieldOptions\": {\n                \"calcs\": [\n                  \"mean\"\n                ],\n                \"defaults\": {\n                  \"mappings\": [],\n                  \"thresholds\": {\n                    \"mode\": \"absolute\",\n                    \"steps\": [\n                      {\n                        \"color\": \"green\",\n                        \"value\": null\n                      },\n                      {\n                        \"color\": \"red\",\n                        \"value\": 10\n                      }\n                    ]\n                  }\n                },\n                \"overrides\": [],\n                \"values\": false\n              },\n              \"orientation\": \"horizontal\",\n              \"showUnfilled\": true\n            },\n            \"pluginVersion\": \"6.6.2\",\n            \"targets\": [\n              {\n                \"expr\": \"sum(dapr_runtime_mtls_workload_cert_rotated_fail_total{app_id=~\\\"$dapr_app_id\\\", kubernetes_namespace=\\\"$namespace\\\"}) by (app_id, reason)\",\n                \"legendFormat\": \"{{reason}} ({{app_id}})\",\n                \"refId\": \"A\"\n              }\n            ],\n            \"timeFrom\": null,\n            \"timeShift\": null,\n            \"title\": \"Cert rotation error reasons\",\n            \"type\": \"bargauge\"\n          }\n        ],\n        \"title\": \"Security\",\n        \"type\": \"row\"\n      }\n    ],\n    \"refresh\": \"10s\",\n    \"schemaVersion\": 22,\n    \"style\": \"dark\",\n    \"tags\": [ \"autosetup\" ],\n    \"templating\": {\n      \"list\": [\n        {\n          \"allValue\": null,\n          \"current\": {},\n          \"datasource\": \"Dapr\",\n          \"definition\": \"label_values(dapr_runtime_component_loaded,kubernetes_namespace)\",\n          \"hide\": 0,\n          \"includeAll\": false,\n          \"index\": -1,\n          \"label\": \"NAMESPACE\",\n          \"multi\": false,\n          \"name\": \"namespace\",\n          \"options\": [],\n          \"query\": \"label_values(dapr_runtime_component_loaded,kubernetes_namespace)\",\n          \"refresh\": 1,\n          \"regex\": \"\",\n          \"skipUrlSync\": false,\n          \"sort\": 0,\n          \"tagValuesQuery\": \"\",\n          \"tags\": [],\n          \"tagsQuery\": \"\",\n          \"type\": \"query\",\n          \"useTags\": false\n        },\n        {\n          \"allValue\": null,\n          \"current\": {},\n          \"datasource\": \"Dapr\",\n          \"definition\": \"label_values(dapr_runtime_component_loaded,app_id)\",\n          \"hide\": 0,\n          \"includeAll\": false,\n          \"index\": -1,\n          \"label\": \"APPID\",\n          \"multi\": true,\n          \"name\": \"dapr_app_id\",\n          \"options\": [],\n          \"query\": \"label_values(dapr_runtime_component_loaded,app_id)\",\n          \"refresh\": 1,\n          \"regex\": \"\",\n          \"skipUrlSync\": false,\n          \"sort\": 1,\n          \"tagValuesQuery\": \"\",\n          \"tags\": [],\n          \"tagsQuery\": \"\",\n          \"type\": \"query\",\n          \"useTags\": false\n        }\n      ]\n    },\n    \"time\": {\n      \"from\": \"now-3h\",\n      \"to\": \"now\"\n    },\n    \"timepicker\": {\n      \"refresh_intervals\": [\n        \"5s\",\n        \"10s\",\n        \"30s\",\n        \"1m\",\n        \"5m\",\n        \"15m\",\n        \"30m\",\n        \"1h\",\n        \"2h\",\n        \"1d\"\n      ]\n    },\n    \"timezone\": \"browser\",\n    \"title\": \"Dapr Sidecar Dashboard\",\n    \"variables\": {\n      \"list\": []\n    },\n    \"version\": 0\n  },\n  \"overwrite\": true\n}"
  },
  {
    "path": "setup/config/system-services-dashboard.json",
    "content": "{ \n  \"dashboard\": {\n    \"annotations\": {\n      \"list\": [\n        {\n          \"builtIn\": 1,\n          \"datasource\": \"-- Grafana --\",\n          \"enable\": true,\n          \"hide\": true,\n          \"iconColor\": \"rgba(0, 211, 255, 1)\",\n          \"name\": \"Annotations & Alerts\",\n          \"type\": \"dashboard\"\n        }\n      ]\n    },\n    \"editable\": true,\n    \"gnetId\": null,\n    \"graphTooltip\": 0,\n    \"id\": null,\n    \"links\": [],\n    \"panels\": [\n      {\n        \"collapsed\": false,\n        \"datasource\": null,\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 0\n        },\n        \"id\": 18,\n        \"panels\": [],\n        \"title\": \"Health & Resource\",\n        \"type\": \"row\"\n      },\n      {\n        \"cacheTimeout\": null,\n        \"datasource\": \"Dapr\",\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 4,\n          \"x\": 0,\n          \"y\": 1\n        },\n        \"id\": 20,\n        \"links\": [],\n        \"options\": {\n          \"colorMode\": \"value\",\n          \"fieldOptions\": {\n            \"calcs\": [\n              \"last\"\n            ],\n            \"defaults\": {\n              \"decimals\": 1,\n              \"mappings\": [\n                {\n                  \"id\": 0,\n                  \"op\": \"=\",\n                  \"text\": \"N/A\",\n                  \"type\": 1,\n                  \"value\": \"null\"\n                }\n              ],\n              \"nullValueMode\": \"connected\",\n              \"thresholds\": {\n                \"mode\": \"absolute\",\n                \"steps\": [\n                  {\n                    \"color\": \"red\",\n                    \"value\": null\n                  },\n                  {\n                    \"color\": \"green\",\n                    \"value\": 600\n                  }\n                ]\n              },\n              \"unit\": \"s\"\n            },\n            \"overrides\": [],\n            \"values\": false\n          },\n          \"graphMode\": \"area\",\n          \"justifyMode\": \"auto\",\n          \"orientation\": \"horizontal\"\n        },\n        \"pluginVersion\": \"6.6.2\",\n        \"targets\": [\n          {\n            \"expr\": \"time() - max(process_start_time_seconds{app=~\\\"dapr-sentry|dapr-placement|dapr-sidecar-injector|dapr-operator\\\"}) by (app)\",\n            \"intervalFactor\": 2,\n            \"legendFormat\": \"{{app}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"timeFrom\": null,\n        \"timeShift\": null,\n        \"title\": \"Uptime\",\n        \"type\": \"stat\"\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Dapr\",\n        \"description\": \"This shows total amount of kernel and user CPU usage time.\",\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 7,\n          \"x\": 4,\n          \"y\": 1\n        },\n        \"hiddenSeries\": false,\n        \"id\": 22,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"dataLinks\": []\n        },\n        \"percentage\": false,\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(rate(container_cpu_usage_seconds_total{pod=~\\\"(dapr-sentry|dapr-sidecar-injector|dapr-placement|dapr-operator).*\\\"}[5m])) by (pod)\",\n            \"legendFormat\": \"{{pod}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"Total CPU usage (kernel and user)\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"s\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Dapr\",\n        \"description\": \"The amount of memory that belongs specifically to that process in bytes. This excludes swapped out memory pages.\",\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 7,\n          \"x\": 11,\n          \"y\": 1\n        },\n        \"hiddenSeries\": false,\n        \"id\": 24,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"dataLinks\": []\n        },\n        \"percentage\": false,\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"max(process_resident_memory_bytes{app=~\\\"(dapr-sentry|dapr-sidecar-injector|dapr-placement|dapr-operator)\\\"}) by (app)\",\n            \"legendFormat\": \"{{app}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"Memory usage in bytes\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"decbytes\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Dapr\",\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 6,\n          \"x\": 18,\n          \"y\": 1\n        },\n        \"hiddenSeries\": false,\n        \"id\": 26,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"dataLinks\": []\n        },\n        \"percentage\": false,\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"max(go_goroutines{app=~\\\"(dapr-sentry|dapr-sidecar-injector|dapr-placement|dapr-operator)\\\"}) by (app)\",\n            \"legendFormat\": \"{{app}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"Number of GO routines\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"collapsed\": false,\n        \"datasource\": null,\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 9\n        },\n        \"id\": 14,\n        \"panels\": [],\n        \"title\": \"Operator\",\n        \"type\": \"row\"\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": null,\n        \"description\": \"The total number of services created.\",\n        \"fill\": 10,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 10\n        },\n        \"hiddenSeries\": false,\n        \"id\": 6,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"dataLinks\": []\n        },\n        \"percentage\": false,\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": true,\n        \"steppedLine\": true,\n        \"targets\": [\n          {\n            \"expr\": \"count(dapr_operator_service_created_total) by (app_id)\",\n            \"legendFormat\": \"{{app_id}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"# Services Created\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 1,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": false\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Dapr\",\n        \"description\": \"The total number of services deleted.\",\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 10\n        },\n        \"hiddenSeries\": false,\n        \"id\": 4,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"dataLinks\": []\n        },\n        \"percentage\": false,\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"dapr_operator_service_deleted_total\",\n            \"legendFormat\": \"{{app_id}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"# Services Deleted\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"collapsed\": false,\n        \"datasource\": null,\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 18\n        },\n        \"id\": 12,\n        \"panels\": [],\n        \"title\": \"Sidecar Injector\",\n        \"type\": \"row\"\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": null,\n        \"description\": \"The total number of sidecar injection requests.\",\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 9,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 19\n        },\n        \"hiddenSeries\": false,\n        \"id\": 8,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"dataLinks\": []\n        },\n        \"percentage\": false,\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"dapr_injector_sidecar_injection_requests_total\",\n            \"legendFormat\": \"sidecars requests\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"# sidecar injection requests\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Dapr\",\n        \"description\": \"The total number of successful sidecar injection requests.\",\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 9,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 19\n        },\n        \"hiddenSeries\": false,\n        \"id\": 10,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"dataLinks\": []\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"6.6.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"dapr_injector_sidecar_injection_succeeded_total\",\n            \"legendFormat\": \"{{app_id}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"# successful sidecar injected\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"collapsed\": false,\n        \"datasource\": null,\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 28\n        },\n        \"id\": 42,\n        \"panels\": [],\n        \"title\": \"CA Sentry\",\n        \"type\": \"row\"\n      },\n      {\n        \"cacheTimeout\": null,\n        \"colorBackground\": false,\n        \"colorValue\": true,\n        \"colors\": [\n          \"#F2495C\",\n          \"#FADE2A\",\n          \"#73BF69\"\n        ],\n        \"datasource\": null,\n        \"decimals\": null,\n        \"description\": \"\",\n        \"format\": \"s\",\n        \"gauge\": {\n          \"maxValue\": 100,\n          \"minValue\": 0,\n          \"show\": false,\n          \"thresholdLabels\": false,\n          \"thresholdMarkers\": true\n        },\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 3,\n          \"x\": 0,\n          \"y\": 29\n        },\n        \"id\": 44,\n        \"interval\": null,\n        \"links\": [],\n        \"mappingType\": 1,\n        \"mappingTypes\": [\n          {\n            \"name\": \"value to text\",\n            \"value\": 1\n          },\n          {\n            \"name\": \"range to text\",\n            \"value\": 2\n          }\n        ],\n        \"maxDataPoints\": 100,\n        \"nullPointMode\": \"connected\",\n        \"nullText\": null,\n        \"options\": {},\n        \"pluginVersion\": \"6.6.2\",\n        \"postfix\": \" left\",\n        \"postfixFontSize\": \"50%\",\n        \"prefix\": \"\",\n        \"prefixFontSize\": \"50%\",\n        \"rangeMaps\": [\n          {\n            \"from\": \"null\",\n            \"text\": \"N/A\",\n            \"to\": \"null\"\n          }\n        ],\n        \"sparkline\": {\n          \"fillColor\": \"rgba(31, 118, 189, 0.18)\",\n          \"full\": false,\n          \"lineColor\": \"rgb(31, 120, 193)\",\n          \"show\": true,\n          \"ymax\": null,\n          \"ymin\": null\n        },\n        \"tableColumn\": \"\",\n        \"targets\": [\n          {\n            \"expr\": \"min(dapr_sentry_issuercert_expiry_timestamp) - time()\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": \"2628000, 5256000\",\n        \"timeFrom\": \"1m\",\n        \"timeShift\": null,\n        \"title\": \"Root/Issuer cert expiry\",\n        \"type\": \"singlestat\",\n        \"valueFontSize\": \"80%\",\n        \"valueMaps\": [\n          {\n            \"op\": \"=\",\n            \"text\": \"N/A\",\n            \"value\": \"null\"\n          }\n        ],\n        \"valueName\": \"current\"\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": null,\n        \"description\": \"Certificate Signing Request ( CSR ) from Dapr runtime\",\n        \"fill\": 0,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 9,\n          \"x\": 3,\n          \"y\": 29\n        },\n        \"hiddenSeries\": false,\n        \"id\": 34,\n        \"legend\": {\n          \"alignAsTable\": true,\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"rightSide\": true,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 2,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"dataLinks\": []\n        },\n        \"percentage\": false,\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [\n          {\n            \"alias\": \"CSR Requests\",\n            \"color\": \"rgb(60, 33, 166)\",\n            \"dashes\": true\n          },\n          {\n            \"alias\": \"CSR Success\",\n            \"color\": \"#73BF69\"\n          },\n          {\n            \"alias\": \"CSR Failure\",\n            \"color\": \"#F2495C\"\n          }\n        ],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(dapr_sentry_cert_sign_request_received_total{app=\\\"dapr-sentry\\\"})\",\n            \"legendFormat\": \"CSR Requests\",\n            \"refId\": \"A\"\n          },\n          {\n            \"expr\": \"sum(dapr_sentry_cert_sign_success_total{app=\\\"dapr-sentry\\\"})\",\n            \"instant\": false,\n            \"legendFormat\": \"CSR Success\",\n            \"refId\": \"B\"\n          },\n          {\n            \"expr\": \"sum(dapr_sentry_cert_sign_failure_total{app=\\\"dapr-sentry\\\"})\",\n            \"instant\": false,\n            \"legendFormat\": \"CSR Failure\",\n            \"refId\": \"C\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"Certificate Signing Requests (CSR) from Daprd\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": false\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": null,\n        \"description\": \"This chart shows the failure reason of Certificate Sign Request.\",\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 29\n        },\n        \"hiddenSeries\": false,\n        \"id\": 38,\n        \"legend\": {\n          \"alignAsTable\": false,\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"rightSide\": true,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"dataLinks\": []\n        },\n        \"percentage\": false,\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(dapr_sentry_cert_sign_failure_total{app=\\\"dapr-sentry\\\"}) by (reason)\",\n            \"legendFormat\": \"{{reason}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"CSR Failures\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": false\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"cacheTimeout\": null,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": \"Dapr\",\n        \"description\": \"This will be counted when issuer cert and key are changed.\",\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 36\n        },\n        \"hiddenSeries\": false,\n        \"id\": 36,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": false,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"links\": [],\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"dataLinks\": []\n        },\n        \"percentage\": false,\n        \"pluginVersion\": \"6.6.2\",\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(dapr_sentry_issuercert_changed_total{app=\\\"dapr-sentry\\\"})\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"Issuer cert and key changed total\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"none\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": null,\n        \"description\": \"This chart shows the reason of gRPC server TLS certificate issuance failures.\",\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 7,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 36\n        },\n        \"hiddenSeries\": false,\n        \"id\": 40,\n        \"legend\": {\n          \"alignAsTable\": false,\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"rightSide\": true,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"dataLinks\": []\n        },\n        \"percentage\": false,\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"sum(dapr_sentry_servercert_issue_failed_total{app=\\\"dapr-sentry\\\"}) by (reason)\",\n            \"legendFormat\": \"{{reason}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"Server TLS certificate issuance failures\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": false\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"collapsed\": false,\n        \"datasource\": null,\n        \"gridPos\": {\n          \"h\": 1,\n          \"w\": 24,\n          \"x\": 0,\n          \"y\": 43\n        },\n        \"id\": 16,\n        \"panels\": [],\n        \"title\": \"Placement\",\n        \"type\": \"row\"\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": null,\n        \"description\": \"The total number of replicas connected to placement service.\",\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 44\n        },\n        \"hiddenSeries\": false,\n        \"id\": 28,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"dataLinks\": []\n        },\n        \"percentage\": false,\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"dapr_placement_hosts_total\",\n            \"legendFormat\": \"hosts\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"# total replicas\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": null,\n        \"description\": \"The total number of replicas which are not hosting actors.\",\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 12,\n          \"y\": 44\n        },\n        \"hiddenSeries\": false,\n        \"id\": 30,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"dataLinks\": []\n        },\n        \"percentage\": false,\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"dapr_placement_nonactorhosts_total\",\n            \"legendFormat\": \"{{kubernetes_pod_name}}\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"# replicas not hosting actors\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      },\n      {\n        \"aliasColors\": {},\n        \"bars\": false,\n        \"dashLength\": 10,\n        \"dashes\": false,\n        \"datasource\": null,\n        \"description\": \"The total number of actor types registered with Dapr runtime.\",\n        \"fill\": 1,\n        \"fillGradient\": 0,\n        \"gridPos\": {\n          \"h\": 8,\n          \"w\": 12,\n          \"x\": 0,\n          \"y\": 52\n        },\n        \"hiddenSeries\": false,\n        \"id\": 32,\n        \"legend\": {\n          \"avg\": false,\n          \"current\": false,\n          \"max\": false,\n          \"min\": false,\n          \"show\": true,\n          \"total\": false,\n          \"values\": false\n        },\n        \"lines\": true,\n        \"linewidth\": 1,\n        \"nullPointMode\": \"null\",\n        \"options\": {\n          \"dataLinks\": []\n        },\n        \"percentage\": false,\n        \"pointradius\": 2,\n        \"points\": false,\n        \"renderer\": \"flot\",\n        \"seriesOverrides\": [],\n        \"spaceLength\": 10,\n        \"stack\": false,\n        \"steppedLine\": false,\n        \"targets\": [\n          {\n            \"expr\": \"dapr_placement_actortypes_total\",\n            \"legendFormat\": \"actor types\",\n            \"refId\": \"A\"\n          }\n        ],\n        \"thresholds\": [],\n        \"timeFrom\": null,\n        \"timeRegions\": [],\n        \"timeShift\": null,\n        \"title\": \"# actor types\",\n        \"tooltip\": {\n          \"shared\": true,\n          \"sort\": 0,\n          \"value_type\": \"individual\"\n        },\n        \"type\": \"graph\",\n        \"xaxis\": {\n          \"buckets\": null,\n          \"mode\": \"time\",\n          \"name\": null,\n          \"show\": true,\n          \"values\": []\n        },\n        \"yaxes\": [\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          },\n          {\n            \"format\": \"short\",\n            \"label\": null,\n            \"logBase\": 1,\n            \"max\": null,\n            \"min\": null,\n            \"show\": true\n          }\n        ],\n        \"yaxis\": {\n          \"align\": false,\n          \"alignLevel\": null\n        }\n      }\n    ],\n    \"refresh\": \"15m\",\n    \"schemaVersion\": 22,\n    \"style\": \"dark\",\n    \"tags\": [ \"autosetup\" ],\n    \"templating\": {\n      \"list\": []\n    },\n    \"time\": {\n      \"from\": \"now-1h\",\n      \"to\": \"now\"\n    },\n    \"timepicker\": {\n      \"refresh_intervals\": [\n        \"5s\",\n        \"10s\",\n        \"30s\",\n        \"1m\",\n        \"5m\",\n        \"15m\",\n        \"30m\",\n        \"1h\",\n        \"2h\",\n        \"1d\"\n      ]\n    },\n    \"timezone\": \"browser\",\n    \"title\": \"Dapr System Services Dashboard\",\n    \"version\": 0\n  },\n\"overwrite\": true\n}"
  },
  {
    "path": "setup/config/zipkin.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: zipkin\n  namespace: dapr-monitoring\n  labels:\n    app: zipkin\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: zipkin\n  template:\n    metadata:\n      labels:\n        app: zipkin\n    spec:\n      containers:\n      - name: zipkin\n        image: openzipkin/zipkin\n        ports:\n        - containerPort: 9411\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: zipkin\n  namespace: dapr-monitoring\n  labels:\n    app: zipkin\nspec:\n  selector:\n    app: zipkin\n  ports:\n  - protocol: TCP\n    port: 9411\n    targetPort: 9411\n  type: ClusterIP"
  },
  {
    "path": "setup/gke/Makefile",
    "content": "CLUSTER_NAME     ?=demo\nCLUSTER_ZONE     ?=us-west1-a\nCLUSTER_VERSION  ?=1.17.13-gke.600\nNODE_COUNT       ?=2\nNODE_TYPE        ?=n2-standard-4\n\n.PHONY: all\nall:\n\t@echo === ACTIVE CONFIGURATION ===\n\t@echo \"CLUSTER_NAME:     ${CLUSTER_NAME}\"\n\t@echo \"CLUSTER_ZONE:     ${CLUSTER_ZONE}\"\n\t@echo \"CLUSTER_VERSION:  ${CLUSTER_VERSION}\"\n\t@echo \"NODE_COUNT:       ${NODE_COUNT}\"\n\t@echo \"NODE_TYPE:        ${NODE_TYPE}\"\n\t@echo \n\t@echo \"Export these as environment variables to change their values\"\n\t@echo \n\n\n.PHONY: setup\nsetup: ## Enables GKE APIs\n\tgcloud services enable \\\n\t\tcloudapis.googleapis.com \\\n\t\tcontainer.googleapis.com \\\n\t\tcontainerregistry.googleapis.com\n\n.PHONY: cluster-list\ncluster-list: ## List GKE clusters\n\tgcloud container clusters list\n\n.PHONY: version-list\nversion-list: ## List Kubernetes versions supported in GKE\n\tgcloud container get-server-config\n\n.PHONY: cluster\ncluster: setup ## Create GKE cluster\n\t# Create cluster\n\tgcloud container clusters create $(CLUSTER_NAME) \\\n\t  --addons=HorizontalPodAutoscaling,HttpLoadBalancing \\\n\t  --zone $(CLUSTER_ZONE) \\\n\t  --node-locations $(CLUSTER_ZONE) \\\n\t  --cluster-version $(CLUSTER_VERSION) \\\n\t  --machine-type $(NODE_TYPE) \\\n\t  --num-nodes $(NODE_COUNT) \\\n\t  --enable-ip-alias \\\n\t  --enable-stackdriver-kubernetes \\\n\t  --enable-autorepair \\\n\t  --scopes cloud-platform\n\t# Get cluster credentials \n\tgcloud container clusters get-credentials $(CLUSTER_NAME) \\\n\t  --zone $(CLUSTER_ZONE)\n\t# Create cluster binding for current user\n\tkubectl create clusterrolebinding cluster-admin-binding \\\n\t  --clusterrole=cluster-admin \\\n\t  --user=$(gcloud config get-value core/account)\n\n.PHONY: cluster-down\ncluster-down: cluster-list ## Delete previously created GKE cluster (make clusterdown CLUSTER_NAME=demo)\n\tgcloud container clusters delete $(CLUSTER_NAME) \\\n\t  --zone $(CLUSTER_ZONE)\n\n.PHONY: help\nhelp: ## Display available commands\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \\\n\t\t'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n"
  },
  {
    "path": "setup/gke/README.md",
    "content": "# GKE Cluster Setup\n\nThe following parameters can be used to configure your deployment. Define these as environment variables to set or override the default value:\n\n```shell\nCLUSTER_NAME     # default: demo\nCLUSTER_ZONE     # default: us-west1-a\nCLUSTER_VERSION  # default: 1.17.13-gke.600\nNODE_COUNT       # default: 2\nNODE_COUNT_MIN   # default: 1\nNODE_COUNT_MAX   # default: 5\nNODE_TYPE        # default: n2-standard-4\n```\n\n```shell\ngcloud config set project <your project ID>\ngcloud config set compute/region <your preferred region>\ngcloud config set compute/zone <your preferred zone>\n```\n\n## Usage\n\nStart by navigating to the [setup/gke](./setup/gke) directory\n\n> Run `make` by itself to see the active configuration \n\n* `make cluster` to create a cluster on GKE (make cluster CLUSTER_NAME=demo)\n* `make cluster-list` to list your AKS clusters\n* `make version-list` to list Kubernetes versions supported on AKS\n\nOnce cluster is created, you can follow [these instructions](../) to configure Dapr.\n\n## Cleanup\n\nTo lists previously created clusters run \n\n```shell\nmake cluster-list\n```\n\nTo delete any of the previously created clusters run \n\n> yes, there will be a prompt to confirm before deleting\n\n```shell\nmake cluster-down CLUSTER_NAME=name\n```\n\n## Help\n\nTo find the list of all the commands with their short descriptions run: \n\n```shell\nmake help\n```\n\n## Disclaimer\n\nThis is my personal project and it does not represent my employer. While I do my best to ensure that everything works, I take no responsibility for issues caused by this code.\n\n## License\n\nThis software is released under the [MIT](../../LICENSE)"
  },
  {
    "path": "setup/kubectl-install-dapr",
    "content": "#!/bin/bash\n\nset -o errexit\nset -o pipefail\nset -o nounset\n\ndeclare -r DAPR_RELEASE=${DAPR_RELEASE:-\"1.0.0-rc.1\"}\ndeclare -r DAPR_HA=${DAPR_HA:-\"false\"}\ndeclare -r DAPR_LOG_AS_JSON=${DAPR_LOG_AS_JSON:-\"false\"}\ndeclare -r DAPR_CONFIG=${DAPR_CONFIG:-\"https://raw.githubusercontent.com/mchmarny/dapr-demos/master/setup/config\"}\n\necho \"Configuration:\"\necho \"DAPR_RELEASE=${DAPR_RELEASE}\"\necho \"DAPR_HA=${DAPR_HA}\"\necho \"DAPR_LOG_AS_JSON=${DAPR_LOG_AS_JSON}\"\necho \"DAPR_CONFIG=${DAPR_CONFIG}\"\n\nif pgrep kubectl &> /dev/null ; then pkill kubectl -9 ; fi\n\n# Installing Dapr...\nhelm repo add dapr https://dapr.github.io/helm-charts/\nhelm repo update\nkubectl create ns dapr-system\nhelm install dapr dapr/dapr -n dapr-system \\\n\t--version $DAPR_RELEASE \\\n\t--set global.logAsJson=$DAPR_LOG_AS_JSON \\\n\t--set global.ha.enabled=$DAPR_HA\n# Wait for everything to finish installing \nkubectl rollout status deployment/dapr-operator -n dapr-system\nkubectl rollout status deployment/dapr-dashboard -n dapr-system\nkubectl rollout status deployment/dapr-sentry -n dapr-system\nkubectl rollout status deployment/dapr-sidecar-injector -n dapr-system\n\n# Installing Keda\nhelm repo add kedacore https://kedacore.github.io/charts\nhelm repo update\nkubectl create ns keda\nhelm install keda kedacore/keda -n keda --set logLevel=debug\n# Wait for everything to finish installing \nkubectl rollout status deployment/keda-operator -n keda\nkubectl rollout status deployment/keda-operator-metrics-apiserver -n keda\n\n# Installing observabiliity...\nhelm repo add grafana https://grafana.github.io/helm-charts\nhelm repo add elastic https://helm.elastic.co\nhelm repo add prometheus https://prometheus-community.github.io/helm-charts\nhelm repo update\nkubectl create ns dapr-monitoring\nkubectl apply \\\n\t-f \"${DAPR_CONFIG}/fluentd-config.yaml\" \\\n\t-f \"${DAPR_CONFIG}/fluentd.yaml\"\nkubectl apply -f \"${DAPR_CONFIG}/zipkin.yaml\" -n dapr-monitoring\nhelm install elasticsearch elastic/elasticsearch -n dapr-monitoring\nhelm install dapr-prom prometheus/prometheus -n dapr-monitoring\nhelm install grafana grafana/grafana -n dapr-monitoring \\\n\t--set persistence.enabled=true \\\n\t--set persistence.accessModes={ReadWriteOnce} \\\n\t--set persistence.size=8Gi\nhelm install kibana elastic/kibana -n dapr-monitoring\n\n# Wait for everything to be ready...\nkubectl rollout status deployment/dapr-prom-kube-state-metrics -n dapr-monitoring\nkubectl rollout status deployment/dapr-prom-prometheus-alertmanager -n dapr-monitoring\nkubectl rollout status deployment/dapr-prom-prometheus-pushgateway -n dapr-monitoring\nkubectl rollout status deployment/dapr-prom-prometheus-server -n dapr-monitoring\nkubectl rollout status deployment/grafana -n dapr-monitoring\nkubectl rollout status deployment/kibana-kibana -n dapr-monitoring\n\n# Configure grafana dashbaords \ndeclare -r GRAFANA_PASS=$(kubectl get secret \\\n\t\t\t\t\t\t\t-n dapr-monitoring grafana \\\n                            -o jsonpath=\"{.data.admin-password}\" \\\n\t\t\t\t\t\t\t| base64 --decode)\n\nkubectl port-forward svc/grafana 8888:80 -n dapr-monitoring &\ncurl -X POST -s -k -u \"admin:${GRAFANA_PASS}\" \\\n\t\t-H \"Content-Type: application/json\" \\\n\t\t-d '{ \"name\":\"Dapr\", \"type\":\"prometheus\", \"url\":\"http://dapr-prom-prometheus-server.dapr-monitoring\", \"access\":\"proxy\", \"basicAuth\":false }' \\\n\t\thttp://localhost:8888/api/datasources\n\ndeclare -r DASH1=\"system-services-dashboard.json\"\nif [ ! -f \"$DASH1\" ]; then\n    curl -O \"${DAPR_CONFIG}/${DASH1}\"\nfi\ncurl -X POST -s -k -u \"admin:${GRAFANA_PASS}\" \\\n\t\t-H \"Content-Type: application/json\" \\\n\t\t-d @$DASH1 \\\n\t\thttp://localhost:8888/api/dashboards/db\n\ndeclare -r DASH2=\"sidecar-dashboard.json\"\nif [ ! -f \"$DASH2\" ]; then\n    curl -O \"${DAPR_CONFIG}/${DASH2}\"\nfi\ncurl -X POST -s -k -u \"admin:${GRAFANA_PASS}\" \\\n\t\t-H \"Content-Type: application/json\" \\\n\t\t-d @sidecar-dashboard.json \\\n\t\thttp://localhost:8888/api/dashboards/db\n\ndeclare -r DASH3=\"actor-dashboard.json\"\nif [ ! -f \"$DASH3\" ]; then\n    curl -O \"${DAPR_CONFIG}/${DASH3}\"\nfi\ncurl -X POST -s -k -u \"admin:${GRAFANA_PASS}\" \\\n\t\t-H \"Content-Type: application/json\" \\\n\t\t-d @actor-dashboard.json \\\n\t\thttp://localhost:8888/api/dashboards/db\n\n# Configure kibana\nkubectl port-forward svc/kibana-kibana 5601 -n dapr-monitoring &\ncurl -X POST -H \"kbn-xsrf: true\" \\\n\t\t-H \"Content-Type: application/json\" \\\n\t\t-d '{\"attributes\":{\"title\":\"dapr*\",\"timeFieldName\":\"@timestamp\"}}' \\\n\t\t\"http://localhost:5601/api/saved_objects/index-pattern/dapr\"\n\ncurl -X POST -H \"kbn-xsrf: true\" \\\n\t\t-H \"Content-Type: application/json\" \\\n\t\t-d '{\"value\":\"dapr\"}' \\\n\t\t\"http://localhost:5601/api/kibana/settings/defaultIndex\"\n\n"
  },
  {
    "path": "state-change-handler/Dockerfile",
    "content": "FROM golang:1.15.0 as builder\n\nWORKDIR /src/\nCOPY . /src/\n\nENV GO111MODULE=on\n\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \\\n    go build -a -tags netgo -mod vendor -o ./service .\n\nFROM gcr.io/distroless/static:nonroot\nCOPY --from=builder /src/service .\n\nENTRYPOINT [\"./service\"]\n"
  },
  {
    "path": "state-change-handler/Makefile",
    "content": "RELEASE_VERSION  =v0.11.1\nSERVICE_NAME    ?=\"publisher\"\nDOCKER_USERNAME ?=$(DOCKER_USER)\n\n.PHONY: mod test run debug build dapr event image show imagerun lint clean, tag, init\nall: test\n\ntidy: ## Updates the go modules and vendors all dependencies \n\tgo mod tidy\n\tgo mod vendor\n\ntest: tidy ## Tests the entire project \n\tgo test -count=1 -race ./...\n\nbuild: tidy ## Builds local release binary\n\tCGO_ENABLED=0 go build -a -tags netgo -mod vendor -o bin/$(SERVICE_NAME) .\n\ndebug: ## Runs uncompiled code it in Dapr in debug mode\n\tdapr run \\\n\t\t--app-id $(SERVICE_NAME) \\\n\t    --app-protocol grpc \\\n\t    --app-port 50001 \\\n\t    --components-path ./config \\\n\t    --log-level debug \\\n\t    go run main.go\n\nrun: build ## Builds binary and runs it in Dapr\n\tdapr run \\\n\t\t--app-id $(SERVICE_NAME) \\\n\t    --app-protocol grpc \\\n\t    --app-port 50001 \\\n        --components-path ./config \\\n        bin/$(SERVICE_NAME) \n\nimage: tidy ## Builds and publish docker image \n\tdocker build -t \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\" .\n\tdocker push \"$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)\"\n\nlint: ## Lints the entire project \n\tgolangci-lint run --timeout=3m\n\ntag: ## Creates release tag \n\tgit tag $(RELEASE_VERSION)\n\tgit push origin $(RELEASE_VERSION)\n\nclean: ## Cleans up generated files \n\tgo clean\n\trm -fr ./bin\n\trm -fr ./vendor\n\nhelp: ## Display available commands\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \\\n\t\t'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n"
  },
  {
    "path": "state-change-handler/README.md",
    "content": "# Dapr State Change Publisher \n\nBinding to detect RethinkDB state changes and stream them into a single topic. \n\n## Components \n\n### Source \n\n```yaml\napiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: changes\nspec:\n  type: bindings.rethinkdb.statechange\n  metadata:\n  - name: address\n    value: \"127.0.0.1:28015\"\n  - name: database\n    value: \"dapr\"\n```\n\n### Target\n\n```yaml\napiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: events\nspec:\n  type: pubsub.redis\n  metadata:\n  - name: redisHost\n    value: localhost:6379\n  - name: redisPassword\n    value: \"\"\n```\n\n## Service \n\n```shell\ndapr run --app-id publisher \\\n\t       --protocol grpc \\\n\t       --app-port 50001 \\\n\t       --components-path ./config \\\n\t       go run main.go\n```"
  },
  {
    "path": "state-change-handler/config/pubsub.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: events\nspec:\n  type: pubsub.redis\n  metadata:\n  - name: redisHost\n    value: localhost:6379\n  - name: redisPassword\n    value: \"\"\n"
  },
  {
    "path": "state-change-handler/config/statechange.yaml",
    "content": "apiVersion: dapr.io/v1alpha1\nkind: Component\nmetadata:\n  name: changes\nspec:\n  type: bindings.rethinkdb.statechange\n  metadata:\n  - name: address\n    value: \"127.0.0.1:28015\"\n  - name: database\n    value: \"dapr\"\n  - name: table\n    value: \"daprstate\"\n  - name: username\n    value: \"admin\"  # default\n  - name: password\n    value: \"rethinkdb\" # default"
  },
  {
    "path": "state-change-handler/go.mod",
    "content": "module github.com/mchmarny/dapr-demos/state-change-handler\n\ngo 1.15\n\nrequire (\n\tgithub.com/dapr/go-sdk v0.11.0\n\tgithub.com/pkg/errors v0.9.1\n)\n"
  },
  {
    "path": "state-change-handler/go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo=\ngithub.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=\ngolang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 h1:4X356008q5SA3YXu8PiRap39KFmy4Lf6sGlceJKZQsU=\ngolang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0 h1:uslsjIdqvZYANxSBQjTI47vZfwMaTN3mLELkMnMIY/A=\ngoogle.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=\ngoogle.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "state-change-handler/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\n\tdapr \"github.com/dapr/go-sdk/client\"\n\t\"github.com/dapr/go-sdk/service/common\"\n\tdaprd \"github.com/dapr/go-sdk/service/grpc\"\n\t\"github.com/pkg/errors\"\n)\n\nvar (\n\tclient  dapr.Client\n\tlogger  = log.New(os.Stdout, \"\", 0)\n\taddress = getEnvVar(\"ADDRESS\", \":50001\")\n\tmethod  = getEnvVar(\"METHOD\", \"changes\")\n\tpubsub  = getEnvVar(\"PUBSUB\", \"events\")\n\ttopic   = getEnvVar(\"TOPIC\", \"changes\")\n)\n\nfunc main() {\n\t// create client\n\tc, err := dapr.NewClient()\n\tif err != nil {\n\t\tlogger.Fatalf(\"error creating Dapr client: %v\", err)\n\t}\n\tclient = c\n\tdefer client.Close()\n\n\t// create the service\n\ts, err := daprd.NewService(address)\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to start the server: %v\", err)\n\t}\n\n\t// add method handler\n\terr = s.AddBindingInvocationHandler(method, bindingHandler)\n\tif err != nil {\n\t\tlog.Fatalf(\"error adding binding handler: %v\", err)\n\t}\n\n\t// start the service\n\tif err := s.Start(); err != nil {\n\t\tlog.Fatalf(\"server error: %v\", err)\n\t}\n}\n\nfunc bindingHandler(ctx context.Context, in *common.BindingEvent) (out []byte, err error) {\n\tlog.Printf(\"binding - Data:%s, Meta:%v\", in.Data, in.Metadata)\n\tif in.Data != nil || len(in.Data) > 0 {\n\t\tif err := client.PublishEvent(ctx, pubsub, topic, in.Data); err != nil {\n\t\t\tlogger.Printf(\"error publishing data to topic: %s\", topic)\n\t\t\treturn nil, errors.Wrapf(err, \"error publishing data to topic: %s\", topic)\n\t\t}\n\t}\n\treturn nil, nil\n}\n\nfunc getEnvVar(key, fallbackValue string) string {\n\tif val, ok := os.LookupEnv(key); ok {\n\t\treturn strings.TrimSpace(val)\n\t}\n\treturn fallbackValue\n}\n"
  }
]