Repository: mchmarny/dapr-demos Branch: master Commit: cc29106fb457 Files: 291 Total size: 1.1 MB Directory structure: gitextract_3z6jna8z/ ├── .gitignore ├── LICENSE ├── README.md ├── apim-gateway/ │ ├── README.md │ ├── apim/ │ │ ├── api.yaml │ │ ├── policy-all.json │ │ ├── policy-echo.json │ │ ├── policy-message.json │ │ └── policy-save.json │ └── k8s/ │ ├── binding.yaml │ ├── echo-service.yaml │ ├── event-subscriber.yaml │ ├── gateway.yaml │ └── pubsub.yaml ├── autoscaling-on-queue/ │ ├── README.md │ ├── deployment/ │ │ ├── kafka-client.yaml │ │ ├── kafka-pubsub.yaml │ │ ├── keda-2.0.0-beta.yaml │ │ ├── producer.yaml │ │ ├── subscriber-scaler.yaml │ │ └── subscriber.yaml │ ├── producer/ │ │ ├── Dockerfile │ │ ├── Makefile │ │ ├── go.mod │ │ ├── go.sum │ │ └── main.go │ └── subscriber/ │ ├── Dockerfile │ ├── Makefile │ ├── go.mod │ ├── go.sum │ └── main.go ├── component-api/ │ ├── README.md │ ├── config/ │ │ ├── email.yaml │ │ ├── state.yaml │ │ └── twitter.yaml │ └── sample/ │ ├── email.json │ └── twitter.json ├── cron-binding/ │ ├── Dockerfile │ ├── Makefile │ ├── README.md │ ├── config/ │ │ └── cron.yaml │ ├── go.mod │ ├── go.sum │ ├── k8s/ │ │ ├── component.yaml │ │ └── deployment.yaml │ └── main.go ├── dapr-aci/ │ ├── README.md │ ├── components/ │ │ ├── pubsub.yaml │ │ └── state.yaml │ ├── deployment/ │ │ └── app.yaml │ └── src/ │ ├── Dockerfile │ ├── Makefile │ ├── go.mod │ ├── go.sum │ └── main.go ├── dapr-api-on-aci/ │ ├── README.md │ └── email.yaml ├── daprized-ingress/ │ ├── README.md │ └── config/ │ ├── ingress-annotations.yaml │ ├── ingress-config.yaml │ ├── ingress.yaml │ └── namespace.yml ├── fan-out/ │ ├── README.md │ ├── grpc-echo-service/ │ │ ├── Dockerfile │ │ ├── Makefile │ │ ├── README.md │ │ ├── deployment.yaml │ │ ├── go.mod │ │ ├── go.sum │ │ └── main.go │ ├── http-format-converter/ │ │ ├── Dockerfile │ │ ├── Makefile │ │ ├── config/ │ │ │ ├── source-pubsub.yaml │ │ │ └── target-binding.yaml │ │ ├── go.mod │ │ ├── go.sum │ │ ├── k8s/ │ │ │ ├── components.yaml │ │ │ └── deployment.yaml │ │ └── main.go │ ├── queue-event-consumer/ │ │ ├── Dockerfile │ │ ├── Makefile │ │ ├── config/ │ │ │ └── source-pabusb.yaml │ │ ├── go.mod │ │ ├── go.sum │ │ ├── k8s/ │ │ │ ├── deployment.yaml │ │ │ ├── target-kafka.yaml │ │ │ └── target-redis.yaml │ │ └── main.go │ ├── queue-event-producer/ │ │ ├── Dockerfile │ │ ├── Makefile │ │ ├── config/ │ │ │ └── target-pabusb.yaml │ │ ├── go.mod │ │ ├── go.sum │ │ ├── k8s/ │ │ │ ├── deployment.yaml │ │ │ ├── target-kafka.yaml │ │ │ └── target-redis.yaml │ │ └── main.go │ ├── queue-format-converter/ │ │ ├── Dockerfile │ │ ├── Makefile │ │ ├── config/ │ │ │ ├── kafka.yaml │ │ │ ├── source-pabusb.yaml │ │ │ └── target-pubsub.yaml │ │ ├── go.mod │ │ ├── go.sum │ │ ├── k8s/ │ │ │ ├── deployment.yaml │ │ │ ├── target-kafka.yaml │ │ │ └── target-redis.yaml │ │ └── main.go │ └── service-format-converter/ │ ├── Dockerfile │ ├── Makefile │ ├── config/ │ │ └── source-pubsub.yaml │ ├── go.mod │ ├── go.sum │ ├── k8s/ │ │ ├── components.yaml │ │ └── deployment.yaml │ └── main.go ├── grpc-echo-service/ │ ├── Dockerfile │ ├── Makefile │ ├── README.md │ ├── deployment/ │ │ ├── app.yaml │ │ └── space.yaml │ ├── go.mod │ ├── go.sum │ └── main.go ├── grpc-event-subscriber/ │ ├── Dockerfile │ ├── Makefile │ ├── README.md │ ├── config/ │ │ └── pubsub.yaml │ ├── go.mod │ ├── go.sum │ ├── k8s/ │ │ ├── component.yaml │ │ └── deployment.yaml │ └── main.go ├── hardened/ │ ├── Makefile │ ├── README.md │ ├── config/ │ │ ├── pubsub.yaml │ │ └── state.yaml │ ├── deployment/ │ │ ├── hardened/ │ │ │ ├── app1.yaml │ │ │ ├── app2.yaml │ │ │ ├── app3.yaml │ │ │ ├── pubsub.yaml │ │ │ └── state.yaml │ │ ├── namespace/ │ │ │ ├── ns.yaml │ │ │ ├── role.yaml │ │ │ └── trace.yaml │ │ └── simple/ │ │ ├── app1.yaml │ │ ├── app2.yaml │ │ ├── app3.yaml │ │ ├── pubsub.yaml │ │ └── state.yaml │ └── src/ │ ├── app1/ │ │ ├── Dockerfile │ │ ├── Makefile │ │ ├── go.mod │ │ ├── go.sum │ │ └── main.go │ ├── app2/ │ │ ├── Dockerfile │ │ ├── Makefile │ │ ├── go.mod │ │ ├── go.sum │ │ └── main.go │ └── app3/ │ ├── Dockerfile │ ├── Makefile │ ├── go.mod │ ├── go.sum │ └── main.go ├── http-echo-service/ │ ├── Dockerfile │ ├── Makefile │ ├── README.md │ ├── deployment.yaml │ ├── go.mod │ ├── go.sum │ └── main.go ├── http-event-subscriber/ │ ├── Dockerfile │ ├── Makefile │ ├── README.md │ ├── clooudevent.json │ ├── config/ │ │ └── pubsub.yaml │ ├── go.mod │ ├── go.sum │ ├── k8s/ │ │ ├── component.yaml │ │ └── deployment.yaml │ └── main.go ├── order-cancellation/ │ ├── README.md │ ├── demo/ │ │ ├── Makefile │ │ ├── README.md │ │ ├── component/ │ │ │ ├── audit-store.yaml │ │ │ ├── email.yaml │ │ │ ├── queue.yaml │ │ │ └── workflow-store.yaml │ │ ├── config/ │ │ │ └── order-cancel.json │ │ ├── data/ │ │ │ ├── cancellation.json │ │ │ ├── email.json │ │ │ └── meta.json │ │ └── deployment/ │ │ ├── auditor.yaml │ │ ├── ingress.yaml │ │ ├── space.yaml │ │ ├── viewer.yaml │ │ └── workflow.yaml │ └── src/ │ ├── fn/ │ │ ├── .gitignore │ │ ├── DaprAzFn.csproj │ │ ├── Dockerfile │ │ ├── Makefile │ │ ├── ReceiveEvent.cs │ │ ├── config/ │ │ │ ├── meta.json │ │ │ ├── pubsub.yaml │ │ │ └── statestore.yaml │ │ ├── host.json │ │ └── local.settings.json │ └── viewer/ │ ├── Dockerfile │ ├── Makefile │ ├── config/ │ │ └── queue.yaml │ ├── go.mod │ ├── go.sum │ ├── main.go │ └── resource/ │ ├── static/ │ │ ├── css/ │ │ │ └── app.css │ │ └── js/ │ │ └── app.js │ └── template/ │ ├── footer.html │ ├── header.html │ └── index.html ├── pipeline/ │ ├── README.md │ ├── k8s/ │ │ ├── ingress.yaml │ │ ├── process-pubsub.yaml │ │ ├── processor.yaml │ │ ├── provider.yaml │ │ ├── scorer.yaml │ │ ├── space.yaml │ │ ├── state.yaml │ │ ├── tweeter-pubsub.yaml │ │ ├── twitter.yaml │ │ └── viewer.yaml │ ├── sentiment-scorer/ │ │ ├── Dockerfile │ │ ├── Makefile │ │ ├── README.md │ │ ├── config/ │ │ │ └── secret.yaml │ │ ├── go.mod │ │ ├── go.sum │ │ └── main.go │ ├── tweet-processor/ │ │ ├── Dockerfile │ │ ├── Makefile │ │ ├── config/ │ │ │ ├── result-pubsub.yaml │ │ │ └── source-pubsub.yaml │ │ ├── go.mod │ │ ├── go.sum │ │ └── main.go │ ├── tweet-provider/ │ │ ├── Dockerfile │ │ ├── Makefile │ │ ├── README.md │ │ ├── config/ │ │ │ ├── pubsub.yaml │ │ │ ├── secret.yaml │ │ │ ├── state.yaml │ │ │ └── twitter.yaml │ │ ├── go.mod │ │ ├── go.sum │ │ └── main.go │ └── tweet-viewer/ │ ├── Dockerfile │ ├── Makefile │ ├── config/ │ │ └── source-pubsub.yaml │ ├── go.mod │ ├── go.sum │ ├── main.go │ ├── resource/ │ │ ├── static/ │ │ │ ├── css/ │ │ │ │ └── app.css │ │ │ └── js/ │ │ │ └── app.js │ │ └── template/ │ │ ├── footer.html │ │ ├── header.html │ │ └── index.html │ └── tweet.json ├── setup/ │ ├── Makefile │ ├── README.md │ ├── aks/ │ │ ├── Makefile │ │ └── README.md │ ├── config/ │ │ ├── actor-dashboard.json │ │ ├── fluentd-config.yaml │ │ ├── fluentd.yaml │ │ ├── ingress-annotations.yaml │ │ ├── ingress-config.yaml │ │ ├── ingress-template.yaml │ │ ├── namespace-template.yml │ │ ├── sidecar-dashboard.json │ │ ├── system-services-dashboard.json │ │ └── zipkin.yaml │ ├── gke/ │ │ ├── Makefile │ │ └── README.md │ └── kubectl-install-dapr └── state-change-handler/ ├── Dockerfile ├── Makefile ├── README.md ├── config/ │ ├── pubsub.yaml │ └── statechange.yaml ├── go.mod ├── go.sum └── main.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # VS Code Debug # Test binary, built with `go test -c` *.test # Output *.out # mac .DS_Store # bin bin # vendored dependnacies vendor # certs *.pem # secrets secrets.json # certs certs setup/certs # other setup/config/ingress.yaml setup/config/namespace.yml ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 Mark Chmarny Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Dapr demos Collection of personal [Dapr](https://dapr.io) demos. > 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. * Bindings * [Scheduling using cron](./cron-binding) - Using scheduler to execute service * [Tweet stream](./pipeline/tweet-provider) - Subscribing to a Twitter even stream and publishing to a pub/sub topic * [State change handler](./state-change-handler) - RethinkDB state changes streamed into topic * Eventing * [gRPC event subscriber](./grpc-event-subscriber) - Subscribing to topic and processing its events using gRPC service * [HTTP event subscriber](./http-event-subscriber) - Subscribing to topic and processing its events using HTTP service * Services * [gRPC echo service](./grpc-echo-service) - gRPC service invocation example * [HTTP echo service](./http-echo-service) - HTTP service invocation example * [Sentiment Scorer](./pipeline/sentiment-scorer) - Sentiment scoring serving backed by Azure Cognitive Service * Integrations * [Components in ACI](./dapr-api-on-aci) - Dapr components as microservices on ACI without app * [Component in Kubernetes](./component-api) - Dapr components as microservices on Kubernetes without app * [Dapr Apps in ACI](./dapr-aci) - Deploying apps with Dapr sidecar and components on ACI * [Dapr with APIM](./apim-gateway) - Dapr API using Azure API Management self-hosted gateway * [Dapr API on Ingress](./daprized-ingress) - Expose Dapr API on Kubernetes Ingress Controller using NGINX * [Dapr GitOps](https://github.com/mchmarny/git-ops) - GitHub Actions build pipeline for Dapr apps * Solutions * [Order cancellation](./order-cancellation) - multiple Dapr service integrations with observability * [Pipeline](./pipeline) - Demos combining Twitter binding, Sentiment scoring, Multi Pub/Sub Processor, and WebSocket Viewer app * [Fan-out](./fan-out) - Single message source "broadcasted" to multiple, configurable targets (e.g. Redis PubSub, HTTP, gRPC) * [Hardened](./hardened) - Example of multi-microservice app with tightly controlled access to secrets, components, and full invoking service identity validation * Templates * [Dapr gRPC Service](https://github.com/mchmarny/dapr-grpc-service-template) - gRPC service template * [Dapr HTTP Event Subscriber](https://github.com/mchmarny/dapr-http-event-subscriber-template) - Event subscriber HTTP service template * [Dapr gRPC Event Subscriber](https://github.com/mchmarny/dapr-grpc-event-subscriber-template) - Event subscriber gRPC service template * [dapr-http-cron-handler](https://github.com/mchmarny/dapr-http-cron-handler-template) - Scheduled service development template ## Disclaimer This 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. ## License This software is released under the [MIT](./LICENSE) ================================================ FILE: apim-gateway/README.md ================================================ # Dapr & Azure API Management Integration Demo Dapr integration with [Azure API Management](https://azure.microsoft.com/en-us/services/api-management/) (APIM) using self-hosted gateway on Kubernetes. ![APIM Self-hosted Gateway Overview](img/overview-diagram.png) In 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: * Invocation of a specific Dapr service method * Publishing content to a Pub/Sub topic * Binding invocation with request content transformation In addition, we will overview the use of APIM tracing to debug your configuration. > 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. ## Prerequisite * [Azure account](https://azure.microsoft.com/en-us/free/) * [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest) * [Kubernetes cluster with Dapr](https://github.com/dapr/docs/blob/v0.9.0/getting-started/environment-setup.md#installing-dapr-on-a-kubernetes-cluster) * [Helm](https://helm.sh/docs/intro/install/) ## Terminology You 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: * [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) * [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 * [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 ## Setup To make this demo easier to reproduce, start by exporting the name for your new Azure API Management (APIM) service. > Note, the name of your API Management service instance has to be globally unique! ```shell export APIM_SERVICE_NAME="dapr-apim-demo" ``` In 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. ```shell export AZ_SUBSCRIPTION_ID="your-subscription-id" export AZ_RESOURCE_GROUP="your-resource-group" ``` ## Azure API Management We will start by configuring the Azure API Management service. ### Service Creation Create service instance: > The `publisher-email` and `publisher-name` are only required to receive system notifications e-mails. ```shell az apim create --name $APIM_SERVICE_NAME \ --subscription $AZ_SUBSCRIPTION_ID \ --resource-group $AZ_RESOURCE_GROUP \ --publisher-email "you@your-domain.com" \ --publisher-name "Your Name" ``` > 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) ### API Configuration Each [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: ```yaml servers: - url: http://.azure-api.net - url: https://.azure-api.net ``` When finished, import that OpenAPI definition fle into APIM service instance: ```shell az apim api import --path / \ --api-id dapr \ --subscription $AZ_SUBSCRIPTION_ID \ --resource-group $AZ_RESOURCE_GROUP \ --service-name $APIM_SERVICE_NAME \ --display-name "Demo Dapr Service API" \ --protocols http https \ --subscription-required true \ --specification-path apim/api.yaml \ --specification-format OpenApi ``` > 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. ### Azure API Token Export the Azure management API token to use through this demo. ```shell export AZ_API_TOKEN=$(az account get-access-token --resource=https://management.azure.com --query accessToken --output tsv) ``` > If you receive an error later that your token expired, just re-run this command ### Policy Management APIM [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. #### Global Policy APIM 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. > 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. ```xml ... ``` Apply that [policy](apim/policy-all.json) to all operations submit it to the Azure management API. ```shell curl -i -X PUT \ -d @apim/policy-all.json \ -H "Content-Type: application/json" \ -H "If-Match: *" \ -H "Authorization: Bearer ${AZ_API_TOKEN}" \ "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" ``` If everything goes well, the management API will return the created policy. #### Echo Service Policy The Dapr service invocation handles all the service discovery, so to invoke a specific method on any Dapr service users follow this API: ```http POST/GET/PUT/DELETE /v1.0/invoke//method/ ``` To 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 (``) 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. ```xml ... ``` To apply [this policy](apim/policy-echo.json) to the `echo` operation on our API, submit it to the Azure management API: ```shell curl -i -X PUT \ -d @apim/policy-echo.json \ -H "Content-Type: application/json" \ -H "If-Match: *" \ -H "Authorization: Bearer ${AZ_API_TOKEN}" \ "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" ``` If 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). Also, 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. ![](img/backend-policy.png) #### Message Topic Policy In addition to Dapr service invocation, APIM can also be used to publish to Dapr Pub/Sub API: ```http POST /v1.0/publish// ``` To 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: ```xml @(context.Request.Body.As()) ... ``` To apply [this policy](apim/policy-message.json) to the `message` operation on our API, submit it to the Azure management API: ```shell curl -i -X PUT \ -d @apim/policy-message.json \ -H "Content-Type: application/json" \ -H "If-Match: *" \ -H "Authorization: Bearer ${AZ_API_TOKEN}" \ "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" ``` If 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). #### Save Binding Policy In our final case, we are going to overview exposing the Dapr binding API. ```http POST/PUT /v1.0/bindings/ ``` In 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: ```json { "data": "", "metadata": { "": "", "": "" }, "operation": "" } ``` The 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: * `source` which will be a static value indicating the record came from `APIM` * `client-ip` which will be set to the client request IP * `key` which will be set to the value of the variable defined above Finally, for `data`, we simply use the original content of the client request. ```xml APIM @( context.Request.IpAddress ) @( (string)context.Variables["key"] ) @( context.Request.Body.As() ) ... ``` To apply [this policy](apim/policy-save.json) to the `save` operation on our API, submit it to the Azure management API: ```shell curl -i -X PUT \ -d @apim/policy-save.json \ -H "Content-Type: application/json" \ -H "If-Match: *" \ -H "Authorization: Bearer ${AZ_API_TOKEN}" \ "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" ``` > 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. If 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). ### Gateway Configuration To 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: ```shell curl -i -X PUT -d '{"properties": {"description": "Dapr Gateway","locationData": {"name": "Virtual"}}}' \ -H "Content-Type: application/json" \ -H "If-Match: *" \ -H "Authorization: Bearer ${AZ_API_TOKEN}" \ "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" ``` And then map that gateway to the previously created API: ```shell curl -i -X PUT -d '{ "properties": { "provisioningState": "created" } }' \ -H "Content-Type: application/json" \ -H "If-Match: *" \ -H "Authorization: Bearer ${AZ_API_TOKEN}" \ "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" ``` If everything goes well, the API returns JSON of the created objects. ## Kubernetes Switching now to the Kubernetes cluster... ### Dependencies To 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. > 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 Start with adding the Redis repo to your Helm charts: ```shell helm repo add bitnami https://charts.bitnami.com/bitnami helm repo update ``` And install Redis and wait for the deployment to complete: > Note, for simplicity, we are deploying everything into the `default` namespace. ```shell helm install redis bitnami/redis kubectl rollout status statefulset.apps/redis-master kubectl rollout status statefulset.apps/redis-slave ``` ### Dapr Components Dapr'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. ![](img/dapr-building-blocks.png) To create the binding component to point to the above created Redis cluster the configuration looks like this: ```yaml apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: demo-events spec: type: pubsub.redis metadata: - name: redisHost value: redis-master.default.svc.cluster.local:6379 - name: redisPassword secretKeyRef: name: redis key: redis-password - name: allowedTopics value: "messages" ``` Notice 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. To apply these component configurations run: ```shell kubectl apply -f k8s/pubsub.yaml kubectl apply -f k8s/binding.yaml ``` > Note, if you updated components after deploying the gateway you will need to restart the deployments. ```shell kubectl rollout restart deployment/event-subscriber kubectl rollout status deployment/event-subscriber kubectl rollout restart deployment/demo-apim-gateway kubectl rollout status deployment/demo-apim-gateway ``` To check if the components were registered correctly in Dapr, inspect the `daprd` logs in `demo-apim-gateway` pod for `demo-events` and `demo-binding`: ```shell kubectl logs -l app=demo-apim-gateway -c daprd --tail=200 ``` ### Dapr Services To deploy your application as a Dapr service all you need to do is augment your Kubernetes deployment template with few Dapr annotations. ```yaml annotations: dapr.io/enabled: "true" dapr.io/app-id: "event-subscriber" dapr.io/app-port: "8080" ``` > To learn more about Kubernetes sidecar configuration see [Dapr docs](https://github.com/dapr/docs/blob/master/concepts/configuration/README.md#kubernetes-sidecar-configuration). For 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: * [k8s/echo-service.yaml](k8s/echo-service.yaml) * [k8s/event-subscriber.yaml](k8s/event-subscriber.yaml) Deploy both of these and check that it is ready: ```shell kubectl apply -f k8s/echo-service.yaml kubectl apply -f k8s/event-subscriber.yaml kubectl get pods -l demo=dapr-apim -w ``` > Service is ready when its status is `Running` and the ready column is `2/2` (Dapr and our echo service containers both started) ```shell NAME READY STATUS RESTARTS AGE echo-service-668986b998-v2ssp 2/2 Running 0 10m event-subscriber-7d68b67d9d-5v7bf 2/2 Running 0 10m ``` To make sure that the event subscriber connects to the Redis service you can query the service logs ```shell kubectl logs -l app=event-subscriber -c daprd | grep demo-events ``` You should see entries containing: ```shell app responded with subscriptions [{demo-events messages /messages map[]}] app is subscribed to the following topics: [messages] through pubsub=demo-events subscribing to topic=messages on pubsub=demo-events ``` ### Self-hosted APIM Gateway To 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: > Note, the maximum validity for access tokens is 30 days. Update the below `expiry` parameter to be withing 30 days from today ```shell curl -i -X POST -d '{ "keyType": "primary", "expiry": "2020-10-10T00:00:01Z" }' \ -H "Content-Type: application/json" \ -H "Authorization: Bearer ${AZ_API_TOKEN}" \ "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" ``` Copy the content of `value` from the response and create a secret: ```shell kubectl create secret generic demo-apim-gateway-token --type Opaque --from-literal value="GatewayKey paste-the-key-here" ``` > Make sure the secret includes the `GatewayKey` and a space ` ` as well as the value of your token (e.g. `GatewayKey a1b2c3...`) Now, create a config map containing the APIM service endpoint that will be used to configure your self-hosted gateway: ```shell kubectl create configmap demo-apim-gateway-env --from-literal \ "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" ``` And finally, deploy the gateway and check that it's ready: ```shell kubectl apply -f k8s/gateway.yaml kubectl get pods -l app=demo-apim-gateway ``` > Note, the self-hosted gateway is deployed with 2 replicas to ensure availability during upgrades. Make sure both instances have status `Running` and container is ready `2/2` (gateway container + Dapr side-car). ```shell NAME READY STATUS RESTARTS AGE demo-apim-gateway-6dfb968f5c-cb4t7 2/2 Running 0 26s demo-apim-gateway-6dfb968f5c-gxrrq 2/2 Running 0 26s ``` To check on the gateway logs: ```shell kubectl logs -l app=demo-apim-gateway -c demo-apim-gateway ``` ## Usage (API Test) With APIM configured and self-hosted gateway deployed we are ready to test. Start by capturing the cluster load balancer ingress IP: ```shell export GATEWAY_IP=$(kubectl get svc demo-apim-gateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}') ``` ### API Subscription Key All of the APIs we defined in this demo are protected with subscription key. To invoke them, we will first need a subscription key: ```shell curl -i -H POST -d '{}' -H "Authorization: Bearer ${AZ_API_TOKEN}" \ "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" ``` The 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: ```shell export AZ_API_SUB_KEY="your-api-subscription-key" ``` ### Service Invocation To invoke the backing gRPC service over Dapr API exposed by APIM run: ```shell curl -i -X POST -d '{ "message": "hello" }' \ -H "Content-Type: application/json" \ -H "Ocp-Apim-Subscription-Key: ${AZ_API_SUB_KEY}" \ -H "Ocp-Apim-Trace: true" \ "http://${GATEWAY_IP}/echo" ``` If everything is configured correctly, you should see the response from your backing Dapr service: ```json { "message": "hello" } ``` In addition, you can also check the `echo-service` logs: ```shell kubectl logs -l app=echo-service -c service ``` ### Message Publishing To post a message to the Dapr Pub/Sub API exposed on APIM run: ```shell curl -i -X POST \ -d '{ "content": "hello" }' \ -H "Content-Type: application/json" \ -H "Ocp-Apim-Subscription-Key: ${AZ_API_SUB_KEY}" \ -H "Ocp-Apim-Trace: true" \ "http://${GATEWAY_IP}/message" ``` If everything is configured correctly, you will see `200` status code in the header, indicating the message was successfully delivered to the Dapr API. You can also check the `event-subscriber` logs: ```shell kubectl logs -l app=event-subscriber -c service ``` There should be an entry similar to this: ```shell event - PubsubName:demo-events, Topic:messages, ID:24f0e6f0-ab29-4cd6-8617-6c6c36ac1171, Data: map[message:hello] ``` ### Binding Invocation To save a record into database using the Dapr binding API exposed by APIM run: ```shell curl -i -X POST \ -d '{"city":"PDX","time":"1600171062","metric":"aqi","value": 457}' \ -H "Content-Type: application/json" \ -H "Ocp-Apim-Subscription-Key: ${AZ_API_SUB_KEY}" \ -H "Ocp-Apim-Trace: true" \ "http://${GATEWAY_IP}/save" ``` If 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. ### Debugging Notice 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: ```json ... { "source": "request-forwarder", "timestamp": "2020-09-11T11:15:52.9405903Z", "elapsed": "00:00:00.1382166", "data": { "message": "Request is being forwarded to the backend service. Timeout set to 300 seconds", "request": { "method": "POST", "url": "http://localhost:3500/v1.0/publish/demo-events/messages" } } }, { "source": "publish-to-dapr", "timestamp": "2020-09-11T11:15:53.1899121Z", "elapsed": "00:00:00.3875400", "data": { "response": { "status": { "code": 200, "reason": "OK" }, "headers": [ { "name": "Server", "value": "fasthttp" }, { "name": "Date", "value": "Fri, 11 Sep 2020 11:15:52 GMT" }, { "name": "Content-Length", "value": "0" }, { "name": "Traceparent", "value": "00-5b1f0bdfc2191742a4635a906359a7aa-196f5df2e977b00a-01" } ] } } } ... ``` ## Summary This 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/). ## Cleanup ```shell kubectl delete -f k8s/gateway.yaml kubectl delete secret demo-apim-gateway-token kubectl delete configmap demo-apim-gateway-env kubectl delete -f k8s/echo-service.yaml kubectl delete -f k8s/event-subscriber.yaml kubectl delete -f k8s/pubsub.yaml kubectl delete -f k8s/binding.yaml az apim delete --name $APIM_SERVICE_NAME --no-wait --yes ``` ================================================ FILE: apim-gateway/apim/api.yaml ================================================ openapi: 3.0.1 info: title: dapr version: '1.0' servers: - url: http://dapr-apim-demo.azure-api.net - url: https://dapr-apim-demo.azure-api.net paths: /echo: post: summary: Echo Service description: Invoke service using Dapr API operationId: echo requestBody: content: application/json: example: message: hello responses: '200': description: '' /message: post: summary: Message Topic description: Post to topic using Dapr API operationId: message requestBody: content: application/json: example: content: hello responses: '200': description: '' /save: post: summary: DB Binding description: DB binding using Dapr API operationId: save requestBody: content: application/json: example: city: PDX, time: 1600171062 metric: aqi value: 457 responses: '200': description: '' ================================================ FILE: apim-gateway/apim/policy-all.json ================================================ { "properties": { "format": "xml", "value": "" } } ================================================ FILE: apim-gateway/apim/policy-echo.json ================================================ { "properties": { "format": "xml", "value": "" } } ================================================ FILE: apim-gateway/apim/policy-message.json ================================================ { "properties": { "format": "xml", "value": "@( context.Request.Body.As<string>() )" } } ================================================ FILE: apim-gateway/apim/policy-save.json ================================================ { "properties": { "format": "xml", "value": "APIM@( context.Request.IpAddress )@( (string)context.Variables[\"key\"] )@( context.Request.Body.As<string>() )" } } ================================================ FILE: apim-gateway/k8s/binding.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: demo-binding spec: type: bindings.redis metadata: - name: redisHost value: redis-master.default.svc.cluster.local:6379 - name: redisPassword secretKeyRef: name: redis key: redis-password ================================================ FILE: apim-gateway/k8s/echo-service.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: echo-service labels: app: echo-service demo: dapr-apim spec: replicas: 1 selector: matchLabels: app: echo-service template: metadata: labels: app: echo-service demo: dapr-apim annotations: dapr.io/enabled: "true" dapr.io/app-id: "echo-service" dapr.io/app-protocol: "grpc" dapr.io/app-port: "60002" dapr.io/config: "tracing" dapr.io/log-as-json: "true" dapr.io/log-level: "debug" spec: containers: - name: service image: mchmarny/grpc-echo-service:v0.11.1 imagePullPolicy: Always ports: - containerPort: 60002 env: - name: ADDRESS value: ":60002" ================================================ FILE: apim-gateway/k8s/event-subscriber.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: event-subscriber labels: app: event-subscriber demo: dapr-apim spec: selector: matchLabels: app: event-subscriber template: metadata: labels: app: event-subscriber demo: dapr-apim annotations: dapr.io/enabled: "true" dapr.io/app-id: "event-subscriber" dapr.io/app-port: "8080" dapr.io/config: "tracing" dapr.io/log-as-json: "true" dapr.io/log-level: "debug" spec: containers: - name: service image: mchmarny/http-event-subscriber:v0.11.1 ports: - containerPort: 8080 env: - name: PORT value: "8080" - name: PUBSUB_NAME value: "demo-events" - name: TOPIC_NAME value: "messages" ================================================ FILE: apim-gateway/k8s/gateway.yaml ================================================ # NOTE: Before deploying to a production environment, please review the documentation -> https://aka.ms/self-hosted-gateway-production --- apiVersion: apps/v1 kind: Deployment metadata: name: demo-apim-gateway spec: replicas: 2 selector: matchLabels: app: demo-apim-gateway strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 0 maxSurge: 25% template: metadata: labels: app: demo-apim-gateway annotations: dapr.io/enabled: "true" dapr.io/app-id: "demo-apim-gateway" dapr.io/config: "tracing" dapr.io/log-as-json: "true" dapr.io/log-level: "debug" spec: terminationGracePeriodSeconds: 60 containers: - name: demo-apim-gateway image: mcr.microsoft.com/azure-api-management/gateway:beta ports: - name: http containerPort: 8080 - name: https containerPort: 8081 readinessProbe: httpGet: path: /internal-status-0123456789abcdef port: http scheme: HTTP initialDelaySeconds: 0 periodSeconds: 5 failureThreshold: 3 successThreshold: 1 env: - name: config.service.auth valueFrom: secretKeyRef: name: demo-apim-gateway-token key: value envFrom: - configMapRef: name: demo-apim-gateway-env --- apiVersion: v1 kind: Service metadata: name: demo-apim-gateway spec: type: LoadBalancer externalTrafficPolicy: Local ports: - name: http port: 80 targetPort: 8080 - name: https port: 443 targetPort: 8081 selector: app: demo-apim-gateway ================================================ FILE: apim-gateway/k8s/pubsub.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: demo-events spec: type: pubsub.redis metadata: - name: redisHost value: redis-master.default.svc.cluster.local:6379 - name: redisPassword secretKeyRef: name: redis key: redis-password - name: allowedTopics value: "messages" ================================================ FILE: autoscaling-on-queue/README.md ================================================ # Autoscaling Dapr service based on queue depth Dapr, 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. In 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. ![](image/diagram.png) ## Setup The 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. ### Keda Start by install [Keda](https://github.com/kedacore/keda) into the cluster and wait for it become ready: ```shell kubectl apply -f deployment/keda-2.0.0-beta.yaml kubectl rollout status deployment.apps/keda-operator -n keda ``` ### Kafka (optional) Next, if you don't have access to Kafka you can use these instructions to install Kafka into the cluster: ```shell helm repo add confluentinc https://confluentinc.github.io/cp-helm-charts/ helm repo update kubectl create ns kafka helm install kafka confluentinc/cp-helm-charts -n kafka \ --set cp-schema-registry.enabled=false \ --set cp-kafka-rest.enabled=false \ --set cp-kafka-connect.enabled=false \ --set dataLogDirStorageClass=default \ --set dataDirStorageClass=default \ --set storageClass=default kubectl rollout status deployment.apps/kafka-cp-control-center -n kafka kubectl rollout status deployment.apps/kafka-cp-ksql-server -n kafka kubectl rollout status statefulset.apps/kafka-cp-kafka -n kafka kubectl rollout status statefulset.apps/kafka-cp-zookeeper -n kafka ``` When done, also deploy Kafka client and wait until it's ready: ```shell kubectl apply -n kafka -f deployment/kafka-client.yaml kubectl wait -n kafka --for=condition=ready pod kafka-client --timeout=120s ``` Next, create the `metric` topic which we will use in this demo: > The number of `partitions` is connected to the maximum number of replicas Keda will create. ```shell kubectl -n kafka exec -it kafka-client -- kafka-topics \ --zookeeper kafka-cp-zookeeper-headless:2181 \ --topic metric \ --create \ --partitions 10 \ --replication-factor 3 \ --if-not-exists ``` ## Deployment To 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. ### Subscriber The `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. To 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): ```shell kubectl apply -f deployment/kafka-pubsub.yaml kubectl apply -f deployment/subscriber.yaml kubectl apply -f deployment/subscriber-scaler.yaml ``` When done, start watching for the number of replicas of the deployed `subscriber` service: ```shell watch kubectl get pods -l app=autoscaling-subscriber ``` > 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`. ### Producer In a second terminal session, deploy the [producer service](deployment/producer.yaml) and wait for it to be ready: ```shell kubectl apply -f deployment/producer.yaml kubectl rollout status deployment/autoscaling-producer ``` ## Demo Back 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: ```shell NAME READY STATUS RESTARTS AGE autoscaling-subscriber-696ffb5c7b-64zqq 2/2 Running 0 31s autoscaling-subscriber-696ffb5c7b-67f74 2/2 Running 0 15s autoscaling-subscriber-696ffb5c7b-gpc2d 2/2 Running 0 7m42s ``` By 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): ```yaml pollingInterval: 15 minReplicaCount: 0 maxReplicaCount: 10 cooldownPeriod: 30 ``` To 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: ```yaml - name: PROCESS_DURATION value: "300ms" ``` Finally, 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: ```yaml - name: NUMBER_OF_PUBLISHERS value: "1" - name: PUBLISHERS_FREQ value: "100ms" ``` The `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). > 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 ```shell kubectl scale -n kafka deployment/autoscaling-producer --replicas=10 ``` ### Updating Components If you have changed already deployed Dapr component, make sure to reload the `subscriber` and `producer` deployments: ```shell kubectl rollout restart deployment/autoscaling-subscriber kubectl rollout status deployment/autoscaling-subscriber kubectl rollout restart deployment/autoscaling-producer kubectl rollout status deployment/autoscaling-producer ``` ### Kafka Helpers Get `metric` topic offsets for `autoscaling-subscriber` consumer group: ```shell kubectl -n kafka exec -it kafka-client -- kafka-consumer-groups \ --bootstrap-server kafka-cp-kafka:9092 \ --describe \ --group autoscaling-subscriber ``` Purge the `metric` topic: ```shell kubectl -n kafka exec -it kafka-client -- kafka-topics \ --zookeeper kafka-cp-zookeeper:2181 \ --alter \ --topic metric \ --config retention.ms=1000 sleep 15 kubectl -n kafka exec -it kafka-client -- kafka-topics \ --zookeeper kafka-cp-zookeeper:2181 \ --alter \ --topic metric \ --delete-config retention.ms ``` Delete `metric` topic ```shell kubectl -n kafka exec -it kafka-client -- kafka-topics \ --zookeeper kafka-cp-zookeeper:2181 \ --delete \ --topic metric ``` ## Cleanup ```shell kubectl delete -f deployment/producer.yaml kubectl delete -f deployment/kafka-pubsub.yaml kubectl delete -f deployment/subscriber.yaml kubectl delete -f deployment/subscriber-scaler.yaml kubectl delete -f deployment/keda-2.0.0-beta.yaml ``` ## Disclaimer This 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. ## License This software is released under the [MIT](../LICENSE) ================================================ FILE: autoscaling-on-queue/deployment/kafka-client.yaml ================================================ apiVersion: v1 kind: Pod metadata: name: kafka-client spec: containers: - name: kafka-client image: confluentinc/cp-enterprise-kafka:5.5.0 command: - sh - -c - "exec tail -f /dev/null" ================================================ FILE: autoscaling-on-queue/deployment/kafka-pubsub.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: autoscaling-pubsub spec: type: pubsub.kafka metadata: - name: brokers value: kafka-cp-kafka.kafka.svc.cluster.local:9092 - name: authRequired value: "false" - name: allowedTopics value: metric - name: consumerID value: autoscaling-subscriber ================================================ FILE: autoscaling-on-queue/deployment/keda-2.0.0-beta.yaml ================================================ apiVersion: v1 kind: Namespace metadata: labels: app.kubernetes.io/name: keda app.kubernetes.io/part-of: keda-operator app.kubernetes.io/version: 2.0.0-beta name: keda --- apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.3.0 creationTimestamp: null labels: app.kubernetes.io/part-of: keda-operator app.kubernetes.io/version: 2.0.0-beta name: scaledjobs.keda.sh spec: additionalPrinterColumns: - JSONPath: .spec.triggers[*].type name: Triggers type: string - JSONPath: .spec.triggers[*].authenticationRef.name name: Authentication type: string - JSONPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string - JSONPath: .status.conditions[?(@.type=="Active")].status name: Active type: string - JSONPath: .metadata.creationTimestamp name: Age type: date group: keda.sh names: kind: ScaledJob listKind: ScaledJobList plural: scaledjobs shortNames: - sj singular: scaledjob scope: Namespaced subresources: status: {} validation: openAPIV3Schema: description: ScaledJob is the Schema for the scaledjobs API properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object spec: description: ScaledJobSpec defines the desired state of ScaledJob properties: envSourceContainerName: type: string failedJobsHistoryLimit: format: int32 type: integer jobTargetRef: description: JobSpec describes how the job execution will look like. properties: activeDeadlineSeconds: description: Specifies the duration in seconds relative to the startTime that the job may be active before the system tries to terminate it; value must be positive integer format: int64 type: integer backoffLimit: description: Specifies the number of retries before marking this job failed. Defaults to 6 format: int32 type: integer completions: description: 'Specifies the desired number of successfully finished pods the job should be run with. Setting to nil means that the success of any pod signals the success of all pods, and allows parallelism to have any positive value. Setting to 1 means that parallelism is limited to 1 and the success of that pod signals the success of the job. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/' format: int32 type: integer manualSelector: description: 'manualSelector controls generation of pod labels and pod selectors. Leave `manualSelector` unset unless you are certain what you are doing. When false or unset, the system pick labels unique to this job and appends those labels to the pod template. When true, the user is responsible for picking unique labels and specifying the selector. Failure to pick a unique label may cause this and other jobs to not function correctly. However, You may see `manualSelector=true` in jobs that were created with the old `extensions/v1beta1` API. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/#specifying-your-own-pod-selector' type: boolean parallelism: description: 'Specifies the maximum desired number of pods the job should run at any given time. The actual number of pods running in steady state will be less than this number when ((.spec.completions - .status.successful) < .spec.parallelism), i.e. when the work left to do is less than max parallelism. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/' format: int32 type: integer selector: description: 'A label query over pods that should match the pod count. Normally, the system sets this field for you. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors' properties: matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. items: description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. properties: key: description: key is the label key that the selector applies to. type: string operator: description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. type: string values: description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. items: type: string type: array required: - key - operator type: object type: array matchLabels: additionalProperties: type: string description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object template: description: 'Describes the pod that will be created when executing a job. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/' properties: metadata: description: 'Standard object''s metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata' type: object spec: description: 'Specification of the desired behavior of the pod. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status' properties: activeDeadlineSeconds: description: Optional duration in seconds the pod may be active on the node relative to StartTime before the system will actively try to mark it failed and kill associated containers. Value must be a positive integer. format: int64 type: integer affinity: description: If specified, the pod's scheduling constraints properties: nodeAffinity: description: Describes node affinity scheduling rules for the pod. properties: preferredDuringSchedulingIgnoredDuringExecution: description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node matches the corresponding matchExpressions; the node(s) with the highest sum are the most preferred. items: description: An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). properties: preference: description: A node selector term, associated with the corresponding weight. properties: matchExpressions: description: A list of node selector requirements by node's labels. items: description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. properties: key: description: The label key that the selector applies to. type: string operator: description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. type: string values: description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. items: type: string type: array required: - key - operator type: object type: array matchFields: description: A list of node selector requirements by node's fields. items: description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. properties: key: description: The label key that the selector applies to. type: string operator: description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. type: string values: description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. items: type: string type: array required: - key - operator type: object type: array type: object weight: description: Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. format: int32 type: integer required: - preference - weight type: object type: array requiredDuringSchedulingIgnoredDuringExecution: description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to an update), the system may or may not try to eventually evict the pod from its node. properties: nodeSelectorTerms: description: Required. A list of node selector terms. The terms are ORed. items: description: A null or empty node selector term matches no objects. The requirements of them are ANDed. The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. properties: matchExpressions: description: A list of node selector requirements by node's labels. items: description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. properties: key: description: The label key that the selector applies to. type: string operator: description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. type: string values: description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. items: type: string type: array required: - key - operator type: object type: array matchFields: description: A list of node selector requirements by node's fields. items: description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. properties: key: description: The label key that the selector applies to. type: string operator: description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. type: string values: description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. items: type: string type: array required: - key - operator type: object type: array type: object type: array required: - nodeSelectorTerms type: object type: object podAffinity: description: Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). properties: preferredDuringSchedulingIgnoredDuringExecution: description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) properties: podAffinityTerm: description: Required. A pod affinity term, associated with the corresponding weight. properties: labelSelector: description: A label query over a set of resources, in this case pods. properties: matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. items: description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. properties: key: description: key is the label key that the selector applies to. type: string operator: description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. type: string values: description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. items: type: string type: array required: - key - operator type: object type: array matchLabels: additionalProperties: type: string description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object namespaces: description: namespaces specifies which namespaces the labelSelector applies to (matches against); null or empty list means "this pod's namespace" items: type: string type: array topologyKey: description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. type: string required: - topologyKey type: object weight: description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. format: int32 type: integer required: - podAffinityTerm - weight type: object type: array requiredDuringSchedulingIgnoredDuringExecution: description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. items: description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running properties: labelSelector: description: A label query over a set of resources, in this case pods. properties: matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. items: description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. properties: key: description: key is the label key that the selector applies to. type: string operator: description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. type: string values: description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. items: type: string type: array required: - key - operator type: object type: array matchLabels: additionalProperties: type: string description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object namespaces: description: namespaces specifies which namespaces the labelSelector applies to (matches against); null or empty list means "this pod's namespace" items: type: string type: array topologyKey: description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. type: string required: - topologyKey type: object type: array type: object podAntiAffinity: description: Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). properties: preferredDuringSchedulingIgnoredDuringExecution: description: The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. items: description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) properties: podAffinityTerm: description: Required. A pod affinity term, associated with the corresponding weight. properties: labelSelector: description: A label query over a set of resources, in this case pods. properties: matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. items: description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. properties: key: description: key is the label key that the selector applies to. type: string operator: description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. type: string values: description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. items: type: string type: array required: - key - operator type: object type: array matchLabels: additionalProperties: type: string description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object namespaces: description: namespaces specifies which namespaces the labelSelector applies to (matches against); null or empty list means "this pod's namespace" items: type: string type: array topologyKey: description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. type: string required: - topologyKey type: object weight: description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. format: int32 type: integer required: - podAffinityTerm - weight type: object type: array requiredDuringSchedulingIgnoredDuringExecution: description: If the anti-affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the anti-affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. items: description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running properties: labelSelector: description: A label query over a set of resources, in this case pods. properties: matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. items: description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. properties: key: description: key is the label key that the selector applies to. type: string operator: description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. type: string values: description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. items: type: string type: array required: - key - operator type: object type: array matchLabels: additionalProperties: type: string description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object namespaces: description: namespaces specifies which namespaces the labelSelector applies to (matches against); null or empty list means "this pod's namespace" items: type: string type: array topologyKey: description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. type: string required: - topologyKey type: object type: array type: object type: object automountServiceAccountToken: description: AutomountServiceAccountToken indicates whether a service account token should be automatically mounted. type: boolean containers: description: List of containers belonging to the pod. Containers cannot currently be added or removed. There must be at least one container in a Pod. Cannot be updated. items: description: A single application container that you want to run within a pod. properties: args: description: 'Arguments to the entrypoint. The docker image''s CMD is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container''s environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable 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' items: type: string type: array command: description: 'Entrypoint array. Not executed within a shell. The docker image''s ENTRYPOINT is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container''s environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable 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' items: type: string type: array env: description: List of environment variables to set in the container. Cannot be updated. items: description: EnvVar represents an environment variable present in a Container. properties: name: description: Name of the environment variable. Must be a C_IDENTIFIER. type: string value: description: 'Variable references $(VAR_NAME) are expanded using the previous defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".' type: string valueFrom: description: Source for the environment variable's value. Cannot be used if value is not empty. properties: configMapKeyRef: description: Selects a key of a ConfigMap. properties: key: description: The key to select. type: string name: description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' type: string optional: description: Specify whether the ConfigMap or its key must be defined type: boolean required: - key type: object fieldRef: description: 'Selects a field of the pod: supports metadata.name, metadata.namespace, metadata.labels, metadata.annotations, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs.' properties: apiVersion: description: Version of the schema the FieldPath is written in terms of, defaults to "v1". type: string fieldPath: description: Path of the field to select in the specified API version. type: string required: - fieldPath type: object resourceFieldRef: description: 'Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.' properties: containerName: description: 'Container name: required for volumes, optional for env vars' type: string divisor: anyOf: - type: integer - type: string description: Specifies the output format of the exposed resources, defaults to "1" pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true resource: description: 'Required: resource to select' type: string required: - resource type: object secretKeyRef: description: Selects a key of a secret in the pod's namespace properties: key: description: The key of the secret to select from. Must be a valid secret key. type: string name: description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' type: string optional: description: Specify whether the Secret or its key must be defined type: boolean required: - key type: object type: object required: - name type: object type: array envFrom: description: List of sources to populate environment variables in the container. The keys defined within a source must be a C_IDENTIFIER. All invalid keys will be reported as an event when the container is starting. When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be updated. items: description: EnvFromSource represents the source of a set of ConfigMaps properties: configMapRef: description: The ConfigMap to select from properties: name: description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' type: string optional: description: Specify whether the ConfigMap must be defined type: boolean type: object prefix: description: An optional identifier to prepend to each key in the ConfigMap. Must be a C_IDENTIFIER. type: string secretRef: description: The Secret to select from properties: name: description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' type: string optional: description: Specify whether the Secret must be defined type: boolean type: object type: object type: array image: description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images This field is optional to allow higher level config management to default or override container images in workload controllers like Deployments and StatefulSets.' type: string imagePullPolicy: description: 'Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' type: string lifecycle: description: Actions that the management system should take in response to container lifecycle events. Cannot be updated. properties: postStart: description: 'PostStart is called immediately after a container is created. If the handler fails, the container is terminated and restarted according to its restart policy. Other management of the container blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' properties: exec: description: One and only one of the following should be specified. Exec specifies the action to take. properties: command: description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. items: type: string type: array type: object httpGet: description: HTTPGet specifies the http request to perform. properties: host: description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. type: string httpHeaders: description: Custom headers to set in the request. HTTP allows repeated headers. items: description: HTTPHeader describes a custom header to be used in HTTP probes properties: name: description: The header field name type: string value: description: The header field value type: string required: - name - value type: object type: array path: description: Path to access on the HTTP server. type: string port: anyOf: - type: integer - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. x-kubernetes-int-or-string: true scheme: description: Scheme to use for connecting to the host. Defaults to HTTP. type: string required: - port type: object tcpSocket: description: 'TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported TODO: implement a realistic TCP lifecycle hook' properties: host: description: 'Optional: Host name to connect to, defaults to the pod IP.' type: string port: anyOf: - type: integer - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. x-kubernetes-int-or-string: true required: - port type: object type: object preStop: description: 'PreStop is called immediately before a container is terminated due to an API request or management event such as liveness/startup probe failure, preemption, resource contention, etc. The handler is not called if the container crashes or exits. The reason for termination is passed to the handler. The Pod''s termination grace period countdown begins before the PreStop hooked is executed. Regardless of the outcome of the handler, the container will eventually terminate within the Pod''s termination grace period. Other management of the container blocks until the hook completes or until the termination grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' properties: exec: description: One and only one of the following should be specified. Exec specifies the action to take. properties: command: description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. items: type: string type: array type: object httpGet: description: HTTPGet specifies the http request to perform. properties: host: description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. type: string httpHeaders: description: Custom headers to set in the request. HTTP allows repeated headers. items: description: HTTPHeader describes a custom header to be used in HTTP probes properties: name: description: The header field name type: string value: description: The header field value type: string required: - name - value type: object type: array path: description: Path to access on the HTTP server. type: string port: anyOf: - type: integer - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. x-kubernetes-int-or-string: true scheme: description: Scheme to use for connecting to the host. Defaults to HTTP. type: string required: - port type: object tcpSocket: description: 'TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported TODO: implement a realistic TCP lifecycle hook' properties: host: description: 'Optional: Host name to connect to, defaults to the pod IP.' type: string port: anyOf: - type: integer - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. x-kubernetes-int-or-string: true required: - port type: object type: object type: object livenessProbe: description: 'Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' properties: exec: description: One and only one of the following should be specified. Exec specifies the action to take. properties: command: description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. items: type: string type: array type: object failureThreshold: description: Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1. format: int32 type: integer httpGet: description: HTTPGet specifies the http request to perform. properties: host: description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. type: string httpHeaders: description: Custom headers to set in the request. HTTP allows repeated headers. items: description: HTTPHeader describes a custom header to be used in HTTP probes properties: name: description: The header field name type: string value: description: The header field value type: string required: - name - value type: object type: array path: description: Path to access on the HTTP server. type: string port: anyOf: - type: integer - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. x-kubernetes-int-or-string: true scheme: description: Scheme to use for connecting to the host. Defaults to HTTP. type: string required: - port type: object initialDelaySeconds: description: 'Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' format: int32 type: integer periodSeconds: description: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1. format: int32 type: integer successThreshold: description: Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. format: int32 type: integer tcpSocket: description: 'TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported TODO: implement a realistic TCP lifecycle hook' properties: host: description: 'Optional: Host name to connect to, defaults to the pod IP.' type: string port: anyOf: - type: integer - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. x-kubernetes-int-or-string: true required: - port type: object timeoutSeconds: description: 'Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' format: int32 type: integer type: object name: description: Name of the container specified as a DNS_LABEL. Each container in a pod must have a unique name (DNS_LABEL). Cannot be updated. type: string ports: description: List of ports to expose from the container. Exposing a port here gives the system additional information about the network connections a container uses, but is primarily informational. Not specifying a port here DOES NOT prevent that port from being exposed. Any port which is listening on the default "0.0.0.0" address inside a container will be accessible from the network. Cannot be updated. items: description: ContainerPort represents a network port in a single container. properties: containerPort: description: Number of port to expose on the pod's IP address. This must be a valid port number, 0 < x < 65536. format: int32 type: integer hostIP: description: What host IP to bind the external port to. type: string hostPort: description: Number of port to expose on the host. If specified, this must be a valid port number, 0 < x < 65536. If HostNetwork is specified, this must match ContainerPort. Most containers do not need this. format: int32 type: integer name: description: If specified, this must be an IANA_SVC_NAME and unique within the pod. Each named port in a pod must have a unique name. Name for the port that can be referred to by services. type: string protocol: description: Protocol for port. Must be UDP, TCP, or SCTP. Defaults to "TCP". type: string required: - containerPort - protocol type: object type: array x-kubernetes-list-map-keys: - containerPort - protocol x-kubernetes-list-type: map readinessProbe: description: 'Periodic probe of container service readiness. Container will be removed from service endpoints if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' properties: exec: description: One and only one of the following should be specified. Exec specifies the action to take. properties: command: description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. items: type: string type: array type: object failureThreshold: description: Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1. format: int32 type: integer httpGet: description: HTTPGet specifies the http request to perform. properties: host: description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. type: string httpHeaders: description: Custom headers to set in the request. HTTP allows repeated headers. items: description: HTTPHeader describes a custom header to be used in HTTP probes properties: name: description: The header field name type: string value: description: The header field value type: string required: - name - value type: object type: array path: description: Path to access on the HTTP server. type: string port: anyOf: - type: integer - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. x-kubernetes-int-or-string: true scheme: description: Scheme to use for connecting to the host. Defaults to HTTP. type: string required: - port type: object initialDelaySeconds: description: 'Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' format: int32 type: integer periodSeconds: description: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1. format: int32 type: integer successThreshold: description: Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. format: int32 type: integer tcpSocket: description: 'TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported TODO: implement a realistic TCP lifecycle hook' properties: host: description: 'Optional: Host name to connect to, defaults to the pod IP.' type: string port: anyOf: - type: integer - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. x-kubernetes-int-or-string: true required: - port type: object timeoutSeconds: description: 'Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' format: int32 type: integer type: object resources: description: 'Compute Resources required by this container. Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' properties: limits: additionalProperties: anyOf: - type: integer - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true description: 'Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' type: object requests: additionalProperties: anyOf: - type: integer - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true description: 'Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' type: object type: object securityContext: description: 'Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/' properties: allowPrivilegeEscalation: description: 'AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. This bool directly controls if the no_new_privs flag will be set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN' type: boolean capabilities: description: The capabilities to add/drop when running containers. Defaults to the default set of capabilities granted by the container runtime. properties: add: description: Added capabilities items: description: Capability represent POSIX capabilities type type: string type: array drop: description: Removed capabilities items: description: Capability represent POSIX capabilities type type: string type: array type: object privileged: description: Run container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false. type: boolean procMount: description: procMount denotes the type of proc mount to use for the containers. The default is DefaultProcMount which uses the container runtime defaults for readonly paths and masked paths. This requires the ProcMountType feature flag to be enabled. type: string readOnlyRootFilesystem: description: Whether this container has a read-only root filesystem. Default is false. type: boolean runAsGroup: description: The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. format: int64 type: integer runAsNonRoot: description: Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. type: boolean runAsUser: description: The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. format: int64 type: integer seLinuxOptions: description: The SELinux context to be applied to the container. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. properties: level: description: Level is SELinux level label that applies to the container. type: string role: description: Role is a SELinux role label that applies to the container. type: string type: description: Type is a SELinux type label that applies to the container. type: string user: description: User is a SELinux user label that applies to the container. type: string type: object windowsOptions: description: The Windows specific settings applied to all containers. If unspecified, the options from the PodSecurityContext will be used. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. properties: gmsaCredentialSpec: description: GMSACredentialSpec is where the GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the GMSA credential spec named by the GMSACredentialSpecName field. type: string gmsaCredentialSpecName: description: GMSACredentialSpecName is the name of the GMSA credential spec to use. type: string runAsUserName: description: The UserName in Windows to run the entrypoint of the container process. Defaults to the user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. type: string type: object type: object startupProbe: description: 'StartupProbe indicates that the Pod has successfully initialized. If specified, no other probes are executed until this completes successfully. If this probe fails, the Pod will be restarted, just as if the livenessProbe failed. This can be used to provide different probe parameters at the beginning of a Pod''s lifecycle, when it might take a long time to load data or warm a cache, than during steady-state operation. This cannot be updated. This is a beta feature enabled by the StartupProbe feature flag. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' properties: exec: description: One and only one of the following should be specified. Exec specifies the action to take. properties: command: description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. items: type: string type: array type: object failureThreshold: description: Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1. format: int32 type: integer httpGet: description: HTTPGet specifies the http request to perform. properties: host: description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. type: string httpHeaders: description: Custom headers to set in the request. HTTP allows repeated headers. items: description: HTTPHeader describes a custom header to be used in HTTP probes properties: name: description: The header field name type: string value: description: The header field value type: string required: - name - value type: object type: array path: description: Path to access on the HTTP server. type: string port: anyOf: - type: integer - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. x-kubernetes-int-or-string: true scheme: description: Scheme to use for connecting to the host. Defaults to HTTP. type: string required: - port type: object initialDelaySeconds: description: 'Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' format: int32 type: integer periodSeconds: description: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1. format: int32 type: integer successThreshold: description: Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. format: int32 type: integer tcpSocket: description: 'TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported TODO: implement a realistic TCP lifecycle hook' properties: host: description: 'Optional: Host name to connect to, defaults to the pod IP.' type: string port: anyOf: - type: integer - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. x-kubernetes-int-or-string: true required: - port type: object timeoutSeconds: description: 'Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' format: int32 type: integer type: object stdin: description: Whether this container should allocate a buffer for stdin in the container runtime. If this is not set, reads from stdin in the container will always result in EOF. Default is false. type: boolean stdinOnce: description: Whether the container runtime should close the stdin channel after it has been opened by a single attach. When stdin is true the stdin stream will remain open across multiple attach sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the first client attaches to stdin, and then remains open and accepts data until the client disconnects, at which time stdin is closed and remains closed until the container is restarted. If this flag is false, a container processes that reads from stdin will never receive an EOF. Default is false type: boolean terminationMessagePath: description: 'Optional: Path at which the file to which the container''s termination message will be written is mounted into the container''s filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.' type: string terminationMessagePolicy: description: Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated. type: string tty: description: Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. Default is false. type: boolean volumeDevices: description: volumeDevices is the list of block devices to be used by the container. items: description: volumeDevice describes a mapping of a raw block device within a container. properties: devicePath: description: devicePath is the path inside of the container that the device will be mapped to. type: string name: description: name must match the name of a persistentVolumeClaim in the pod type: string required: - devicePath - name type: object type: array volumeMounts: description: Pod volumes to mount into the container's filesystem. Cannot be updated. items: description: VolumeMount describes a mounting of a Volume within a container. properties: mountPath: description: Path within the container at which the volume should be mounted. Must not contain ':'. type: string mountPropagation: description: mountPropagation determines how mounts are propagated from the host to container and the other way around. When not set, MountPropagationNone is used. This field is beta in 1.10. type: string name: description: This must match the Name of a Volume. type: string readOnly: description: Mounted read-only if true, read-write otherwise (false or unspecified). Defaults to false. type: boolean subPath: description: Path within the volume from which the container's volume should be mounted. Defaults to "" (volume's root). type: string subPathExpr: description: Expanded path within the volume from which the container's volume should be mounted. Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. Defaults to "" (volume's root). SubPathExpr and SubPath are mutually exclusive. type: string required: - mountPath - name type: object type: array workingDir: description: Container's working directory. If not specified, the container runtime's default will be used, which might be configured in the container image. Cannot be updated. type: string required: - name type: object type: array dnsConfig: description: Specifies the DNS parameters of a pod. Parameters specified here will be merged to the generated DNS configuration based on DNSPolicy. properties: nameservers: description: A list of DNS name server IP addresses. This will be appended to the base nameservers generated from DNSPolicy. Duplicated nameservers will be removed. items: type: string type: array options: description: A list of DNS resolver options. This will be merged with the base options generated from DNSPolicy. Duplicated entries will be removed. Resolution options given in Options will override those that appear in the base DNSPolicy. items: description: PodDNSConfigOption defines DNS resolver options of a pod. properties: name: description: Required. type: string value: type: string type: object type: array searches: description: A list of DNS search domains for host-name lookup. This will be appended to the base search paths generated from DNSPolicy. Duplicated search paths will be removed. items: type: string type: array type: object dnsPolicy: description: Set DNS policy for the pod. Defaults to "ClusterFirst". Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'. DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'. type: string enableServiceLinks: description: 'EnableServiceLinks indicates whether information about services should be injected into pod''s environment variables, matching the syntax of Docker links. Optional: Defaults to true.' type: boolean ephemeralContainers: description: List of ephemeral containers run in this pod. Ephemeral containers may be run in an existing pod to perform user-initiated actions such as debugging. This list cannot be specified when creating a pod, and it cannot be modified by updating the pod spec. In order to add an ephemeral container to an existing pod, use the pod's ephemeralcontainers subresource. This field is alpha-level and is only honored by servers that enable the EphemeralContainers feature. items: description: An EphemeralContainer is a container that may be added temporarily to an existing pod for user-initiated activities such as debugging. Ephemeral containers have no resource or scheduling guarantees, and they will not be restarted when they exit or when a pod is removed or restarted. If an ephemeral container causes a pod to exceed its resource allocation, the pod may be evicted. Ephemeral containers may not be added by directly updating the pod spec. They must be added via the pod's ephemeralcontainers subresource, and they will appear in the pod spec once added. This is an alpha feature enabled by the EphemeralContainers feature flag. properties: args: description: 'Arguments to the entrypoint. The docker image''s CMD is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container''s environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable 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' items: type: string type: array command: description: 'Entrypoint array. Not executed within a shell. The docker image''s ENTRYPOINT is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container''s environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable 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' items: type: string type: array env: description: List of environment variables to set in the container. Cannot be updated. items: description: EnvVar represents an environment variable present in a Container. properties: name: description: Name of the environment variable. Must be a C_IDENTIFIER. type: string value: description: 'Variable references $(VAR_NAME) are expanded using the previous defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".' type: string valueFrom: description: Source for the environment variable's value. Cannot be used if value is not empty. properties: configMapKeyRef: description: Selects a key of a ConfigMap. properties: key: description: The key to select. type: string name: description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' type: string optional: description: Specify whether the ConfigMap or its key must be defined type: boolean required: - key type: object fieldRef: description: 'Selects a field of the pod: supports metadata.name, metadata.namespace, metadata.labels, metadata.annotations, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs.' properties: apiVersion: description: Version of the schema the FieldPath is written in terms of, defaults to "v1". type: string fieldPath: description: Path of the field to select in the specified API version. type: string required: - fieldPath type: object resourceFieldRef: description: 'Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.' properties: containerName: description: 'Container name: required for volumes, optional for env vars' type: string divisor: anyOf: - type: integer - type: string description: Specifies the output format of the exposed resources, defaults to "1" pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true resource: description: 'Required: resource to select' type: string required: - resource type: object secretKeyRef: description: Selects a key of a secret in the pod's namespace properties: key: description: The key of the secret to select from. Must be a valid secret key. type: string name: description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' type: string optional: description: Specify whether the Secret or its key must be defined type: boolean required: - key type: object type: object required: - name type: object type: array envFrom: description: List of sources to populate environment variables in the container. The keys defined within a source must be a C_IDENTIFIER. All invalid keys will be reported as an event when the container is starting. When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be updated. items: description: EnvFromSource represents the source of a set of ConfigMaps properties: configMapRef: description: The ConfigMap to select from properties: name: description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' type: string optional: description: Specify whether the ConfigMap must be defined type: boolean type: object prefix: description: An optional identifier to prepend to each key in the ConfigMap. Must be a C_IDENTIFIER. type: string secretRef: description: The Secret to select from properties: name: description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' type: string optional: description: Specify whether the Secret must be defined type: boolean type: object type: object type: array image: description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images' type: string imagePullPolicy: description: 'Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' type: string lifecycle: description: Lifecycle is not allowed for ephemeral containers. properties: postStart: description: 'PostStart is called immediately after a container is created. If the handler fails, the container is terminated and restarted according to its restart policy. Other management of the container blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' properties: exec: description: One and only one of the following should be specified. Exec specifies the action to take. properties: command: description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. items: type: string type: array type: object httpGet: description: HTTPGet specifies the http request to perform. properties: host: description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. type: string httpHeaders: description: Custom headers to set in the request. HTTP allows repeated headers. items: description: HTTPHeader describes a custom header to be used in HTTP probes properties: name: description: The header field name type: string value: description: The header field value type: string required: - name - value type: object type: array path: description: Path to access on the HTTP server. type: string port: anyOf: - type: integer - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. x-kubernetes-int-or-string: true scheme: description: Scheme to use for connecting to the host. Defaults to HTTP. type: string required: - port type: object tcpSocket: description: 'TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported TODO: implement a realistic TCP lifecycle hook' properties: host: description: 'Optional: Host name to connect to, defaults to the pod IP.' type: string port: anyOf: - type: integer - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. x-kubernetes-int-or-string: true required: - port type: object type: object preStop: description: 'PreStop is called immediately before a container is terminated due to an API request or management event such as liveness/startup probe failure, preemption, resource contention, etc. The handler is not called if the container crashes or exits. The reason for termination is passed to the handler. The Pod''s termination grace period countdown begins before the PreStop hooked is executed. Regardless of the outcome of the handler, the container will eventually terminate within the Pod''s termination grace period. Other management of the container blocks until the hook completes or until the termination grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' properties: exec: description: One and only one of the following should be specified. Exec specifies the action to take. properties: command: description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. items: type: string type: array type: object httpGet: description: HTTPGet specifies the http request to perform. properties: host: description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. type: string httpHeaders: description: Custom headers to set in the request. HTTP allows repeated headers. items: description: HTTPHeader describes a custom header to be used in HTTP probes properties: name: description: The header field name type: string value: description: The header field value type: string required: - name - value type: object type: array path: description: Path to access on the HTTP server. type: string port: anyOf: - type: integer - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. x-kubernetes-int-or-string: true scheme: description: Scheme to use for connecting to the host. Defaults to HTTP. type: string required: - port type: object tcpSocket: description: 'TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported TODO: implement a realistic TCP lifecycle hook' properties: host: description: 'Optional: Host name to connect to, defaults to the pod IP.' type: string port: anyOf: - type: integer - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. x-kubernetes-int-or-string: true required: - port type: object type: object type: object livenessProbe: description: Probes are not allowed for ephemeral containers. properties: exec: description: One and only one of the following should be specified. Exec specifies the action to take. properties: command: description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. items: type: string type: array type: object failureThreshold: description: Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1. format: int32 type: integer httpGet: description: HTTPGet specifies the http request to perform. properties: host: description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. type: string httpHeaders: description: Custom headers to set in the request. HTTP allows repeated headers. items: description: HTTPHeader describes a custom header to be used in HTTP probes properties: name: description: The header field name type: string value: description: The header field value type: string required: - name - value type: object type: array path: description: Path to access on the HTTP server. type: string port: anyOf: - type: integer - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. x-kubernetes-int-or-string: true scheme: description: Scheme to use for connecting to the host. Defaults to HTTP. type: string required: - port type: object initialDelaySeconds: description: 'Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' format: int32 type: integer periodSeconds: description: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1. format: int32 type: integer successThreshold: description: Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. format: int32 type: integer tcpSocket: description: 'TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported TODO: implement a realistic TCP lifecycle hook' properties: host: description: 'Optional: Host name to connect to, defaults to the pod IP.' type: string port: anyOf: - type: integer - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. x-kubernetes-int-or-string: true required: - port type: object timeoutSeconds: description: 'Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' format: int32 type: integer type: object name: description: Name of the ephemeral container specified as a DNS_LABEL. This name must be unique among all containers, init containers and ephemeral containers. type: string ports: description: Ports are not allowed for ephemeral containers. items: description: ContainerPort represents a network port in a single container. properties: containerPort: description: Number of port to expose on the pod's IP address. This must be a valid port number, 0 < x < 65536. format: int32 type: integer hostIP: description: What host IP to bind the external port to. type: string hostPort: description: Number of port to expose on the host. If specified, this must be a valid port number, 0 < x < 65536. If HostNetwork is specified, this must match ContainerPort. Most containers do not need this. format: int32 type: integer name: description: If specified, this must be an IANA_SVC_NAME and unique within the pod. Each named port in a pod must have a unique name. Name for the port that can be referred to by services. type: string protocol: description: Protocol for port. Must be UDP, TCP, or SCTP. Defaults to "TCP". type: string required: - containerPort - protocol type: object type: array readinessProbe: description: Probes are not allowed for ephemeral containers. properties: exec: description: One and only one of the following should be specified. Exec specifies the action to take. properties: command: description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. items: type: string type: array type: object failureThreshold: description: Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1. format: int32 type: integer httpGet: description: HTTPGet specifies the http request to perform. properties: host: description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. type: string httpHeaders: description: Custom headers to set in the request. HTTP allows repeated headers. items: description: HTTPHeader describes a custom header to be used in HTTP probes properties: name: description: The header field name type: string value: description: The header field value type: string required: - name - value type: object type: array path: description: Path to access on the HTTP server. type: string port: anyOf: - type: integer - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. x-kubernetes-int-or-string: true scheme: description: Scheme to use for connecting to the host. Defaults to HTTP. type: string required: - port type: object initialDelaySeconds: description: 'Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' format: int32 type: integer periodSeconds: description: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1. format: int32 type: integer successThreshold: description: Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. format: int32 type: integer tcpSocket: description: 'TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported TODO: implement a realistic TCP lifecycle hook' properties: host: description: 'Optional: Host name to connect to, defaults to the pod IP.' type: string port: anyOf: - type: integer - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. x-kubernetes-int-or-string: true required: - port type: object timeoutSeconds: description: 'Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' format: int32 type: integer type: object resources: description: Resources are not allowed for ephemeral containers. Ephemeral containers use spare resources already allocated to the pod. properties: limits: additionalProperties: anyOf: - type: integer - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true description: 'Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' type: object requests: additionalProperties: anyOf: - type: integer - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true description: 'Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' type: object type: object securityContext: description: SecurityContext is not allowed for ephemeral containers. properties: allowPrivilegeEscalation: description: 'AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. This bool directly controls if the no_new_privs flag will be set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN' type: boolean capabilities: description: The capabilities to add/drop when running containers. Defaults to the default set of capabilities granted by the container runtime. properties: add: description: Added capabilities items: description: Capability represent POSIX capabilities type type: string type: array drop: description: Removed capabilities items: description: Capability represent POSIX capabilities type type: string type: array type: object privileged: description: Run container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false. type: boolean procMount: description: procMount denotes the type of proc mount to use for the containers. The default is DefaultProcMount which uses the container runtime defaults for readonly paths and masked paths. This requires the ProcMountType feature flag to be enabled. type: string readOnlyRootFilesystem: description: Whether this container has a read-only root filesystem. Default is false. type: boolean runAsGroup: description: The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. format: int64 type: integer runAsNonRoot: description: Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. type: boolean runAsUser: description: The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. format: int64 type: integer seLinuxOptions: description: The SELinux context to be applied to the container. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. properties: level: description: Level is SELinux level label that applies to the container. type: string role: description: Role is a SELinux role label that applies to the container. type: string type: description: Type is a SELinux type label that applies to the container. type: string user: description: User is a SELinux user label that applies to the container. type: string type: object windowsOptions: description: The Windows specific settings applied to all containers. If unspecified, the options from the PodSecurityContext will be used. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. properties: gmsaCredentialSpec: description: GMSACredentialSpec is where the GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the GMSA credential spec named by the GMSACredentialSpecName field. type: string gmsaCredentialSpecName: description: GMSACredentialSpecName is the name of the GMSA credential spec to use. type: string runAsUserName: description: The UserName in Windows to run the entrypoint of the container process. Defaults to the user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. type: string type: object type: object startupProbe: description: Probes are not allowed for ephemeral containers. properties: exec: description: One and only one of the following should be specified. Exec specifies the action to take. properties: command: description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. items: type: string type: array type: object failureThreshold: description: Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1. format: int32 type: integer httpGet: description: HTTPGet specifies the http request to perform. properties: host: description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. type: string httpHeaders: description: Custom headers to set in the request. HTTP allows repeated headers. items: description: HTTPHeader describes a custom header to be used in HTTP probes properties: name: description: The header field name type: string value: description: The header field value type: string required: - name - value type: object type: array path: description: Path to access on the HTTP server. type: string port: anyOf: - type: integer - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. x-kubernetes-int-or-string: true scheme: description: Scheme to use for connecting to the host. Defaults to HTTP. type: string required: - port type: object initialDelaySeconds: description: 'Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' format: int32 type: integer periodSeconds: description: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1. format: int32 type: integer successThreshold: description: Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. format: int32 type: integer tcpSocket: description: 'TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported TODO: implement a realistic TCP lifecycle hook' properties: host: description: 'Optional: Host name to connect to, defaults to the pod IP.' type: string port: anyOf: - type: integer - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. x-kubernetes-int-or-string: true required: - port type: object timeoutSeconds: description: 'Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' format: int32 type: integer type: object stdin: description: Whether this container should allocate a buffer for stdin in the container runtime. If this is not set, reads from stdin in the container will always result in EOF. Default is false. type: boolean stdinOnce: description: Whether the container runtime should close the stdin channel after it has been opened by a single attach. When stdin is true the stdin stream will remain open across multiple attach sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the first client attaches to stdin, and then remains open and accepts data until the client disconnects, at which time stdin is closed and remains closed until the container is restarted. If this flag is false, a container processes that reads from stdin will never receive an EOF. Default is false type: boolean targetContainerName: description: If set, the name of the container from PodSpec that this ephemeral container targets. The ephemeral container will be run in the namespaces (IPC, PID, etc) of this container. If not set then the ephemeral container is run in whatever namespaces are shared for the pod. Note that the container runtime must support this feature. type: string terminationMessagePath: description: 'Optional: Path at which the file to which the container''s termination message will be written is mounted into the container''s filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.' type: string terminationMessagePolicy: description: Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated. type: string tty: description: Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. Default is false. type: boolean volumeDevices: description: volumeDevices is the list of block devices to be used by the container. items: description: volumeDevice describes a mapping of a raw block device within a container. properties: devicePath: description: devicePath is the path inside of the container that the device will be mapped to. type: string name: description: name must match the name of a persistentVolumeClaim in the pod type: string required: - devicePath - name type: object type: array volumeMounts: description: Pod volumes to mount into the container's filesystem. Cannot be updated. items: description: VolumeMount describes a mounting of a Volume within a container. properties: mountPath: description: Path within the container at which the volume should be mounted. Must not contain ':'. type: string mountPropagation: description: mountPropagation determines how mounts are propagated from the host to container and the other way around. When not set, MountPropagationNone is used. This field is beta in 1.10. type: string name: description: This must match the Name of a Volume. type: string readOnly: description: Mounted read-only if true, read-write otherwise (false or unspecified). Defaults to false. type: boolean subPath: description: Path within the volume from which the container's volume should be mounted. Defaults to "" (volume's root). type: string subPathExpr: description: Expanded path within the volume from which the container's volume should be mounted. Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. Defaults to "" (volume's root). SubPathExpr and SubPath are mutually exclusive. type: string required: - mountPath - name type: object type: array workingDir: description: Container's working directory. If not specified, the container runtime's default will be used, which might be configured in the container image. Cannot be updated. type: string required: - name type: object type: array hostAliases: description: HostAliases is an optional list of hosts and IPs that will be injected into the pod's hosts file if specified. This is only valid for non-hostNetwork pods. items: description: HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the pod's hosts file. properties: hostnames: description: Hostnames for the above IP address. items: type: string type: array ip: description: IP address of the host file entry. type: string type: object type: array hostIPC: description: 'Use the host''s ipc namespace. Optional: Default to false.' type: boolean hostNetwork: description: Host networking requested for this pod. Use the host's network namespace. If this option is set, the ports that will be used must be specified. Default to false. type: boolean hostPID: description: 'Use the host''s pid namespace. Optional: Default to false.' type: boolean hostname: description: Specifies the hostname of the Pod If not specified, the pod's hostname will be set to a system-defined value. type: string imagePullSecrets: description: 'ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec. If specified, these secrets will be passed to individual puller implementations for them to use. For example, in the case of docker, only DockerConfig type secrets are honored. More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod' items: description: LocalObjectReference contains enough information to let you locate the referenced object inside the same namespace. properties: name: description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' type: string type: object type: array initContainers: description: 'List of initialization containers belonging to the pod. Init containers are executed in order prior to containers being started. If any init container fails, the pod is considered to have failed and is handled according to its restartPolicy. The name for an init container or normal container must be unique among all containers. Init containers may not have Lifecycle actions, Readiness probes, Liveness probes, or Startup probes. The resourceRequirements of an init container are taken into account during scheduling by finding the highest request/limit for each resource type, and then using the max of of that value or the sum of the normal containers. Limits are applied to init containers in a similar fashion. Init containers cannot currently be added or removed. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/' items: description: A single application container that you want to run within a pod. properties: args: description: 'Arguments to the entrypoint. The docker image''s CMD is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container''s environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable 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' items: type: string type: array command: description: 'Entrypoint array. Not executed within a shell. The docker image''s ENTRYPOINT is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container''s environment. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable 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' items: type: string type: array env: description: List of environment variables to set in the container. Cannot be updated. items: description: EnvVar represents an environment variable present in a Container. properties: name: description: Name of the environment variable. Must be a C_IDENTIFIER. type: string value: description: 'Variable references $(VAR_NAME) are expanded using the previous defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".' type: string valueFrom: description: Source for the environment variable's value. Cannot be used if value is not empty. properties: configMapKeyRef: description: Selects a key of a ConfigMap. properties: key: description: The key to select. type: string name: description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' type: string optional: description: Specify whether the ConfigMap or its key must be defined type: boolean required: - key type: object fieldRef: description: 'Selects a field of the pod: supports metadata.name, metadata.namespace, metadata.labels, metadata.annotations, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs.' properties: apiVersion: description: Version of the schema the FieldPath is written in terms of, defaults to "v1". type: string fieldPath: description: Path of the field to select in the specified API version. type: string required: - fieldPath type: object resourceFieldRef: description: 'Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.' properties: containerName: description: 'Container name: required for volumes, optional for env vars' type: string divisor: anyOf: - type: integer - type: string description: Specifies the output format of the exposed resources, defaults to "1" pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true resource: description: 'Required: resource to select' type: string required: - resource type: object secretKeyRef: description: Selects a key of a secret in the pod's namespace properties: key: description: The key of the secret to select from. Must be a valid secret key. type: string name: description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' type: string optional: description: Specify whether the Secret or its key must be defined type: boolean required: - key type: object type: object required: - name type: object type: array envFrom: description: List of sources to populate environment variables in the container. The keys defined within a source must be a C_IDENTIFIER. All invalid keys will be reported as an event when the container is starting. When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be updated. items: description: EnvFromSource represents the source of a set of ConfigMaps properties: configMapRef: description: The ConfigMap to select from properties: name: description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' type: string optional: description: Specify whether the ConfigMap must be defined type: boolean type: object prefix: description: An optional identifier to prepend to each key in the ConfigMap. Must be a C_IDENTIFIER. type: string secretRef: description: The Secret to select from properties: name: description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' type: string optional: description: Specify whether the Secret must be defined type: boolean type: object type: object type: array image: description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images This field is optional to allow higher level config management to default or override container images in workload controllers like Deployments and StatefulSets.' type: string imagePullPolicy: description: 'Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images' type: string lifecycle: description: Actions that the management system should take in response to container lifecycle events. Cannot be updated. properties: postStart: description: 'PostStart is called immediately after a container is created. If the handler fails, the container is terminated and restarted according to its restart policy. Other management of the container blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' properties: exec: description: One and only one of the following should be specified. Exec specifies the action to take. properties: command: description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. items: type: string type: array type: object httpGet: description: HTTPGet specifies the http request to perform. properties: host: description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. type: string httpHeaders: description: Custom headers to set in the request. HTTP allows repeated headers. items: description: HTTPHeader describes a custom header to be used in HTTP probes properties: name: description: The header field name type: string value: description: The header field value type: string required: - name - value type: object type: array path: description: Path to access on the HTTP server. type: string port: anyOf: - type: integer - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. x-kubernetes-int-or-string: true scheme: description: Scheme to use for connecting to the host. Defaults to HTTP. type: string required: - port type: object tcpSocket: description: 'TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported TODO: implement a realistic TCP lifecycle hook' properties: host: description: 'Optional: Host name to connect to, defaults to the pod IP.' type: string port: anyOf: - type: integer - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. x-kubernetes-int-or-string: true required: - port type: object type: object preStop: description: 'PreStop is called immediately before a container is terminated due to an API request or management event such as liveness/startup probe failure, preemption, resource contention, etc. The handler is not called if the container crashes or exits. The reason for termination is passed to the handler. The Pod''s termination grace period countdown begins before the PreStop hooked is executed. Regardless of the outcome of the handler, the container will eventually terminate within the Pod''s termination grace period. Other management of the container blocks until the hook completes or until the termination grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' properties: exec: description: One and only one of the following should be specified. Exec specifies the action to take. properties: command: description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. items: type: string type: array type: object httpGet: description: HTTPGet specifies the http request to perform. properties: host: description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. type: string httpHeaders: description: Custom headers to set in the request. HTTP allows repeated headers. items: description: HTTPHeader describes a custom header to be used in HTTP probes properties: name: description: The header field name type: string value: description: The header field value type: string required: - name - value type: object type: array path: description: Path to access on the HTTP server. type: string port: anyOf: - type: integer - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. x-kubernetes-int-or-string: true scheme: description: Scheme to use for connecting to the host. Defaults to HTTP. type: string required: - port type: object tcpSocket: description: 'TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported TODO: implement a realistic TCP lifecycle hook' properties: host: description: 'Optional: Host name to connect to, defaults to the pod IP.' type: string port: anyOf: - type: integer - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. x-kubernetes-int-or-string: true required: - port type: object type: object type: object livenessProbe: description: 'Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' properties: exec: description: One and only one of the following should be specified. Exec specifies the action to take. properties: command: description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. items: type: string type: array type: object failureThreshold: description: Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1. format: int32 type: integer httpGet: description: HTTPGet specifies the http request to perform. properties: host: description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. type: string httpHeaders: description: Custom headers to set in the request. HTTP allows repeated headers. items: description: HTTPHeader describes a custom header to be used in HTTP probes properties: name: description: The header field name type: string value: description: The header field value type: string required: - name - value type: object type: array path: description: Path to access on the HTTP server. type: string port: anyOf: - type: integer - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. x-kubernetes-int-or-string: true scheme: description: Scheme to use for connecting to the host. Defaults to HTTP. type: string required: - port type: object initialDelaySeconds: description: 'Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' format: int32 type: integer periodSeconds: description: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1. format: int32 type: integer successThreshold: description: Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. format: int32 type: integer tcpSocket: description: 'TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported TODO: implement a realistic TCP lifecycle hook' properties: host: description: 'Optional: Host name to connect to, defaults to the pod IP.' type: string port: anyOf: - type: integer - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. x-kubernetes-int-or-string: true required: - port type: object timeoutSeconds: description: 'Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' format: int32 type: integer type: object name: description: Name of the container specified as a DNS_LABEL. Each container in a pod must have a unique name (DNS_LABEL). Cannot be updated. type: string ports: description: List of ports to expose from the container. Exposing a port here gives the system additional information about the network connections a container uses, but is primarily informational. Not specifying a port here DOES NOT prevent that port from being exposed. Any port which is listening on the default "0.0.0.0" address inside a container will be accessible from the network. Cannot be updated. items: description: ContainerPort represents a network port in a single container. properties: containerPort: description: Number of port to expose on the pod's IP address. This must be a valid port number, 0 < x < 65536. format: int32 type: integer hostIP: description: What host IP to bind the external port to. type: string hostPort: description: Number of port to expose on the host. If specified, this must be a valid port number, 0 < x < 65536. If HostNetwork is specified, this must match ContainerPort. Most containers do not need this. format: int32 type: integer name: description: If specified, this must be an IANA_SVC_NAME and unique within the pod. Each named port in a pod must have a unique name. Name for the port that can be referred to by services. type: string protocol: description: Protocol for port. Must be UDP, TCP, or SCTP. Defaults to "TCP". type: string required: - containerPort - protocol type: object type: array x-kubernetes-list-map-keys: - containerPort - protocol x-kubernetes-list-type: map readinessProbe: description: 'Periodic probe of container service readiness. Container will be removed from service endpoints if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' properties: exec: description: One and only one of the following should be specified. Exec specifies the action to take. properties: command: description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. items: type: string type: array type: object failureThreshold: description: Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1. format: int32 type: integer httpGet: description: HTTPGet specifies the http request to perform. properties: host: description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. type: string httpHeaders: description: Custom headers to set in the request. HTTP allows repeated headers. items: description: HTTPHeader describes a custom header to be used in HTTP probes properties: name: description: The header field name type: string value: description: The header field value type: string required: - name - value type: object type: array path: description: Path to access on the HTTP server. type: string port: anyOf: - type: integer - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. x-kubernetes-int-or-string: true scheme: description: Scheme to use for connecting to the host. Defaults to HTTP. type: string required: - port type: object initialDelaySeconds: description: 'Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' format: int32 type: integer periodSeconds: description: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1. format: int32 type: integer successThreshold: description: Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. format: int32 type: integer tcpSocket: description: 'TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported TODO: implement a realistic TCP lifecycle hook' properties: host: description: 'Optional: Host name to connect to, defaults to the pod IP.' type: string port: anyOf: - type: integer - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. x-kubernetes-int-or-string: true required: - port type: object timeoutSeconds: description: 'Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' format: int32 type: integer type: object resources: description: 'Compute Resources required by this container. Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' properties: limits: additionalProperties: anyOf: - type: integer - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true description: 'Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' type: object requests: additionalProperties: anyOf: - type: integer - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true description: 'Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' type: object type: object securityContext: description: 'Security options the pod should run with. More info: https://kubernetes.io/docs/concepts/policy/security-context/ More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/' properties: allowPrivilegeEscalation: description: 'AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. This bool directly controls if the no_new_privs flag will be set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN' type: boolean capabilities: description: The capabilities to add/drop when running containers. Defaults to the default set of capabilities granted by the container runtime. properties: add: description: Added capabilities items: description: Capability represent POSIX capabilities type type: string type: array drop: description: Removed capabilities items: description: Capability represent POSIX capabilities type type: string type: array type: object privileged: description: Run container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false. type: boolean procMount: description: procMount denotes the type of proc mount to use for the containers. The default is DefaultProcMount which uses the container runtime defaults for readonly paths and masked paths. This requires the ProcMountType feature flag to be enabled. type: string readOnlyRootFilesystem: description: Whether this container has a read-only root filesystem. Default is false. type: boolean runAsGroup: description: The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. format: int64 type: integer runAsNonRoot: description: Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. type: boolean runAsUser: description: The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. format: int64 type: integer seLinuxOptions: description: The SELinux context to be applied to the container. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. properties: level: description: Level is SELinux level label that applies to the container. type: string role: description: Role is a SELinux role label that applies to the container. type: string type: description: Type is a SELinux type label that applies to the container. type: string user: description: User is a SELinux user label that applies to the container. type: string type: object windowsOptions: description: The Windows specific settings applied to all containers. If unspecified, the options from the PodSecurityContext will be used. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. properties: gmsaCredentialSpec: description: GMSACredentialSpec is where the GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the GMSA credential spec named by the GMSACredentialSpecName field. type: string gmsaCredentialSpecName: description: GMSACredentialSpecName is the name of the GMSA credential spec to use. type: string runAsUserName: description: The UserName in Windows to run the entrypoint of the container process. Defaults to the user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. type: string type: object type: object startupProbe: description: 'StartupProbe indicates that the Pod has successfully initialized. If specified, no other probes are executed until this completes successfully. If this probe fails, the Pod will be restarted, just as if the livenessProbe failed. This can be used to provide different probe parameters at the beginning of a Pod''s lifecycle, when it might take a long time to load data or warm a cache, than during steady-state operation. This cannot be updated. This is a beta feature enabled by the StartupProbe feature flag. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' properties: exec: description: One and only one of the following should be specified. Exec specifies the action to take. properties: command: description: Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. items: type: string type: array type: object failureThreshold: description: Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1. format: int32 type: integer httpGet: description: HTTPGet specifies the http request to perform. properties: host: description: Host name to connect to, defaults to the pod IP. You probably want to set "Host" in httpHeaders instead. type: string httpHeaders: description: Custom headers to set in the request. HTTP allows repeated headers. items: description: HTTPHeader describes a custom header to be used in HTTP probes properties: name: description: The header field name type: string value: description: The header field value type: string required: - name - value type: object type: array path: description: Path to access on the HTTP server. type: string port: anyOf: - type: integer - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. x-kubernetes-int-or-string: true scheme: description: Scheme to use for connecting to the host. Defaults to HTTP. type: string required: - port type: object initialDelaySeconds: description: 'Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' format: int32 type: integer periodSeconds: description: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1. format: int32 type: integer successThreshold: description: Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. format: int32 type: integer tcpSocket: description: 'TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported TODO: implement a realistic TCP lifecycle hook' properties: host: description: 'Optional: Host name to connect to, defaults to the pod IP.' type: string port: anyOf: - type: integer - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. x-kubernetes-int-or-string: true required: - port type: object timeoutSeconds: description: 'Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' format: int32 type: integer type: object stdin: description: Whether this container should allocate a buffer for stdin in the container runtime. If this is not set, reads from stdin in the container will always result in EOF. Default is false. type: boolean stdinOnce: description: Whether the container runtime should close the stdin channel after it has been opened by a single attach. When stdin is true the stdin stream will remain open across multiple attach sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the first client attaches to stdin, and then remains open and accepts data until the client disconnects, at which time stdin is closed and remains closed until the container is restarted. If this flag is false, a container processes that reads from stdin will never receive an EOF. Default is false type: boolean terminationMessagePath: description: 'Optional: Path at which the file to which the container''s termination message will be written is mounted into the container''s filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.' type: string terminationMessagePolicy: description: Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated. type: string tty: description: Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. Default is false. type: boolean volumeDevices: description: volumeDevices is the list of block devices to be used by the container. items: description: volumeDevice describes a mapping of a raw block device within a container. properties: devicePath: description: devicePath is the path inside of the container that the device will be mapped to. type: string name: description: name must match the name of a persistentVolumeClaim in the pod type: string required: - devicePath - name type: object type: array volumeMounts: description: Pod volumes to mount into the container's filesystem. Cannot be updated. items: description: VolumeMount describes a mounting of a Volume within a container. properties: mountPath: description: Path within the container at which the volume should be mounted. Must not contain ':'. type: string mountPropagation: description: mountPropagation determines how mounts are propagated from the host to container and the other way around. When not set, MountPropagationNone is used. This field is beta in 1.10. type: string name: description: This must match the Name of a Volume. type: string readOnly: description: Mounted read-only if true, read-write otherwise (false or unspecified). Defaults to false. type: boolean subPath: description: Path within the volume from which the container's volume should be mounted. Defaults to "" (volume's root). type: string subPathExpr: description: Expanded path within the volume from which the container's volume should be mounted. Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. Defaults to "" (volume's root). SubPathExpr and SubPath are mutually exclusive. type: string required: - mountPath - name type: object type: array workingDir: description: Container's working directory. If not specified, the container runtime's default will be used, which might be configured in the container image. Cannot be updated. type: string required: - name type: object type: array nodeName: description: NodeName is a request to schedule this pod onto a specific node. If it is non-empty, the scheduler simply schedules this pod onto that node, assuming that it fits resource requirements. type: string nodeSelector: additionalProperties: type: string description: 'NodeSelector is a selector which must be true for the pod to fit on a node. Selector which must match a node''s labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' type: object overhead: additionalProperties: anyOf: - type: integer - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true description: 'Overhead represents the resource overhead associated with running a pod for a given RuntimeClass. This field will be autopopulated at admission time by the RuntimeClass admission controller. If the RuntimeClass admission controller is enabled, overhead must not be set in Pod create requests. The RuntimeClass admission controller will reject Pod create requests which have the overhead already set. If RuntimeClass is configured and selected in the PodSpec, Overhead will be set to the value defined in the corresponding RuntimeClass, otherwise it will remain unset and treated as zero. More info: https://git.k8s.io/enhancements/keps/sig-node/20190226-pod-overhead.md This field is alpha-level as of Kubernetes v1.16, and is only honored by servers that enable the PodOverhead feature.' type: object preemptionPolicy: description: PreemptionPolicy is the Policy for preempting pods with lower priority. One of Never, PreemptLowerPriority. Defaults to PreemptLowerPriority if unset. This field is alpha-level and is only honored by servers that enable the NonPreemptingPriority feature. type: string priority: description: The priority value. Various system components use this field to find the priority of the pod. When Priority Admission Controller is enabled, it prevents users from setting this field. The admission controller populates this field from PriorityClassName. The higher the value, the higher the priority. format: int32 type: integer priorityClassName: description: If specified, indicates the pod's priority. "system-node-critical" and "system-cluster-critical" are two special keywords which indicate the highest priorities with the former being the highest priority. Any other name must be defined by creating a PriorityClass object with that name. If not specified, the pod priority will be default or zero if there is no default. type: string readinessGates: description: 'If specified, all readiness gates will be evaluated for pod readiness. A pod is ready when all its containers are ready AND all conditions specified in the readiness gates have status equal to "True" More info: https://git.k8s.io/enhancements/keps/sig-network/0007-pod-ready%2B%2B.md' items: description: PodReadinessGate contains the reference to a pod condition properties: conditionType: description: ConditionType refers to a condition in the pod's condition list with matching type. type: string required: - conditionType type: object type: array restartPolicy: description: 'Restart policy for all containers within the pod. One of Always, OnFailure, Never. Default to Always. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#restart-policy' type: string runtimeClassName: description: 'RuntimeClassName refers to a RuntimeClass object in the node.k8s.io group, which should be used to run this pod. If no RuntimeClass resource matches the named class, the pod will not be run. If unset or empty, the "legacy" RuntimeClass will be used, which is an implicit class with an empty definition that uses the default runtime handler. More info: https://git.k8s.io/enhancements/keps/sig-node/runtime-class.md This is a beta feature as of Kubernetes v1.14.' type: string schedulerName: description: If specified, the pod will be dispatched by specified scheduler. If not specified, the pod will be dispatched by default scheduler. type: string securityContext: description: 'SecurityContext holds pod-level security attributes and common container settings. Optional: Defaults to empty. See type description for default values of each field.' properties: fsGroup: description: "A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to change the ownership of that volume to be owned by the pod: \n 1. The owning GID will be the FSGroup 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) 3. The permission bits are OR'd with rw-rw---- \n If unset, the Kubelet will not modify the ownership and permissions of any volume." format: int64 type: integer fsGroupChangePolicy: description: 'fsGroupChangePolicy defines behavior of changing ownership and permission of the volume before being exposed inside Pod. This field will only apply to volume types which support fsGroup based ownership(and permissions). It will have no effect on ephemeral volume types such as: secret, configmaps and emptydir. Valid values are "OnRootMismatch" and "Always". If not specified defaults to "Always".' type: string runAsGroup: description: The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container. format: int64 type: integer runAsNonRoot: description: Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. type: boolean runAsUser: description: The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container. format: int64 type: integer seLinuxOptions: description: The SELinux context to be applied to all containers. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container. properties: level: description: Level is SELinux level label that applies to the container. type: string role: description: Role is a SELinux role label that applies to the container. type: string type: description: Type is a SELinux type label that applies to the container. type: string user: description: User is a SELinux user label that applies to the container. type: string type: object supplementalGroups: description: A list of groups applied to the first process run in each container, in addition to the container's primary GID. If unspecified, no groups will be added to any container. items: format: int64 type: integer type: array sysctls: description: Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported sysctls (by the container runtime) might fail to launch. items: description: Sysctl defines a kernel parameter to be set properties: name: description: Name of a property to set type: string value: description: Value of a property to set type: string required: - name - value type: object type: array windowsOptions: description: The Windows specific settings applied to all containers. If unspecified, the options within a container's SecurityContext will be used. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. properties: gmsaCredentialSpec: description: GMSACredentialSpec is where the GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the GMSA credential spec named by the GMSACredentialSpecName field. type: string gmsaCredentialSpecName: description: GMSACredentialSpecName is the name of the GMSA credential spec to use. type: string runAsUserName: description: The UserName in Windows to run the entrypoint of the container process. Defaults to the user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. type: string type: object type: object serviceAccount: description: 'DeprecatedServiceAccount is a depreciated alias for ServiceAccountName. Deprecated: Use serviceAccountName instead.' type: string serviceAccountName: description: 'ServiceAccountName is the name of the ServiceAccount to use to run this pod. More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/' type: string shareProcessNamespace: description: 'Share a single process namespace between all of the containers in a pod. When this is set containers will be able to view and signal processes from other containers in the same pod, and the first process in each container will not be assigned PID 1. HostPID and ShareProcessNamespace cannot both be set. Optional: Default to false.' type: boolean subdomain: description: If specified, the fully qualified Pod hostname will be "...svc.". If not specified, the pod will not have a domainname at all. type: string terminationGracePeriodSeconds: description: Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period will be used instead. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. Defaults to 30 seconds. format: int64 type: integer tolerations: description: If specified, the pod's tolerations. items: description: The pod this Toleration is attached to tolerates any taint that matches the triple using the matching operator . properties: effect: description: Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. type: string key: description: Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys. type: string operator: description: Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. type: string tolerationSeconds: description: TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system. format: int64 type: integer value: description: Value is the taint value the toleration matches to. If the operator is Exists, the value should be empty, otherwise just a regular string. type: string type: object type: array topologySpreadConstraints: description: TopologySpreadConstraints describes how a group of pods ought to spread across topology domains. Scheduler will schedule pods in a way which abides by the constraints. This field is only honored by clusters that enable the EvenPodsSpread feature. All topologySpreadConstraints are ANDed. items: description: TopologySpreadConstraint specifies how to spread matching pods among the given topology. properties: labelSelector: description: LabelSelector is used to find matching pods. Pods that match this label selector are counted to determine the number of pods in their corresponding topology domain. properties: matchExpressions: description: matchExpressions is a list of label selector requirements. The requirements are ANDed. items: description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. properties: key: description: key is the label key that the selector applies to. type: string operator: description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. type: string values: description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. items: type: string type: array required: - key - operator type: object type: array matchLabels: additionalProperties: type: string description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object type: object maxSkew: description: 'MaxSkew describes the degree to which pods may be unevenly distributed. It''s the maximum permitted difference between the number of matching pods in any two topology domains of a given topology type. For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same labelSelector spread as 1/1/0: | zone1 | zone2 | zone3 | | P | P | | - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 1/1/1; scheduling it onto zone1(zone2) would make the ActualSkew(2-0) on zone1(zone2) violate MaxSkew(1). - if MaxSkew is 2, incoming pod can be scheduled onto any zone. It''s a required field. Default value is 1 and 0 is not allowed.' format: int32 type: integer topologyKey: description: TopologyKey is the key of node labels. Nodes that have a label with this key and identical values are considered to be in the same topology. We consider each as a "bucket", and try to put balanced number of pods into each bucket. It's a required field. type: string whenUnsatisfiable: description: 'WhenUnsatisfiable indicates how to deal with a pod if it doesn''t satisfy the spread constraint. - DoNotSchedule (default) tells the scheduler not to schedule it - ScheduleAnyway tells the scheduler to still schedule it It''s considered as "Unsatisfiable" if and only if placing incoming pod on any topology violates "MaxSkew". For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same labelSelector spread as 3/1/1: | zone1 | zone2 | zone3 | | P P P | P | P | If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler won''t make it *more* imbalanced. It''s a required field.' type: string required: - maxSkew - topologyKey - whenUnsatisfiable type: object type: array x-kubernetes-list-map-keys: - topologyKey - whenUnsatisfiable x-kubernetes-list-type: map volumes: description: 'List of volumes that can be mounted by containers belonging to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes' items: description: Volume represents a named volume in a pod that may be accessed by any container in the pod. properties: awsElasticBlockStore: description: 'AWSElasticBlockStore represents an AWS Disk resource that is attached to a kubelet''s host machine and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' properties: fsType: description: 'Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore TODO: how do we prevent errors in the filesystem from compromising the machine' type: string partition: description: 'The partition in the volume that you want to mount. If omitted, the default is to mount by volume name. Examples: For volume /dev/sda1, you specify the partition as "1". Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty).' format: int32 type: integer readOnly: description: 'Specify "true" to force and set the ReadOnly property in VolumeMounts to "true". If omitted, the default is "false". More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' type: boolean volumeID: description: 'Unique ID of the persistent disk resource in AWS (Amazon EBS volume). More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' type: string required: - volumeID type: object azureDisk: description: AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. properties: cachingMode: description: 'Host Caching mode: None, Read Only, Read Write.' type: string diskName: description: The Name of the data disk in the blob storage type: string diskURI: description: The URI the data disk in the blob storage type: string fsType: description: Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. type: string kind: description: 'Expected values Shared: multiple blob disks per storage account Dedicated: single blob disk per storage account Managed: azure managed data disk (only in managed availability set). defaults to shared' type: string readOnly: description: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. type: boolean required: - diskName - diskURI type: object azureFile: description: AzureFile represents an Azure File Service mount on the host and bind mount to the pod. properties: readOnly: description: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. type: boolean secretName: description: the name of secret that contains Azure Storage Account Name and Key type: string shareName: description: Share Name type: string required: - secretName - shareName type: object cephfs: description: CephFS represents a Ceph FS mount on the host that shares a pod's lifetime properties: monitors: description: 'Required: Monitors is a collection of Ceph monitors More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' items: type: string type: array path: description: 'Optional: Used as the mounted root, rather than the full Ceph tree, default is /' type: string readOnly: description: 'Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' type: boolean secretFile: description: 'Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' type: string secretRef: description: 'Optional: SecretRef is reference to the authentication secret for User, default is empty. More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' properties: name: description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' type: string type: object user: description: 'Optional: User is the rados user name, default is admin More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' type: string required: - monitors type: object cinder: description: 'Cinder represents a cinder volume attached and mounted on kubelets host machine. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' properties: fsType: description: 'Filesystem type to mount. Must be a filesystem type supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' type: string readOnly: description: 'Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' type: boolean secretRef: description: 'Optional: points to a secret object containing parameters used to connect to OpenStack.' properties: name: description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' type: string type: object volumeID: description: 'volume id used to identify the volume in cinder. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' type: string required: - volumeID type: object configMap: description: ConfigMap represents a configMap that should populate this volume properties: defaultMode: description: 'Optional: mode bits to use on created files by default. Must be a value between 0 and 0777. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' format: int32 type: integer items: description: If unspecified, each key-value pair in the Data field of the referenced ConfigMap will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the ConfigMap, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'. items: description: Maps a string key to a path within a volume. properties: key: description: The key to project. type: string mode: description: 'Optional: mode bits to use on this file, must be a value between 0 and 0777. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' format: int32 type: integer path: description: The relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'. type: string required: - key - path type: object type: array name: description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' type: string optional: description: Specify whether the ConfigMap or its keys must be defined type: boolean type: object csi: description: CSI (Container Storage Interface) represents storage that is handled by an external CSI driver (Alpha feature). properties: driver: description: Driver is the name of the CSI driver that handles this volume. Consult with your admin for the correct name as registered in the cluster. type: string fsType: description: Filesystem type to mount. Ex. "ext4", "xfs", "ntfs". If not provided, the empty value is passed to the associated CSI driver which will determine the default filesystem to apply. type: string nodePublishSecretRef: description: NodePublishSecretRef is a reference to the secret object containing sensitive information to pass to the CSI driver to complete the CSI NodePublishVolume and NodeUnpublishVolume calls. This field is optional, and may be empty if no secret is required. If the secret object contains more than one secret, all secret references are passed. properties: name: description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' type: string type: object readOnly: description: Specifies a read-only configuration for the volume. Defaults to false (read/write). type: boolean volumeAttributes: additionalProperties: type: string description: VolumeAttributes stores driver-specific properties that are passed to the CSI driver. Consult your driver's documentation for supported values. type: object required: - driver type: object downwardAPI: description: DownwardAPI represents downward API about the pod that should populate this volume properties: defaultMode: description: 'Optional: mode bits to use on created files by default. Must be a value between 0 and 0777. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' format: int32 type: integer items: description: Items is a list of downward API volume file items: description: DownwardAPIVolumeFile represents information to create the file containing the pod field properties: fieldRef: description: 'Required: Selects a field of the pod: only annotations, labels, name and namespace are supported.' properties: apiVersion: description: Version of the schema the FieldPath is written in terms of, defaults to "v1". type: string fieldPath: description: Path of the field to select in the specified API version. type: string required: - fieldPath type: object mode: description: 'Optional: mode bits to use on this file, must be a value between 0 and 0777. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' format: int32 type: integer path: description: 'Required: Path is the relative path name of the file to be created. Must not be absolute or contain the ''..'' path. Must be utf-8 encoded. The first item of the relative path must not start with ''..''' type: string resourceFieldRef: description: 'Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported.' properties: containerName: description: 'Container name: required for volumes, optional for env vars' type: string divisor: anyOf: - type: integer - type: string description: Specifies the output format of the exposed resources, defaults to "1" pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true resource: description: 'Required: resource to select' type: string required: - resource type: object required: - path type: object type: array type: object emptyDir: description: 'EmptyDir represents a temporary directory that shares a pod''s lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' properties: medium: description: 'What type of storage medium should back this directory. The default is "" which means to use the node''s default medium. Must be an empty string (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' type: string sizeLimit: anyOf: - type: integer - type: string description: 'Total amount of local storage required for this EmptyDir volume. The size limit is also applicable for memory medium. The maximum usage on memory medium EmptyDir would be the minimum value between the SizeLimit specified here and the sum of memory limits of all containers in a pod. The default is nil which means that the limit is undefined. More info: http://kubernetes.io/docs/user-guide/volumes#emptydir' pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true type: object fc: description: FC represents a Fibre Channel resource that is attached to a kubelet's host machine and then exposed to the pod. properties: fsType: description: 'Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. TODO: how do we prevent errors in the filesystem from compromising the machine' type: string lun: description: 'Optional: FC target lun number' format: int32 type: integer readOnly: description: 'Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.' type: boolean targetWWNs: description: 'Optional: FC target worldwide names (WWNs)' items: type: string type: array wwids: description: 'Optional: FC volume world wide identifiers (wwids) Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously.' items: type: string type: array type: object flexVolume: description: FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. properties: driver: description: Driver is the name of the driver to use for this volume. type: string fsType: description: Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script. type: string options: additionalProperties: type: string description: 'Optional: Extra command options if any.' type: object readOnly: description: 'Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.' type: boolean secretRef: description: 'Optional: SecretRef is reference to the secret object containing sensitive information to pass to the plugin scripts. This may be empty if no secret object is specified. If the secret object contains more than one secret, all secrets are passed to the plugin scripts.' properties: name: description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' type: string type: object required: - driver type: object flocker: description: Flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running properties: datasetName: description: Name of the dataset stored as metadata -> name on the dataset for Flocker should be considered as deprecated type: string datasetUUID: description: UUID of the dataset. This is unique identifier of a Flocker dataset type: string type: object gcePersistentDisk: description: 'GCEPersistentDisk represents a GCE Disk resource that is attached to a kubelet''s host machine and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' properties: fsType: description: 'Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk TODO: how do we prevent errors in the filesystem from compromising the machine' type: string partition: description: 'The partition in the volume that you want to mount. If omitted, the default is to mount by volume name. Examples: For volume /dev/sda1, you specify the partition as "1". Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' format: int32 type: integer pdName: description: 'Unique name of the PD resource in GCE. Used to identify the disk in GCE. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' type: string readOnly: description: 'ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' type: boolean required: - pdName type: object gitRepo: description: 'GitRepo represents a git repository at a particular revision. DEPRECATED: GitRepo is deprecated. To provision a container with a git repo, mount an EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir into the Pod''s container.' properties: directory: description: Target directory name. Must not contain or start with '..'. If '.' is supplied, the volume directory will be the git repository. Otherwise, if specified, the volume will contain the git repository in the subdirectory with the given name. type: string repository: description: Repository URL type: string revision: description: Commit hash for the specified revision. type: string required: - repository type: object glusterfs: description: 'Glusterfs represents a Glusterfs mount on the host that shares a pod''s lifetime. More info: https://examples.k8s.io/volumes/glusterfs/README.md' properties: endpoints: description: 'EndpointsName is the endpoint name that details Glusterfs topology. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' type: string path: description: 'Path is the Glusterfs volume path. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' type: string readOnly: description: 'ReadOnly here will force the Glusterfs volume to be mounted with read-only permissions. Defaults to false. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' type: boolean required: - endpoints - path type: object hostPath: description: 'HostPath represents a pre-existing file or directory on the host machine that is directly exposed to the container. This is generally used for system agents or other privileged things that are allowed to see the host machine. Most containers will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath --- TODO(jonesdl) We need to restrict who can use host directory mounts and who can/can not mount host directories as read/write.' properties: path: description: 'Path of the directory on the host. If the path is a symlink, it will follow the link to the real path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' type: string type: description: 'Type for HostPath Volume Defaults to "" More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' type: string required: - path type: object iscsi: description: 'ISCSI represents an ISCSI Disk resource that is attached to a kubelet''s host machine and then exposed to the pod. More info: https://examples.k8s.io/volumes/iscsi/README.md' properties: chapAuthDiscovery: description: whether support iSCSI Discovery CHAP authentication type: boolean chapAuthSession: description: whether support iSCSI Session CHAP authentication type: boolean fsType: description: 'Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi TODO: how do we prevent errors in the filesystem from compromising the machine' type: string initiatorName: description: Custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface : will be created for the connection. type: string iqn: description: Target iSCSI Qualified Name. type: string iscsiInterface: description: iSCSI Interface Name that uses an iSCSI transport. Defaults to 'default' (tcp). type: string lun: description: iSCSI Target Lun number. format: int32 type: integer portals: description: iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260). items: type: string type: array readOnly: description: ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. type: boolean secretRef: description: CHAP Secret for iSCSI target and initiator authentication properties: name: description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' type: string type: object targetPortal: description: iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260). type: string required: - iqn - lun - targetPortal type: object name: description: 'Volume''s name. Must be a DNS_LABEL and unique within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' type: string nfs: description: 'NFS represents an NFS mount on the host that shares a pod''s lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' properties: path: description: 'Path that is exported by the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' type: string readOnly: description: 'ReadOnly here will force the NFS export to be mounted with read-only permissions. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' type: boolean server: description: 'Server is the hostname or IP address of the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' type: string required: - path - server type: object persistentVolumeClaim: description: 'PersistentVolumeClaimVolumeSource represents a reference to a PersistentVolumeClaim in the same namespace. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' properties: claimName: description: 'ClaimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' type: string readOnly: description: Will force the ReadOnly setting in VolumeMounts. Default false. type: boolean required: - claimName type: object photonPersistentDisk: description: PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine properties: fsType: description: Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. type: string pdID: description: ID that identifies Photon Controller persistent disk type: string required: - pdID type: object portworxVolume: description: PortworxVolume represents a portworx volume attached and mounted on kubelets host machine properties: fsType: description: FSType represents the filesystem type to mount Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs". Implicitly inferred to be "ext4" if unspecified. type: string readOnly: description: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. type: boolean volumeID: description: VolumeID uniquely identifies a Portworx volume type: string required: - volumeID type: object projected: description: Items for all in one resources secrets, configmaps, and downward API properties: defaultMode: description: Mode bits to use on created files by default. Must be a value between 0 and 0777. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set. format: int32 type: integer sources: description: list of volume projections items: description: Projection that may be projected along with other supported volume types properties: configMap: description: information about the configMap data to project properties: items: description: If unspecified, each key-value pair in the Data field of the referenced ConfigMap will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the ConfigMap, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'. items: description: Maps a string key to a path within a volume. properties: key: description: The key to project. type: string mode: description: 'Optional: mode bits to use on this file, must be a value between 0 and 0777. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' format: int32 type: integer path: description: The relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'. type: string required: - key - path type: object type: array name: description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' type: string optional: description: Specify whether the ConfigMap or its keys must be defined type: boolean type: object downwardAPI: description: information about the downwardAPI data to project properties: items: description: Items is a list of DownwardAPIVolume file items: description: DownwardAPIVolumeFile represents information to create the file containing the pod field properties: fieldRef: description: 'Required: Selects a field of the pod: only annotations, labels, name and namespace are supported.' properties: apiVersion: description: Version of the schema the FieldPath is written in terms of, defaults to "v1". type: string fieldPath: description: Path of the field to select in the specified API version. type: string required: - fieldPath type: object mode: description: 'Optional: mode bits to use on this file, must be a value between 0 and 0777. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' format: int32 type: integer path: description: 'Required: Path is the relative path name of the file to be created. Must not be absolute or contain the ''..'' path. Must be utf-8 encoded. The first item of the relative path must not start with ''..''' type: string resourceFieldRef: description: 'Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported.' properties: containerName: description: 'Container name: required for volumes, optional for env vars' type: string divisor: anyOf: - type: integer - type: string description: Specifies the output format of the exposed resources, defaults to "1" pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true resource: description: 'Required: resource to select' type: string required: - resource type: object required: - path type: object type: array type: object secret: description: information about the secret data to project properties: items: description: If unspecified, each key-value pair in the Data field of the referenced Secret will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the Secret, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'. items: description: Maps a string key to a path within a volume. properties: key: description: The key to project. type: string mode: description: 'Optional: mode bits to use on this file, must be a value between 0 and 0777. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' format: int32 type: integer path: description: The relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'. type: string required: - key - path type: object type: array name: description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' type: string optional: description: Specify whether the Secret or its key must be defined type: boolean type: object serviceAccountToken: description: information about the serviceAccountToken data to project properties: audience: description: Audience is the intended audience of the token. A recipient of a token must identify itself with an identifier specified in the audience of the token, and otherwise should reject the token. The audience defaults to the identifier of the apiserver. type: string expirationSeconds: description: ExpirationSeconds is the requested duration of validity of the service account token. As the token approaches expiration, the kubelet volume plugin will proactively rotate the service account token. The kubelet will start trying to rotate the token if the token is older than 80 percent of its time to live or if the token is older than 24 hours.Defaults to 1 hour and must be at least 10 minutes. format: int64 type: integer path: description: Path is the path relative to the mount point of the file to project the token into. type: string required: - path type: object type: object type: array required: - sources type: object quobyte: description: Quobyte represents a Quobyte mount on the host that shares a pod's lifetime properties: group: description: Group to map volume access to Default is no group type: string readOnly: description: ReadOnly here will force the Quobyte volume to be mounted with read-only permissions. Defaults to false. type: boolean registry: description: Registry represents a single or multiple Quobyte Registry services specified as a string as host:port pair (multiple entries are separated with commas) which acts as the central registry for volumes type: string tenant: description: Tenant owning the given Quobyte volume in the Backend Used with dynamically provisioned Quobyte volumes, value is set by the plugin type: string user: description: User to map volume access to Defaults to serivceaccount user type: string volume: description: Volume is a string that references an already created Quobyte volume by name. type: string required: - registry - volume type: object rbd: description: 'RBD represents a Rados Block Device mount on the host that shares a pod''s lifetime. More info: https://examples.k8s.io/volumes/rbd/README.md' properties: fsType: description: 'Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd TODO: how do we prevent errors in the filesystem from compromising the machine' type: string image: description: 'The rados image name. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' type: string keyring: description: 'Keyring is the path to key ring for RBDUser. Default is /etc/ceph/keyring. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' type: string monitors: description: 'A collection of Ceph monitors. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' items: type: string type: array pool: description: 'The rados pool name. Default is rbd. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' type: string readOnly: description: 'ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' type: boolean secretRef: description: 'SecretRef is name of the authentication secret for RBDUser. If provided overrides keyring. Default is nil. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' properties: name: description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' type: string type: object user: description: 'The rados user name. Default is admin. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' type: string required: - image - monitors type: object scaleIO: description: ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. properties: fsType: description: Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Default is "xfs". type: string gateway: description: The host address of the ScaleIO API Gateway. type: string protectionDomain: description: The name of the ScaleIO Protection Domain for the configured storage. type: string readOnly: description: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. type: boolean secretRef: description: SecretRef references to the secret for ScaleIO user and other sensitive information. If this is not provided, Login operation will fail. properties: name: description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' type: string type: object sslEnabled: description: Flag to enable/disable SSL communication with Gateway, default false type: boolean storageMode: description: Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. Default is ThinProvisioned. type: string storagePool: description: The ScaleIO Storage Pool associated with the protection domain. type: string system: description: The name of the storage system as configured in ScaleIO. type: string volumeName: description: The name of a volume already created in the ScaleIO system that is associated with this volume source. type: string required: - gateway - secretRef - system type: object secret: description: 'Secret represents a secret that should populate this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' properties: defaultMode: description: 'Optional: mode bits to use on created files by default. Must be a value between 0 and 0777. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' format: int32 type: integer items: description: If unspecified, each key-value pair in the Data field of the referenced Secret will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the Secret, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'. items: description: Maps a string key to a path within a volume. properties: key: description: The key to project. type: string mode: description: 'Optional: mode bits to use on this file, must be a value between 0 and 0777. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.' format: int32 type: integer path: description: The relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'. type: string required: - key - path type: object type: array optional: description: Specify whether the Secret or its keys must be defined type: boolean secretName: description: 'Name of the secret in the pod''s namespace to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' type: string type: object storageos: description: StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes. properties: fsType: description: Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. type: string readOnly: description: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. type: boolean secretRef: description: SecretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted. properties: name: description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' type: string type: object volumeName: description: VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace. type: string volumeNamespace: description: VolumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod's namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to "default" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created. type: string type: object vsphereVolume: description: VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine properties: fsType: description: Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. type: string storagePolicyID: description: Storage Policy Based Management (SPBM) profile ID associated with the StoragePolicyName. type: string storagePolicyName: description: Storage Policy Based Management (SPBM) profile name. type: string volumePath: description: Path that identifies vSphere volume vmdk type: string required: - volumePath type: object required: - name type: object type: array required: - containers type: object type: object ttlSecondsAfterFinished: description: ttlSecondsAfterFinished limits the lifetime of a Job that has finished execution (either Complete or Failed). If this field is set, ttlSecondsAfterFinished after the Job finishes, it is eligible to be automatically deleted. When the Job is being deleted, its lifecycle guarantees (e.g. finalizers) will be honored. If this field is unset, the Job won't be automatically deleted. If this field is set to zero, the Job becomes eligible to be deleted immediately after it finishes. This field is alpha-level and is only honored by servers that enable the TTLAfterFinished feature. format: int32 type: integer required: - template type: object maxReplicaCount: format: int32 type: integer pollingInterval: format: int32 type: integer successfulJobsHistoryLimit: format: int32 type: integer triggers: items: description: ScaleTriggers reference the scaler that will be used properties: authenticationRef: description: ScaledObjectAuthRef points to the TriggerAuthentication object that is used to authenticate the scaler with the environment properties: name: type: string required: - name type: object metadata: additionalProperties: type: string type: object name: type: string type: type: string required: - metadata - type type: object type: array required: - jobTargetRef - triggers type: object status: description: ScaledJobStatus defines the observed state of ScaledJob properties: conditions: description: Conditions an array representation to store multiple Conditions items: description: Condition to store the condition state properties: message: description: A human readable message indicating details about the transition. type: string reason: description: The reason for the condition's last transition. type: string status: description: Status of the condition, one of True, False, Unknown. type: string type: description: Type of condition type: string required: - status - type type: object type: array lastActiveTime: format: date-time type: string type: object type: object version: v1alpha1 versions: - name: v1alpha1 served: true storage: true status: acceptedNames: kind: "" plural: "" conditions: [] storedVersions: [] --- apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.3.0 creationTimestamp: null labels: app.kubernetes.io/part-of: keda-operator app.kubernetes.io/version: 2.0.0-beta name: scaledobjects.keda.sh spec: additionalPrinterColumns: - JSONPath: .status.scaleTargetKind name: ScaleTargetKind type: string - JSONPath: .spec.scaleTargetRef.name name: ScaleTargetName type: string - JSONPath: .spec.triggers[*].type name: Triggers type: string - JSONPath: .spec.triggers[*].authenticationRef.name name: Authentication type: string - JSONPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string - JSONPath: .status.conditions[?(@.type=="Active")].status name: Active type: string - JSONPath: .metadata.creationTimestamp name: Age type: date group: keda.sh names: kind: ScaledObject listKind: ScaledObjectList plural: scaledobjects shortNames: - so singular: scaledobject scope: Namespaced subresources: status: {} validation: openAPIV3Schema: description: ScaledObject is a specification for a ScaledObject resource properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object spec: description: ScaledObjectSpec is the spec for a ScaledObject resource properties: advanced: description: AdvancedConfig specifies advance scaling options properties: horizontalPodAutoscalerConfig: description: HorizontalPodAutoscalerConfig specifies horizontal scale config properties: behavior: description: HorizontalPodAutoscalerBehavior configures the scaling behavior of the target in both Up and Down directions (scaleUp and scaleDown fields respectively). properties: scaleDown: description: scaleDown is scaling policy for scaling Down. If not set, the default value is to allow to scale down to minReplicas pods, with a 300 second stabilization window (i.e., the highest recommendation for the last 300sec is used). properties: policies: description: policies is a list of potential scaling polices which can be used during scaling. At least one policy must be specified, otherwise the HPAScalingRules will be discarded as invalid items: description: HPAScalingPolicy is a single policy which must hold true for a specified past interval. properties: periodSeconds: description: PeriodSeconds specifies the window of time for which the policy should hold true. PeriodSeconds must be greater than zero and less than or equal to 1800 (30 min). format: int32 type: integer type: description: Type is used to specify the scaling policy. type: string value: description: Value contains the amount of change which is permitted by the policy. It must be greater than zero format: int32 type: integer required: - periodSeconds - type - value type: object type: array selectPolicy: description: selectPolicy is used to specify which policy should be used. If not set, the default value MaxPolicySelect is used. type: string stabilizationWindowSeconds: description: 'StabilizationWindowSeconds is the number of seconds for which past recommendations should be considered while scaling up or scaling down. StabilizationWindowSeconds must be greater than or equal to zero and less than or equal to 3600 (one hour). If not set, use the default values: - For scale up: 0 (i.e. no stabilization is done). - For scale down: 300 (i.e. the stabilization window is 300 seconds long).' format: int32 type: integer type: object scaleUp: description: 'scaleUp is scaling policy for scaling Up. If not set, the default value is the higher of: * increase no more than 4 pods per 60 seconds * double the number of pods per 60 seconds No stabilization is used.' properties: policies: description: policies is a list of potential scaling polices which can be used during scaling. At least one policy must be specified, otherwise the HPAScalingRules will be discarded as invalid items: description: HPAScalingPolicy is a single policy which must hold true for a specified past interval. properties: periodSeconds: description: PeriodSeconds specifies the window of time for which the policy should hold true. PeriodSeconds must be greater than zero and less than or equal to 1800 (30 min). format: int32 type: integer type: description: Type is used to specify the scaling policy. type: string value: description: Value contains the amount of change which is permitted by the policy. It must be greater than zero format: int32 type: integer required: - periodSeconds - type - value type: object type: array selectPolicy: description: selectPolicy is used to specify which policy should be used. If not set, the default value MaxPolicySelect is used. type: string stabilizationWindowSeconds: description: 'StabilizationWindowSeconds is the number of seconds for which past recommendations should be considered while scaling up or scaling down. StabilizationWindowSeconds must be greater than or equal to zero and less than or equal to 3600 (one hour). If not set, use the default values: - For scale up: 0 (i.e. no stabilization is done). - For scale down: 300 (i.e. the stabilization window is 300 seconds long).' format: int32 type: integer type: object type: object resourceMetrics: items: description: ResourceMetricSource indicates how to scale on a resource metric known to Kubernetes, as specified in requests and limits, describing each pod in the current scale target (e.g. CPU or memory). The values will be averaged together before being compared to the target. Such metrics are built in to Kubernetes, and have special scaling options on top of those available to normal per-pod metrics using the "pods" source. Only one "target" type should be set. properties: name: description: name is the name of the resource in question. type: string target: description: target specifies the target value for the given metric properties: averageUtilization: description: averageUtilization is the target value of the average of the resource metric across all relevant pods, represented as a percentage of the requested value of the resource for the pods. Currently only valid for Resource metric source type format: int32 type: integer averageValue: anyOf: - type: integer - type: string description: averageValue is the target value of the average of the metric across all relevant pods (as a quantity) pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true type: description: type represents whether the metric type is Utilization, Value, or AverageValue type: string value: anyOf: - type: integer - type: string description: value is the target value of the metric (as a quantity). pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true required: - type type: object required: - name - target type: object type: array type: object restoreToOriginalReplicaCount: type: boolean type: object cooldownPeriod: format: int32 type: integer maxReplicaCount: format: int32 type: integer minReplicaCount: format: int32 type: integer pollingInterval: format: int32 type: integer scaleTargetRef: description: ScaleTarget holds the a reference to the scale target Object properties: apiVersion: type: string envSourceContainerName: type: string kind: type: string name: type: string required: - name type: object triggers: items: description: ScaleTriggers reference the scaler that will be used properties: authenticationRef: description: ScaledObjectAuthRef points to the TriggerAuthentication object that is used to authenticate the scaler with the environment properties: name: type: string required: - name type: object metadata: additionalProperties: type: string type: object name: type: string type: type: string required: - metadata - type type: object type: array required: - scaleTargetRef - triggers type: object status: description: ScaledObjectStatus is the status for a ScaledObject resource properties: conditions: description: Conditions an array representation to store multiple Conditions items: description: Condition to store the condition state properties: message: description: A human readable message indicating details about the transition. type: string reason: description: The reason for the condition's last transition. type: string status: description: Status of the condition, one of True, False, Unknown. type: string type: description: Type of condition type: string required: - status - type type: object type: array externalMetricNames: items: type: string type: array lastActiveTime: format: date-time type: string originalReplicaCount: format: int32 type: integer scaleTargetGVKR: description: GroupVersionKindResource provides unified structure for schema.GroupVersionKind and Resource properties: group: type: string kind: type: string resource: type: string version: type: string required: - group - kind - resource - version type: object scaleTargetKind: type: string type: object required: - spec type: object version: v1alpha1 versions: - name: v1alpha1 served: true storage: true status: acceptedNames: kind: "" plural: "" conditions: [] storedVersions: [] --- apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.3.0 creationTimestamp: null labels: app.kubernetes.io/part-of: keda-operator app.kubernetes.io/version: 2.0.0-beta name: triggerauthentications.keda.sh spec: additionalPrinterColumns: - JSONPath: .spec.podIdentity.provider name: PodIdentity type: string - JSONPath: .spec.secretTargetRef[*].name name: Secret type: string - JSONPath: .spec.env[*].name name: Env type: string group: keda.sh names: kind: TriggerAuthentication listKind: TriggerAuthenticationList plural: triggerauthentications shortNames: - ta - triggerauth singular: triggerauthentication scope: Namespaced subresources: {} validation: openAPIV3Schema: description: TriggerAuthentication defines how a trigger can authenticate properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object spec: description: TriggerAuthenticationSpec defines the various ways to authenticate properties: env: items: description: AuthEnvironment is used to authenticate using environment variables in the destination ScaleTarget spec properties: containerName: type: string name: type: string parameter: type: string required: - name - parameter type: object type: array hashiCorpVault: description: HashiCorpVault is used to authenticate using Hashicorp Vault properties: address: type: string authentication: description: VaultAuthentication contains the list of Hashicorp Vault authentication methods type: string credential: description: Credential defines the Hashicorp Vault credentials depending on the authentication method properties: serviceAccount: type: string token: type: string type: object mount: type: string role: type: string secrets: items: description: VaultSecret defines the mapping between the path of the secret in Vault to the parameter properties: key: type: string parameter: type: string path: type: string required: - key - parameter - path type: object type: array required: - address - authentication - secrets type: object podIdentity: description: AuthPodIdentity allows users to select the platform native identity mechanism properties: provider: description: PodIdentityProvider contains the list of providers type: string required: - provider type: object secretTargetRef: items: description: AuthSecretTargetRef is used to authenticate using a reference to a secret properties: key: type: string name: type: string parameter: type: string required: - key - name - parameter type: object type: array type: object required: - spec type: object version: v1alpha1 versions: - name: v1alpha1 served: true storage: true status: acceptedNames: kind: "" plural: "" conditions: [] storedVersions: [] --- apiVersion: v1 kind: ServiceAccount metadata: labels: app.kubernetes.io/name: keda-operator app.kubernetes.io/part-of: keda-operator app.kubernetes.io/version: 2.0.0-beta name: keda-operator namespace: keda --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: labels: app.kubernetes.io/name: keda-external-metrics-reader app.kubernetes.io/part-of: keda-operator app.kubernetes.io/version: 2.0.0-beta name: keda-external-metrics-reader rules: - apiGroups: - external.metrics.k8s.io resources: - '*' verbs: - '*' --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: creationTimestamp: null labels: app.kubernetes.io/name: keda-operator app.kubernetes.io/part-of: keda-operator app.kubernetes.io/version: 2.0.0-beta name: keda-operator rules: - apiGroups: - "" resources: - configmaps - configmaps/status - events verbs: - '*' - apiGroups: - "" resources: - external - pods - secrets - services verbs: - get - list - watch - apiGroups: - '*' resources: - '*' verbs: - get - apiGroups: - '*' resources: - '*/scale' verbs: - '*' - apiGroups: - autoscaling resources: - horizontalpodautoscalers verbs: - '*' - apiGroups: - batch resources: - jobs verbs: - '*' - apiGroups: - keda.sh resources: - scaledjobs - scaledjobs/status verbs: - '*' - apiGroups: - keda.sh resources: - scaledobjects - scaledobjects/finalizers - scaledobjects/status verbs: - '*' - apiGroups: - keda.sh resources: - triggerauthentications - triggerauthentications/status verbs: - '*' --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: labels: app.kubernetes.io/name: keda-auth-reader app.kubernetes.io/part-of: keda-operator app.kubernetes.io/version: 2.0.0-beta name: keda-auth-reader namespace: keda roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: extension-apiserver-authentication-reader subjects: - kind: ServiceAccount name: keda-operator namespace: keda --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: labels: app.kubernetes.io/name: keda-hpa-controller-external-metrics app.kubernetes.io/part-of: keda-operator app.kubernetes.io/version: 2.0.0-beta name: keda-hpa-controller-external-metrics roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: keda-external-metrics-reader subjects: - kind: ServiceAccount name: horizontal-pod-autoscaler namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: labels: app.kubernetes.io/name: keda-operator app.kubernetes.io/part-of: keda-operator app.kubernetes.io/version: 2.0.0-beta name: keda-operator roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: keda-operator subjects: - kind: ServiceAccount name: keda-operator namespace: keda --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: labels: app.kubernetes.io/name: keda-system-auth-delegator app.kubernetes.io/part-of: keda-operator app.kubernetes.io/version: 2.0.0-beta name: keda:system:auth-delegator roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: system:auth-delegator subjects: - kind: ServiceAccount name: keda-operator namespace: keda --- apiVersion: v1 kind: Service metadata: labels: app.kubernetes.io/name: keda-metrics-apiserver app.kubernetes.io/part-of: keda-operator app.kubernetes.io/version: 2.0.0-beta name: keda-metrics-apiserver namespace: keda spec: ports: - name: https port: 443 targetPort: 6443 - name: http port: 80 targetPort: 8080 selector: app: keda-metrics-apiserver app.kubernetes.io/part-of: keda-operator app.kubernetes.io/version: 2.0.0-beta --- apiVersion: apps/v1 kind: Deployment metadata: labels: app: keda-metrics-apiserver app.kubernetes.io/name: keda-metrics-apiserver app.kubernetes.io/part-of: keda-operator app.kubernetes.io/version: 2.0.0-beta name: keda-metrics-apiserver namespace: keda spec: replicas: 1 selector: matchLabels: app: keda-metrics-apiserver app.kubernetes.io/part-of: keda-operator app.kubernetes.io/version: 2.0.0-beta template: metadata: labels: app: keda-metrics-apiserver app.kubernetes.io/part-of: keda-operator app.kubernetes.io/version: 2.0.0-beta name: keda-metrics-apiserver spec: containers: - args: - /usr/local/bin/keda-adapter - --secure-port=6443 - --logtostderr=true - --v=4 env: - name: WATCH_NAMESPACE value: "" image: docker.io/kedacore/keda-metrics-apiserver:2.0.0-beta imagePullPolicy: Always livenessProbe: httpGet: path: /healthz port: 6443 scheme: HTTPS initialDelaySeconds: 5 name: keda-metrics-apiserver ports: - containerPort: 6443 name: https - containerPort: 8080 name: http readinessProbe: httpGet: path: /readyz port: 6443 scheme: HTTPS initialDelaySeconds: 5 resources: limits: cpu: 1000m memory: 1000Mi requests: cpu: 100m memory: 100Mi volumeMounts: - mountPath: /tmp name: temp-vol nodeSelector: beta.kubernetes.io/os: linux serviceAccountName: keda-operator volumes: - emptyDir: {} name: temp-vol --- apiVersion: apps/v1 kind: Deployment metadata: labels: app: keda-operator app.kubernetes.io/component: operator app.kubernetes.io/name: keda-operator app.kubernetes.io/part-of: keda-operator app.kubernetes.io/version: 2.0.0-beta name: keda-operator namespace: keda spec: replicas: 1 selector: matchLabels: app: keda-operator app.kubernetes.io/part-of: keda-operator app.kubernetes.io/version: 2.0.0-beta template: metadata: labels: app: keda-operator app.kubernetes.io/part-of: keda-operator app.kubernetes.io/version: 2.0.0-beta name: keda-operator name: keda-operator spec: containers: - args: - --enable-leader-election - --zap-log-level=debug - --zap-encoder=console command: - /keda env: - name: WATCH_NAMESPACE value: "" image: docker.io/kedacore/keda:2.0.0-beta imagePullPolicy: Always livenessProbe: httpGet: path: /healthz port: 8081 initialDelaySeconds: 25 name: keda-operator readinessProbe: httpGet: path: /readyz port: 8081 initialDelaySeconds: 20 resources: limits: cpu: 1000m memory: 1000Mi requests: cpu: 100m memory: 100Mi nodeSelector: beta.kubernetes.io/os: linux serviceAccountName: keda-operator terminationGracePeriodSeconds: 10 --- apiVersion: apiregistration.k8s.io/v1 kind: APIService metadata: labels: app.kubernetes.io/name: v1beta1.external.metrics.k8s.io app.kubernetes.io/part-of: keda-operator app.kubernetes.io/version: 2.0.0-beta name: v1beta1.external.metrics.k8s.io spec: group: external.metrics.k8s.io groupPriorityMinimum: 100 insecureSkipTLSVerify: true service: name: keda-metrics-apiserver namespace: keda version: v1beta1 versionPriority: 100 ================================================ FILE: autoscaling-on-queue/deployment/producer.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: autoscaling-producer labels: app: autoscaling-producer spec: selector: matchLabels: app: autoscaling-producer template: metadata: labels: app: autoscaling-producer annotations: dapr.io/enabled: "true" dapr.io/app-id: "autoscaling-producer" dapr.io/app-protocol: "grpc" dapr.io/app-port: "60034" dapr.io/log-level: "debug" dapr.io/log-as-json: "true" spec: containers: - name: service image: mchmarny/autoscaling-producer:v0.11.1 ports: - containerPort: 60034 env: - name: PUBSUB_NAME value: autoscaling-pubsub - name: TOPIC_NAME value: metric - name: NUMBER_OF_PUBLISHERS value: "2" - name: PUBLISHERS_FREQ value: "100ms" - name: PUBLISHERS_DELAY value: "10s" - name: LOG_FREQ value: "3s" ================================================ FILE: autoscaling-on-queue/deployment/subscriber-scaler.yaml ================================================ apiVersion: keda.sh/v1alpha1 kind: ScaledObject metadata: name: subscriber-scaler spec: scaleTargetRef: name: autoscaling-subscriber pollingInterval: 15 minReplicaCount: 0 maxReplicaCount: 10 cooldownPeriod: 30 triggers: - type: kafka metadata: topic: metric bootstrapServers: kafka-cp-kafka.kafka.svc.cluster.local:9092 consumerGroup: autoscaling-subscriber lagThreshold: "3" ================================================ FILE: autoscaling-on-queue/deployment/subscriber.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: autoscaling-subscriber labels: app: autoscaling-subscriber spec: selector: matchLabels: app: autoscaling-subscriber template: metadata: labels: app: autoscaling-subscriber annotations: dapr.io/enabled: "true" dapr.io/app-id: "autoscaling-subscriber" dapr.io/app-protocol: "grpc" dapr.io/app-port: "60033" dapr.io/log-level: "debug" dapr.io/log-as-json: "true" spec: containers: - name: service image: mchmarny/autoscaling-subscriber:v0.11.1 ports: - containerPort: 60033 env: - name: PUBSUB_NAME value: autoscaling-pubsub - name: TOPIC_NAME value: metric - name: PROCESS_DURATION value: "300ms" ================================================ FILE: autoscaling-on-queue/producer/Dockerfile ================================================ FROM golang:1.15.0 as builder WORKDIR /src/ COPY . /src/ ENV GO111MODULE=on RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -a -tags netgo -mod vendor -o ./producer . FROM gcr.io/distroless/static:nonroot COPY --from=builder /src/producer . ENTRYPOINT ["./producer"] ================================================ FILE: autoscaling-on-queue/producer/Makefile ================================================ RELEASE_VERSION =v0.11.1 SERVICE_NAME ?=autoscaling-producer DOCKER_USERNAME ?=$(DOCKER_USER) .PHONY: all all: help .PHONY: tidy tidy: ## Updates the go modules and vendors all dependencies go mod tidy go mod vendor .PHONY: run run: tidy ## Runs uncompiled code NUMBER_OF_PUBLISHERS=3 PUBLISH_TO_CONSOLE=true go run main.go .PHONY: image image: tidy ## Builds and publishes docker image docker build -t "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" . docker push "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" .PHONY: lint lint: ## Lints the entire project golangci-lint run --timeout=3m .PHONY: tag tag: ## Creates release tag git tag $(RELEASE_VERSION) git push origin $(RELEASE_VERSION) .PHONY: clean clean: ## Cleans up generated files go clean rm -fr ./bin rm -fr ./vendor .PHONY: help help: ## Display available commands @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \ 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ================================================ FILE: autoscaling-on-queue/producer/go.mod ================================================ module github.com/mchmarny/dapr-demos/autoscaling-on-queue/producer go 1.15 require ( github.com/dapr/go-sdk v0.11.0 github.com/google/uuid v1.1.2 golang.org/x/net v0.0.0-20200930145003-4acb6c075d10 // indirect golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f // indirect google.golang.org/genproto v0.0.0-20201002142447-3860012362da // indirect ) ================================================ FILE: autoscaling-on-queue/producer/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo= github.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200930145003-4acb6c075d10 h1:YfxMZzv3PjGonQYNUaeU2+DhAdqOxerQ30JFB6WgAXo= golang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201002142447-3860012362da h1:DTQYk4u7nICKkkVZsBv0/0po0ChISxAJ5CTAfUhO0PQ= google.golang.org/genproto v0.0.0-20201002142447-3860012362da/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: autoscaling-on-queue/producer/main.go ================================================ package main import ( "context" "crypto/sha256" "encoding/json" "fmt" "log" "math/rand" "os" "os/signal" "strconv" "strings" "sync" "syscall" "time" dapr "github.com/dapr/go-sdk/client" daprd "github.com/dapr/go-sdk/service/grpc" "github.com/google/uuid" ) const ( chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" ) var ( logger = log.New(os.Stdout, "", 0) serviceAddress = getEnvVar("ADDRESS", ":60034") pubSubName = getEnvVar("PUBSUB_NAME", "autoscaling-pubsub") topicName = getEnvVar("TOPIC_NAME", "metrics") numOfPublishers = getEnvIntOrFail("NUMBER_OF_PUBLISHERS", "1") publishFrequency = getEnvDurationOrFail("PUBLISHERS_FREQ", "1s") publishDelay = getEnvDurationOrFail("PUBLISHERS_DELAY", "10s") logFrequency = getEnvDurationOrFail("LOG_FREQ", "3s") publishToConsole = getEnvBoolOrFail("PUBLISH_TO_CONSOLE", "false") client dapr.Client ) func main() { if numOfPublishers < 1 { numOfPublishers = 1 } logger.Printf("subscription name: %s", pubSubName) logger.Printf("topic name: %s", topicName) logger.Printf("number of publishers: %d", numOfPublishers) logger.Printf("publish frequency: %v", publishFrequency) logger.Printf("log frequency: %v", logFrequency) logger.Printf("publish delay: %v", publishDelay) // create Dapr service s, err := daprd.NewService(serviceAddress) if err != nil { log.Fatalf("failed to start the server: %v", err) } c, err := dapr.NewClient() if err != nil { log.Fatalf("error creating Dapr client: %v", err) } client = c defer client.Close() // handle signals stop := make(chan os.Signal) signal.Notify(stop, syscall.SIGTERM, syscall.SIGINT) resultCh := make(chan bool, 100) stopCh := make(chan struct{}) go func() { <-stop close(stopCh) }() // print results go monitor(resultCh, stopCh) // start producing for i := 1; i <= numOfPublishers; i++ { go publish(i, resultCh, stopCh) } // start the server to handle incoming events if err := s.Start(); err != nil { log.Fatalf("server error: %v", err) } } func monitor(resultCh <-chan bool, stopCh <-chan struct{}) { var mux sync.Mutex var successCounter int64 = 0 var errorCounter int64 = 0 startTime := time.Now() tickerCh := time.NewTicker(logFrequency).C for { select { case r := <-resultCh: mux.Lock() if r { successCounter++ } else { errorCounter++ } mux.Unlock() case <-tickerCh: var avg float64 = 0 if successCounter > 0 { avg = float64(successCounter) / time.Since(startTime).Seconds() } logger.Printf("%10d published, %3.0f/sec, %3d errors", successCounter, avg, errorCounter) case <-stopCh: os.Exit(0) } } } func publish(index int, resultCh chan<- bool, stopCh <-chan struct{}) { delayCh := time.NewTicker(publishDelay).C <-delayCh tickerCh := time.NewTicker(publishFrequency).C for { select { case <-stopCh: return case <-tickerCh: d := getEventData(index) if publishToConsole { logger.Printf("%s", d) resultCh <- true continue } resultCh <- client.PublishEvent(context.Background(), pubSubName, topicName, d) == nil } } } func getEventData(index int) []byte { r := requestContent{ ID: fmt.Sprintf("p%d-%s", index, uuid.New().String()), Data: []byte(getData(256)), Time: time.Now().UTC().Unix(), } // hash the entire message inSha := sha256.Sum256(r.Data) r.Sha = string(inSha[:]) b, err := json.Marshal(r) if err != nil { logger.Fatalf("error generating request: %v", err) } return b } func getData(length int) string { seededRand := rand.New(rand.NewSource(time.Now().UnixNano())) b := make([]byte, length) for i := range b { b[i] = chars[seededRand.Intn(len(chars))] } return string(b) } type requestContent struct { ID string `json:"id"` Data []byte `json:"data"` Sha string `json:"sha"` Time int64 `json:"time"` } func getEnvVar(key, fallbackValue string) string { if val, ok := os.LookupEnv(key); ok { return strings.TrimSpace(val) } return fallbackValue } func getEnvIntOrFail(key, fallbackValue string) int { s := getEnvVar(key, fallbackValue) v, err := strconv.Atoi(s) if err != nil { logger.Fatalf("invalid number variable: %s - %v", s, err) } return v } func getEnvDurationOrFail(key, fallbackValue string) time.Duration { s := getEnvVar(key, fallbackValue) v, err := time.ParseDuration(s) if err != nil { logger.Fatalf("invalid duration variable: %s - %v", s, err) } return v } func getEnvBoolOrFail(key, fallbackValue string) bool { s := getEnvVar(key, fallbackValue) v, err := strconv.ParseBool(s) if err != nil { logger.Fatalf("invalid bool variable: %s - %v", s, err) } return v } ================================================ FILE: autoscaling-on-queue/subscriber/Dockerfile ================================================ FROM golang:1.15.0 as builder WORKDIR /src/ COPY . /src/ ENV GO111MODULE=on RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -a -tags netgo -mod vendor -o ./service . FROM gcr.io/distroless/static:nonroot COPY --from=builder /src/service . ENTRYPOINT ["./service"] ================================================ FILE: autoscaling-on-queue/subscriber/Makefile ================================================ RELEASE_VERSION =v0.11.1 SERVICE_NAME ?=autoscaling-subscriber DOCKER_USERNAME ?=$(DOCKER_USER) .PHONY: all all: help .PHONY: tidy tidy: ## Updates the go modules and vendors all dependencies go mod tidy go mod vendor .PHONY: run run: tidy ## Runs uncompiled code in Dapr dapr run \ --app-id $(SERVICE_NAME) \ --app-port 60022 \ --app-protocol grpc \ --components-path ./config \ --log-level debug \ go run main.go .PHONY: image image: tidy ## Builds and publishes docker image docker build -t "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" . docker push "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" .PHONY: deploy deploy: ## Deploys prebuild image to k8s using currently selected context kubectl apply -f k8s/binding.yaml kubectl apply -f k8s/deployment.yaml kubectl rollout restart deployment/$(SERVICE_NAME) kubectl rollout status deployment/$(SERVICE_NAME) .PHONY: autoscale autoscale: ## Applies Keda to the deployed service kubectl apply -f k8s/keda.yaml .PHONY: scale scale: ## Scales the subscriber manually # kubectl autoscale deployment autoscaling-subscriber --min=1 --max=10 kubectl scale deployment/$(SERVICE_NAME) --replicas=10 .PHONY: kafka-port kafka-port: ## Forwards cluster Kafka port locally kubectl port-forward svc/kafka -n kafka 9092 & @echo use 'pkill kubectl -9' to stop .PHONY: kafka-portless kafka-portless: ## Stops forwarding cluster Kafka port locally @echo use 'pkill kubectl -9' to stop .PHONY: lint lint: ## Lints the entire project golangci-lint run --timeout=3m .PHONY: tag tag: ## Creates release tag git tag $(RELEASE_VERSION) git push origin $(RELEASE_VERSION) .PHONY: clean clean: ## Cleans up generated files go clean rm -fr ./bin rm -fr ./vendor .PHONY: help help: ## Display available commands @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \ 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ================================================ FILE: autoscaling-on-queue/subscriber/go.mod ================================================ module github.com/mchmarny/dapr-demos/autoscaling-on-queue/subscriber go 1.15 require ( github.com/dapr/go-sdk v0.11.0 github.com/pkg/errors v0.9.1 golang.org/x/net v0.0.0-20200930145003-4acb6c075d10 // indirect golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f // indirect google.golang.org/genproto v0.0.0-20201002142447-3860012362da // indirect ) ================================================ FILE: autoscaling-on-queue/subscriber/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo= github.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200930145003-4acb6c075d10 h1:YfxMZzv3PjGonQYNUaeU2+DhAdqOxerQ30JFB6WgAXo= golang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 h1:4X356008q5SA3YXu8PiRap39KFmy4Lf6sGlceJKZQsU= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0 h1:uslsjIdqvZYANxSBQjTI47vZfwMaTN3mLELkMnMIY/A= google.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201002142447-3860012362da h1:DTQYk4u7nICKkkVZsBv0/0po0ChISxAJ5CTAfUhO0PQ= google.golang.org/genproto v0.0.0-20201002142447-3860012362da/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: autoscaling-on-queue/subscriber/main.go ================================================ package main import ( "context" "log" "os/signal" "sync" "syscall" "time" "net/http" "os" "strings" "github.com/dapr/go-sdk/service/common" daprd "github.com/dapr/go-sdk/service/grpc" "github.com/pkg/errors" ) const ( primeStateKey = "high-prime" ) var ( logger = log.New(os.Stdout, "", 0) reqProcDur time.Duration address = getEnvVar("ADDRESS", ":60033") processDuration = getEnvVar("PROCESS_DURATION", "500ms") pubSubName = getEnvVar("PUBSUB_NAME", "autoscaling-pubsub") topicName = getEnvVar("TOPIC_NAME", "metrics") ) func main() { // Dapr service s, err := daprd.NewService(address) if err != nil { logger.Fatalf("failed to start the service: %v", err) } d, err := time.ParseDuration(processDuration) if err != nil { logger.Fatalf("invalid parameter (PROCESS_DURATION) must be a duration): %s - %v", processDuration, err) } reqProcDur = d var mux sync.Mutex var successCount int64 = 1 var errorCount int64 = 0 resultCh := make(chan bool) startTime := time.Now() go func() { tickerCh := time.NewTicker(5 * time.Second).C for { select { case r := <-resultCh: mux.Lock() if r { successCount++ } else { errorCount++ } mux.Unlock() case <-tickerCh: var avg float64 = 0 if successCount > 0 { avg = float64(successCount) / time.Since(startTime).Seconds() } logger.Printf("received: %10d, %3d errors - avg %3.0f/sec", successCount, errorCount, avg) } } }() // define subscription subscription := &common.Subscription{ PubsubName: pubSubName, Topic: topicName, } // subscribe if err := s.AddTopicEventHandler(subscription, func(ctx context.Context, e *common.TopicEvent) (retry bool, err error) { if err := processRequest(ctx, e.Data); err != nil { logger.Printf("error processing request: %v", err) resultCh <- false return true, errors.Wrap(err, "error processing request") } resultCh <- true return false, nil }); err != nil { logger.Fatalf("error adding topic subscription: %v", err) } // handle signals done := make(chan os.Signal, 1) signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) // Start go func() { if err := s.Start(); err != nil && err != http.ErrServerClosed { logger.Fatalf("error starting service: %v", err) } }() // Finish <-done } // does some computing to keep the process busy organically func processRequest(ctx context.Context, in interface{}) error { tickerCh := time.NewTicker(reqProcDur).C <-tickerCh return nil } func getEnvVar(key, fallbackValue string) string { if val, ok := os.LookupEnv(key); ok { return strings.TrimSpace(val) } return fallbackValue } ================================================ FILE: component-api/README.md ================================================ # Use of Dapr as a component API Server This 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: * Simple note management using Redis state store * Sending email using Sendgrid output binding * Querying tweets using Twitter bi-directional binding ## Setup ### State Component Create a `mongo-secret` ```shell kubectl create secret generic redis-secret --from-literal=password="" ``` Deploy component and [restart gateway](#ingress-gateway) ```shell kubectl apply -f config/state.yaml ``` ### Email Component Create a `email-secret` ```shell kubectl create secret generic email-secret --from-literal=apiKey="" ``` Deploy component and [restart gateway](#ingress-gateway) ```shell kubectl apply -f config/email.yaml ``` ### Twitter Component Create a `twitter-secret` ```shell kubectl create secret generic twitter-secret \ --from-literal=consumerKey="" \ --from-literal=consumerSecret="" \ --from-literal=accessToken="" \ --from-literal=accessSecret="" ``` Deploy component and [restart gateway](#ingress-gateway) ```shell kubectl apply -f config/twitter.yaml ``` ### Ingress Gateway Ensure all the gateway instances are aware of new components ```shell kubectl rollout restart deployment/nginx-ingress-nginx-controller kubectl rollout status deployment/nginx-ingress-nginx-controller ``` ## Usage To use any of the components you will need the Dapr API token: ```shell export API_TOKEN=$(kubectl get secret dapr-api-token -o jsonpath="{.data.token}" | base64 --decode) ``` ### State And POST it to the Dapr API to save your note: ```shell curl -X POST \ -d '[{ "key": "1", "value": "This is my first note" }]' \ -H "Content-Type: application/json" \ -H "dapr-api-token: ${API_TOKEN}" \ https://api.cloudylabs.dev/v1.0/state/note-store ``` Retrieve the saved note: ```shell curl -X GET \ -H "Content-Type: application/json" \ -H "dapr-api-token: ${API_TOKEN}" \ https://api.cloudylabs.dev/v1.0/state/note-store/1 ``` And now delete the note: ```shell curl -X DELETE \ -H "Content-Type: application/json" \ -H "dapr-api-token: ${API_TOKEN}" \ https://api.cloudylabs.dev/v1.0/state/note-store/1 ``` > 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. ### Email To send email, first edit the [sample email](./sample/email.json) file: ```json { "operation": "create", "metadata": { "emailTo": "daprdemo@chmarny.com", "subject": "Dapr Demo" }, "data": "

Greetings

Hi

" } ``` And POST it to the Dapr API: ```shell curl -d @./sample/email.json \ -H "Content-Type: application/json" \ -H "dapr-api-token: ${API_TOKEN}" \ "https://api.cloudylabs.dev/v1.0/bindings/send-email" ``` ### Twitter To query the last 100 tweets for particular query, first edit the [sample query](./sample/twitter.json) file: ```json { "operation": "get", "metadata": { "query": "dapr AND serverless", "lang": "en", "result": "recent" } } ``` Metadata parameters: * `query` - can be any valid Twitter query (supports `AND`, `OR` `BUT NOT`, `FROM`, `TO`, `#`, `@`...) * `lang` - (optional) is the [ISO 639-1](https://meta.wikimedia.org/wiki/Template:List_of_language_names_ordered_by_code) language code * `result` - (optional) is one of: * `mixed` - include both popular and real time results in the response * `recent` - return only the most recent results in the response * `popular` - return only the most popular results in the response * `since_id` - (optional) the not inclusive tweet ID query should start from And POST it to the Dapr API: ```shell curl -d @./sample/twitter.json \ -H "Content-Type: application/json" \ -H "dapr-api-token: ${API_TOKEN}" \ "https://api.cloudylabs.dev/v1.0/bindings/query-twitter" ``` And 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: ```shell curl -d @./sample/twitter.json \ -H "Content-Type: application/json" \ -H "dapr-api-token: ${API_TOKEN}" \ "https://api.cloudylabs.dev/v1.0/bindings/query-twitter" \ | jq ".[] | { id: .id_str, user: .user.screen_name, text: .text}" ``` The result ```shell { "id": "1298546227211055109", "user": "markgossa", "text": "What a blast! @AzureFunctions Live of August was fully packed with news (new extension bundle, Dapr extension)" } { "id": "1298181483547357184", "user": "ysakashita3", "text": "I submitted a blog post to https://t.co/DXGTgtC4Xc. 'Serverless plugin': #KEDA for scaling down your containers" } ``` ## Disclaimer This 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. ## License This software is released under the [MIT](../LICENSE) ================================================ FILE: component-api/config/email.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: send-email spec: type: bindings.twilio.sendgrid metadata: - name: emailFrom value: "demo@thingz.io" - name: apiKey secretKeyRef: name: email-secret key: apiKey scopes: - nginx-ingress ================================================ FILE: component-api/config/state.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: note-store spec: type: state.redis metadata: - name: redisHost value: redis-master.redis.svc.cluster.local:6379 - name: redisPassword secretKeyRef: name: redis-secret key: password scopes: - nginx-ingress ================================================ FILE: component-api/config/twitter.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: query-twitter spec: type: bindings.twitter metadata: - name: consumerKey secretKeyRef: name: twitter-secret key: consumerKey - name: consumerSecret secretKeyRef: name: twitter-secret key: consumerSecret - name: accessToken secretKeyRef: name: twitter-secret key: accessToken - name: accessSecret secretKeyRef: name: twitter-secret key: accessSecret scopes: - nginx-ingress ================================================ FILE: component-api/sample/email.json ================================================ { "operation": "create", "metadata": { "emailTo": "daprdemo@chmarny.com", "subject": "Dapr Demo" }, "data": "

Greetings:

Hi

" } ================================================ FILE: component-api/sample/twitter.json ================================================ { "operation": "get", "metadata": { "query": "dapr AND serverless", "lang": "en", "result": "recent" } } ================================================ FILE: cron-binding/Dockerfile ================================================ FROM golang:1.15.0 as builder WORKDIR /src/ COPY . /src/ ENV GO111MODULE=on RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -a -tags netgo -mod vendor -o ./service . FROM gcr.io/distroless/static:nonroot COPY --from=builder /src/service . ENTRYPOINT ["./service"] ================================================ FILE: cron-binding/Makefile ================================================ RELEASE_VERSION =v0.11.1 SERVICE_NAME ?=cron-binding-demo DOCKER_USERNAME ?=$(DOCKER_USER) .PHONY: help tidy build run image lint tag clean all: help tidy: ## Updates the go modules and vendors all dependencies go mod tidy go mod vendor test: tidy ## Tests the entire project go test -count=1 -race ./... build: tidy ## Builds local release binary CGO_ENABLED=0 go build -a -tags netgo -mod vendor -o bin/$(SERVICE_NAME) . debug: ## Runs uncompiled code it in Dapr in debug mode dapr run \ --app-id $(SERVICE_NAME) \ --app-port 8080 \ --app-protocol http \ --components-path ./config \ go run main.go run: build ## Builds binary and runs it in Dapr dapr run \ --app-id $(SERVICE_NAME) \ --app-port 8080 \ --app-protocol http \ --components-path ./config \ bin/$(SERVICE_NAME) image: tidy ## Builds and publish docker image docker build -t "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" . docker push "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" deploy: ## Deploys prebuild image to k8s using currently selected context kubectl apply -f k8s/component.yaml kubectl apply -f k8s/deployment.yaml kubectl rollout restart deployment/cron-binding-demo kubectl rollout status deployment/cron-binding-demo lint: ## Lints the entire project golangci-lint run --timeout=3m tag: ## Creates release tag git tag $(RELEASE_VERSION) git push origin $(RELEASE_VERSION) clean: ## Cleans up generated files go clean rm -fr ./bin rm -fr ./vendor help: ## Display available commands @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \ 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ================================================ FILE: cron-binding/README.md ================================================ # cron-binding ## Binding ```yaml apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: run spec: type: bindings.cron metadata: - name: schedule value: "@every 3s" ``` For more information about this binding see the [Dapr docs](https://github.com/dapr/docs/blob/master/reference/specs/bindings/cron.md) ## Run Dapr cron binding demo in `go`. To use run it, first start the service ```shell dapr run --app-id cron-binding-demo \ --protocol http \ --app-port 8080 \ --components-path ./config \ go run *.go ``` ## Deploy Deploy and wait for the pod to be ready ```shell kubectl apply -f k8s/component.yaml kubectl apply -f k8s/deployment.yaml kubectl rollout status deployment/cron-binding-demo ``` If you have changed an existing component, make sure to reload the deployment and wait until the new version is ready ```shell kubectl rollout restart deployment/cron-binding-demo kubectl rollout status deployment/cron-binding-demo ``` Follow logs to view schedule firing ```shell kubectl logs -l app=cron-binding-demo -c daprd -f ``` Depending on the frequency you used there may not be an entry right away but you should see something similar to this ```json { "app_id":"cron-binding-demo", "instance":"cron-binding-demo-6c88dbb467-j54br", "level":"debug", "msg":"next run: 59m59.629538771s", "scope":"dapr.contrib", "time":"2020-08-31T13:08:34.37049343Z", "type":"log", "ver":"0.10.0" } ``` ## Disclaimer This 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. ## License This software is released under the [MIT](../LICENSE) ================================================ FILE: cron-binding/config/cron.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: run spec: type: bindings.cron metadata: - name: schedule value: "@every 3s" ================================================ FILE: cron-binding/go.mod ================================================ module github.com/mchmarny/dapr-demos/cron-binding go 1.15 require github.com/dapr/go-sdk v0.11.0 ================================================ FILE: cron-binding/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo= github.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: cron-binding/k8s/component.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: schedule spec: type: bindings.cron metadata: - name: schedule value: "@every 1h" scopes: - cron-binding-demo ================================================ FILE: cron-binding/k8s/deployment.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: cron-binding-demo labels: app: cron-binding-demo demo: cron spec: replicas: 1 selector: matchLabels: app: cron-binding-demo template: metadata: labels: app: cron-binding-demo demo: cron annotations: dapr.io/enabled: "true" dapr.io/app-id: "cron-binding-demo" dapr.io/app-protocol: "http" dapr.io/app-port: "8080" dapr.io/config: "tracing" dapr.io/log-as-json: "true" dapr.io/log-level: "debug" spec: containers: - name: service image: mchmarny/cron-binding-demo:v0.11.1 ports: - containerPort: 8080 env: - name: ADDRESS value: ":8080" ================================================ FILE: cron-binding/main.go ================================================ package main import ( "context" "log" "net/http" "os" "strings" "github.com/dapr/go-sdk/service/common" daprd "github.com/dapr/go-sdk/service/http" ) var ( logger = log.New(os.Stdout, "", 0) address = getEnvVar("ADDRESS", ":8080") ) func main() { // create a Dapr service s := daprd.NewService(address) // add some input binding handler if err := s.AddBindingInvocationHandler("schedule", scheduleHandler); err != nil { logger.Fatalf("error adding binding handler: %v", err) } // start the service if err := s.Start(); err != nil && err != http.ErrServerClosed { logger.Fatalf("error starting service: %v", err) } } func scheduleHandler(ctx context.Context, in *common.BindingEvent) (out []byte, err error) { logger.Printf("Schedule - Metadata:%v, Data:%v", in.Metadata, in.Data) // TODO: do something with the cloud event data return nil, nil } func getEnvVar(key, fallbackValue string) string { if val, ok := os.LookupEnv(key); ok { return strings.TrimSpace(val) } return fallbackValue } ================================================ FILE: dapr-aci/README.md ================================================ # Dapr sidecar in ACI Demo of Dapr sidecar in Azure Container Instances (ACI) ## Setup This 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. ```shell export SNAME="dapraci" ``` > assumes your resource group and location defaults are already set. If not, set them now: ```shell az account set --subscription az configure --defaults location= group= ``` Create a storage account ```shell az storage account create --name $SNAME --sku Standard_LRS ``` Create a storage share > For demo purposes share and storage user names are the same ```shell az storage share create --name $SNAME --account-name $SNAME ``` Capture storage key ```shell export ACCOUNT_KEY=$(az storage account keys list --account-name $SNAME \ --query "[0].value" \ --output tsv) echo $ACCOUNT_KEY ``` Now update `volumes[components].azureFile.storageAccountKey` in `configuration/app.yaml` file so that ACI can mount it. Upload the Dapr component files ```shell az storage file upload --account-key $ACCOUNT_KEY \ --account-name $SNAME \ --share-name $SNAME \ --source components/state.yaml az storage file upload --account-key $ACCOUNT_KEY \ --account-name $SNAME \ --share-name $SNAME \ --source components/pubsub.yaml ``` List files to make sure they are all there ```shell az storage file list \ --account-key $ACCOUNT_KEY \ --share-name $SNAME \ --account-name $SNAME \ --output tsv ``` ## Deployment Once the storage is set up, you can deploy ```shell az container create -f deployment/app.yaml ``` When you list the containers: ```shell az container list -o table ``` The result should look something like this: ```shell Name ResourceGroup Status Image IP:ports Network CPU/Memory OsType Location -------- --------------- --------- --------------------------------------------------- --------------------- --------- --------------- -------- ---------- dapraci 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 ``` ## Demo First, capture the IP for ease of access: ```shell export APP_IP=$(az container show -n dapraci --query "ipAddress.ip" -o tsv) ``` Next, invoke the ping method thru Dapr API: ```shell curl -i -d '{"message":"ping"}' \ -H "Content-type: application/json" \ "http://${APP_IP}:3500/v1.0/invoke/dapraci/method/ping" ``` Response should look something like this: ```json { "on": 1604003460965972895, "greeting": "pong" } ``` You can also invoke the PubSub API on Dapr to publish: ```shell curl -i -d '{"message":"hello"}' \ -H "Content-type: application/json" \ "http://${APP_IP}:3500/v1.0/publish/pubsub/messages" ``` Response from post has no body but you should see the headers: ```shell HTTP/1.1 200 OK Server: fasthttp Date: Thu, 29 Oct 2020 21:05:43 GMT Content-Length: 0 Traceparent: 00-bd0f6f745de1b2cc8b5463f8abaa8656-d2f1d6be6d202273-00 ``` This 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: ```shell curl -i -d '{"message":"ping"}' \ -H "Content-type: application/json" \ "http://${APP_IP}:8082/ping" ``` ### Logs To view logs from the Dapr container: ```shell az container logs --name dapraci --container-name daprd ``` > 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 ```shell az container logs --name dapraci --container-name daprd | jq ".msg" ``` To query the app container: ```shell az container logs --name dapraci --container-name app ``` That's it, I hope you found it helpful. ## Todo * Dapr API token auth * Configuration to show ACT * Secrets to enable Azure Vault ## Disclaimer This 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. ## License This software is released under the [MIT](../LICENSE) ================================================ FILE: dapr-aci/components/pubsub.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: pubsub spec: type: pubsub.azure.servicebus metadata: - name: connectionString value: "Endpoint=sb://.servicebus.windows.net/;SharedAccessKeyName=dapracidemo;SharedAccessKey=;EntityPath=messages" ================================================ FILE: dapr-aci/components/state.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: store spec: type: state.azure.tablestorage metadata: - name: accountName value: dapracidemo - name: accountKey value: - name: tableName value: dapracidemodata ================================================ FILE: dapr-aci/deployment/app.yaml ================================================ apiVersion: 2018-06-01 location: westus name: dapraci properties: containers: - name: app properties: image: ghcr.io/mchmarny/aci-app:v0.2.2 resources: requests: cpu: 1.0 memoryInGB: 1.5 environmentVariables: - name: ADDRESS value: ":8082" - name: PUBSUB_NAME value: pubsub - name: TOPIC_NAME value: messages - name: STORE_NAME value: store ports: - port: 8082 - name: daprd properties: image: daprio/daprd:0.11.3 volumeMounts: - name: dapr-logs mountPath: /var/log/pods - name: component-store mountPath: /components command: - /daprd - --app-id - dapraci - --app-port - 8082 - --components-path - /components - --log-as-json - --log-level - debug resources: requests: cpu: 1.0 memoryInGB: 1.5 ports: - port: 3500 osType: Linux restartPolicy: Always ipAddress: ports: # comment the ports you do not want to expose - port: 8082 protocol: TCP - port: 3500 protocol: TCP type: Public volumes: - name: dapr-logs emptyDir: {} - name: component-store azureFile: shareName: dapraci storageAccountName: dapraci storageAccountKey: type: Microsoft.ContainerInstance/containerGroups ================================================ FILE: dapr-aci/src/Dockerfile ================================================ FROM golang:1.15.3 as builder WORKDIR /src/ COPY . /src/ ENV GO111MODULE=on RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -a -tags netgo -mod vendor -o ./app . FROM gcr.io/distroless/static:nonroot COPY --from=builder /src/app . ENTRYPOINT ["./app"] ================================================ FILE: dapr-aci/src/Makefile ================================================ IMAGE_NAME ?=aci-app IMAGE_TAG ?=v0.2.2 IMAGE_OWNER ?=$(shell git config --get user.username) APP_ID ?=aci-app APP_PORT ?=8082 all: help .PHONY: tidy tidy: ## Updates the go modules and vendors all dependencies go mod tidy go mod vendor .PHONY: test test: tidy ## Tests the entire project go test -count=1 -race ./... .PHONY: run run: tidy ## Runs uncompiled code in Dapr dapr run \ --app-id $(APP_ID) \ --app-port $(APP_PORT) \ --app-protocol http \ --dapr-http-port 3500 \ --components-path ../components \ --log-level debug \ go run main.go .PHONY: event event: ## Posts simple event to the messages topic curl -i -d '{"message":"hello"}' \ -H "Content-type: application/json" \ http://localhost:3500/v1.0/publish/pubsub/messages .PHONY: ping ping: ## Invokes app method curl -i -d '{"message":"ping"}' \ -H "Content-type: application/json" \ http://localhost:3500/v1.0/invoke/$(APP_ID)/method/ping .PHONY: image image: tidy ## Builds and publishes image docker build -t "ghcr.io/$(IMAGE_OWNER)/$(IMAGE_NAME):$(IMAGE_TAG)" . docker push "ghcr.io/$(IMAGE_OWNER)/$(IMAGE_NAME):$(IMAGE_TAG)" .PHONY: lint lint: ## Lints the entire project golangci-lint run --timeout=3m .PHONY: clean clean: ## Cleans up generated files go clean rm -fr ./bin rm -fr ./vendor .PHONY: help help: ## Display available commands @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \ 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ================================================ FILE: dapr-aci/src/go.mod ================================================ module github.com/mchmarny/dapr-demos/dapr-aci/src go 1.15 require ( github.com/dapr/go-sdk v0.11.1 github.com/pkg/errors v0.9.1 golang.org/x/net v0.0.0-20201029055024-942e2f445f3c // indirect golang.org/x/sys v0.0.0-20201029080932-201ba4db2418 // indirect golang.org/x/text v0.3.4 // indirect google.golang.org/genproto v0.0.0-20201028140639-c77dae4b0522 // indirect ) ================================================ FILE: dapr-aci/src/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dapr/go-sdk v0.11.1 h1:83lLYLrOKK1dAI1XhuamY9SkQoo2t5Fd8zlNlDMtwWA= github.com/dapr/go-sdk v0.11.1/go.mod h1:l9aJ4Zqsfzi9adwheYgFND6ZxhPDbb34IF/NecuPYJo= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201022231255-08b38378de70 h1:Z6x4N9mAi4oF0TbHweCsH618MO6OI6UFgV0FP5n0wBY= golang.org/x/net v0.0.0-20201022231255-08b38378de70/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201029055024-942e2f445f3c h1:rpcgRPA7OvNEOdprt2Wx8/Re2cBTd8NPo/lvo3AyMqk= golang.org/x/net v0.0.0-20201029055024-942e2f445f3c/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd h1:WgqgiQvkiZWz7XLhphjt2GI2GcGCTIZs9jqXMWmH+oc= golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201029080932-201ba4db2418 h1:HlFl4V6pEMziuLXyRkm5BIYq1y1GAbb02pRlWvI54OM= golang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20201022181438-0ff5f38871d5 h1:YejJbGvoWsTXHab4OKNrzk27Dr7s4lPLnewbHue1+gM= google.golang.org/genproto v0.0.0-20201022181438-0ff5f38871d5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201028140639-c77dae4b0522 h1:7RoRaOmOAXwqnurgQ5g5/d0yCi9ha2UxuTZULXudK7A= google.golang.org/genproto v0.0.0-20201028140639-c77dae4b0522/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.1 h1:DGeFlSan2f+WEtCERJ4J9GJWk15TxUi8QGagfI87Xyc= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: dapr-aci/src/main.go ================================================ package main import ( "context" "encoding/json" "fmt" "log" "time" "net/http" "os" "strings" dapr "github.com/dapr/go-sdk/client" "github.com/dapr/go-sdk/service/common" daprd "github.com/dapr/go-sdk/service/http" "github.com/pkg/errors" ) const ( addInvokeHandlerError = "error adding invocation handler" startingServiceError = "error starting service" clientCreateError = "error creating Dapr client" addSubscriptionError = "error subscribing to a topic" addInvocationError = "error creating invcation handler" methodName = "ping" ) var ( logger = log.New(os.Stdout, "", 0) address = getEnvVar("ADDRESS", ":8082") pubSubName = getEnvVar("PUBSUB_NAME", "pubsub") topicName = getEnvVar("TOPIC_NAME", "messages") storeName = getEnvVar("STORE_NAME", "store") client dapr.Client ) func main() { s := daprd.NewService(address) var clientErr error if client, clientErr = dapr.NewClient(); clientErr != nil { logger.Fatalf("%s: %v", clientCreateError, clientErr) } defer client.Close() if err := s.AddServiceInvocationHandler(methodName, invokeHandler); err != nil { logger.Fatalf("%s: %v", addInvocationError, err) } subscription := &common.Subscription{ PubsubName: pubSubName, Topic: topicName, Route: fmt.Sprintf("/%s", topicName), } if err := s.AddTopicEventHandler(subscription, eventHandler); err != nil { logger.Fatalf("%s: %v", addSubscriptionError, err) } if err := s.Start(); err != nil && err != http.ErrServerClosed { logger.Fatalf("%s: %v", startingServiceError, err) } } func invokeHandler(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) { logger.Printf("Method %s invoked (ContentType:%s, Verb:%s, QueryString:%s, Data:%s)", methodName, in.ContentType, in.Verb, in.QueryString, in.Data) j := []byte(fmt.Sprintf(`{"on": %d, "greeting": "pong"}`, time.Now().UTC().UnixNano())) out = &common.Content{ContentType: in.ContentType, Data: j} return } func eventHandler(ctx context.Context, e *common.TopicEvent) (retry bool, err error) { logger.Printf("Event received (PubsubName:%s, Topic:%s, Data: %v", e.PubsubName, e.Topic, e.Data) data, ok := e.Data.([]byte) if !ok { data, err = json.Marshal(e.Data) if err != nil { return false, errors.Wrapf(err, "invalid data format: %T", e.Data) } } if err := client.SaveState(ctx, storeName, e.ID, data); err != nil { return false, errors.Wrapf(err, "error saving data to %s (%s)", storeName, data) } return false, nil } func getEnvVar(key, fallbackValue string) string { if val, ok := os.LookupEnv(key); ok { return strings.TrimSpace(val) } return fallbackValue } ================================================ FILE: dapr-api-on-aci/README.md ================================================ # Dapr API in ACI * Purpose-configured instance of Dapr deployed into Azure Container Instances (ACI) with API token authentication using single command * Use of Dapr output binding + Dapr as a microservice (in this case email sending) ## Setup The 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. ```shell export SNAME="demodapr" ``` > assumes your resource group and location defaults are already set. If not, set them now: ```shell az account set --subscription az configure --defaults location= group= ``` Create a storage account ```shell az storage account create --name $SNAME --sku Standard_LRS ``` Create a storage share for config > For demo purposes share and storage user names are the same ```shell az storage share create --name $SNAME --account-name $SNAME ``` Capture storage key ```shell export SKEY=$(az storage account keys list --account-name $SNAME --query "[0].value" --output tsv) ``` Create a storage directory for config files ```shell az storage directory create --account-name $SNAME --name $SNAME --share-name $SNAME ``` Upload the Dapr component files > TODO: Make sure you set the Sendgrid API key in the email.yaml ```shell az storage file upload --account-name $SNAME --share-name $SNAME --source email.yaml ``` ## Deployment Once the storage is set up, you can deploy. Start by exporting Dapr API Authentication token ```shell export DTOKEN=$(openssl rand -base64 36) ``` > 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. And launch the Dapr container ```shell az container create \ --name $SNAME \ --ports 3500 \ --protocol TCP \ --dns-name-label $SNAME \ --image docker.io/daprio/daprd:0.11.0 \ --command-line "/daprd --components-path /components --app-protocol http" \ --secure-environment-variables "DAPR_API_TOKEN=${DTOKEN}" \ --azure-file-volume-share-name $SNAME \ --azure-file-volume-account-name $SNAME \ --azure-file-volume-account-key $SKEY \ --azure-file-volume-mount-path /components ``` Then check on the status of the deployment ```shell az container list -o table ``` The result should look something like this ```shell Name ResourceGroup Status Image IP:ports Network CPU/Memory OsType Location -------- ------------- --------- ----------------------------- ----------------- ------- --------------- -------- -------- demodapr mchmarny Succeeded docker.io/daprio/daprd:0.11.0 51.143.49.0:3500 Public 1.0 core/1.5 gb Linux westus2 ``` If everything went OK, you should be able post to the email output binding below To restart the service after update of environment variables ```shell az container restart --name $SNAME ``` ## Use To use the above deployed instance of Dapr configured with SendGrid output binding, POST to the Dapr API following message using `curl`. > 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. ```shell export SREGION=$(az container list --query "[?contains(name, '${SNAME}')].location" --output tsv) ``` ```shell curl -v -X POST -H "Content-Type: application/json" \ -H "dapr-api-token: ${DTOKEN}" \ "http://${SNAME}.${SREGION}.azurecontainer.io:3500/v1.0/bindings/email" \ -d '{ "operation": "create", "data": "

Test Headline

Test message

"}' ``` ## Disclaimer This 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. ## License This software is released under the [MIT](../LICENSE) ================================================ FILE: dapr-api-on-aci/email.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: email spec: type: bindings.twilio.sendgrid metadata: - name: emailFrom value: "demo@thingz.io" - name: subject value: "Demo Email Subject" - name: apiKey value: ================================================ FILE: daprized-ingress/README.md ================================================ # Dapr API on Cluster Ingress Controller This 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/). ![](img/diagram.png) > 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/). ## Setup To make this how-to more reproducible, start by defining the namespace where your NGINX ingress is/will be located: ```shell export INGRESS_NAMESPACE="default" ``` If you are not using `default` namespace, apply also the necessary roles to that namespace: ```shell kubectl apply -f config/namespace.yml -n $INGRESS_NAMESPACE ``` Next, create a secret to hold the Dapr API token: ```shell export API_TOKEN=$(openssl rand -base64 32) kubectl create secret generic dapr-api-token --from-literal=token="${API_TOKEN}" -n $INGRESS_NAMESPACE ``` ## Deployment Next, 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): ```shell kubectl apply -f config/ingress-config.yaml -n $INGRESS_NAMESPACE ``` Add the Helm repo: ```shell helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx helm repo update ``` Install NGINX: > Note, the following command creates 2 replicas to prevent ingress issues during subsequent changes ```shell helm install nginx ingress-nginx/ingress-nginx \ --set controller.replicaCount=2 \ -f config/ingress-annotations.yaml \ -n $INGRESS_NAMESPACE ``` And wait for the deployment to finish: ```shell kubectl rollout status deployment/nginx-ingress-nginx-controller -n $INGRESS_NAMESPACE ``` Notice 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: ```yaml controller: podAnnotations: dapr.io/enabled: "true" dapr.io/app-id: "nginx-ingress" dapr.io/app-protocol: "http" dapr.io/app-port: "80" dapr.io/api-token-secret: "dapr-api-token" dapr.io/config: "ingress-config" dapr.io/log-as-json: "true" ``` Most 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/): ## Configure Finally, 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: > 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 ;) ```yaml spec: rules: - host: api.thingz.io http: paths: - path: / backend: serviceName: nginx-ingress-dapr servicePort: 80 ``` When done, apply the ingress to the cluster: ```shell kubectl apply -f config/ingress.yaml -n $INGRESS_NAMESPACE ``` > See [setup](../setup) for instructions hot to create TLS certificates and configure SSL for your domain. ## DNS Now 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: ```shell kubectl get svc nginx-ingress-nginx-controller \ -o jsonpath='{.status.loadBalancer.ingress[0].ip}' \ -n $INGRESS_NAMESPACE ``` Next 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: ```shell Name: Type: TTL: Data: api A 60s 1.2.3.4 ``` The `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`. When done, you can test if the DNS is ready by running `dig` with your domain name (e.g. `api.thingz.io`): ```shell dig ``` When the A record for your domain name in reflected in the ANSWER SECTION you are ready to test. ## Test In case you lost the terminal session when we first defined the `API_TOKEN` variable, capture it again: ```shell export API_TOKEN=$(kubectl get secret dapr-api-token -o jsonpath="{.data.token}" -n ${INGRESS_NAMESPACE} | base64 --decode) ``` Then curl the Dapr health API to ensure ensure everything works. Just make sure to replace the `` with your actual domain. ```shell curl -i \ -H "Content-type: application/json" \ -H "dapr-api-token: ${API_TOKEN}" \ "http:///v1.0/healthz" ``` If 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. ## Troubleshooting There are a few places to check if you experience any issues: * Does the domain name you used resolve to the cluster ingress IP? * Has Dapr injected its sidecar into the ingress pod? (Container count on `get pods` should be 2) ## Disclaimer This 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. ## License This software is released under the [MIT](../LICENSE) ================================================ FILE: daprized-ingress/config/ingress-annotations.yaml ================================================ controller: podAnnotations: dapr.io/enabled: "true" dapr.io/app-id: "nginx-ingress" dapr.io/app-protocol: "http" dapr.io/app-port: "80" dapr.io/api-token-secret: "dapr-api-token" dapr.io/config: "ingress-config" dapr.io/log-as-json: "true" ================================================ FILE: daprized-ingress/config/ingress-config.yaml ================================================ --- apiVersion: dapr.io/v1alpha1 kind: Configuration metadata: name: ingress-config spec: tracing: samplingRate: "1" secrets: scopes: - storeName: kubernetes defaultAccess: deny allowedSecrets: ["dapr-api-token"] --- apiVersion: dapr.io/v1alpha1 kind: Configuration metadata: name: tracing spec: tracing: samplingRate: "1" ================================================ FILE: daprized-ingress/config/ingress.yaml ================================================ apiVersion: extensions/v1beta1 kind: Ingress metadata: name: ingress-rules annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/rewrite-target: / spec: rules: - host: api.thingz.io http: paths: - path: / backend: serviceName: nginx-ingress-dapr servicePort: 80 ================================================ FILE: daprized-ingress/config/namespace.yml ================================================ apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: secret-reader rules: - apiGroups: [""] resources: ["secrets"] verbs: ["get"] --- kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: dapr-secret-reader subjects: - kind: ServiceAccount name: default roleRef: kind: Role name: secret-reader apiGroup: rbac.authorization.k8s.io ================================================ FILE: fan-out/README.md ================================================ # fan-out demo `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. This demo will illustrate how to use Dapr's plugable component mechanism to `fan-out` events from one Pub/Sub configured with Redis to: * Kafka topic in CSV format * REST endpoint in JSON format * gRPC service in XML format ![](./img/fan-out-in-dapr.png) This 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) This demo requires Dapr `v0.11` as well as go `1.14+` and docker-compose `v1.26+` > 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) ## App 2: Pub/Sub to Pub/Sub Publisher To 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: ```shell docker-compose -f ./config/kafka.yaml up -d ``` The result should look something like this: ```shell Creating network "config_default" with the default driver Creating config_kafka_1 ... done Creating config_zookeeper_1 ... done ``` Now, start `App 2` which will receive events from Redis, convert them to XML, and publish them onto the Kafka topic: ```shell dapr run \ --app-id app2 \ --app-port 60010 \ --app-protocol grpc \ --components-path ./config \ go run main.go ``` > 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. Leave the application running, we will come back to it after configuring `App 1` ## App 1: Pub/Sub Event Producer To 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`: ```shell dapr run \ --app-id app1 \ --app-port 60013 \ --app-protocol grpc \ --components-path ./config \ go run main.go ``` The 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: ```shell == APP == published: {"id":"df50a6c7-b5bb-45ce-b3a8-ad428bbbd5fe","temperature":60.46998219508215,"humidity":94.05150371362079,"time":1598960035} ``` Now in the `App 1`, the log output for each event should look something like this: ```shell == APP == Event - PubsubName:fanout-source-pubsub, Topic:events, ID:5ffa4502-8bf1-4bbf-927e-8b62e1949166 == APP == Target (csv): "45ecf820-705b-47c2-a2e4-7dbb3eecb728",66.459360,43.777042,"2020-09-01T04:33:58-07:00" ``` ## App 3: Pub/Sub to External REST Endpoint Publisher To 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`: ```shell dapr run \ --app-id app3 \ --app-port 60011 \ --app-protocol grpc \ --components-path ./config \ go run main.go ``` > 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) ```yaml apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: fanout-http-target-post-binding spec: type: bindings.http metadata: - name: url value: https://postman-echo.com/post - name: method value: POST ``` If the `App 1` is still running, you should see entries in the log similar to this: ```shell == APP == Event - PubsubName:fanout-source-pubsub, Topic:events, ID:d5bbcc9e-f0d3-46df-8d8e-f7dadb3f304c == APP == Target (json): {"temperature":20.326656,"humidity":36.093533,"time":1598961599,"id":"b2fa85cf-1be6-4489-9aec-613cf969675e"} ``` ## App 4: Pub/Sub to another Dapr Service Publisher To 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. ```shell dapr run \ --app-id grpc-echo-service \ --app-port 60015 \ --app-protocol grpc \ go run main.go ``` Then, in yet another terminal session, navigate to the `service-format-converter` directory and start the `App 4` that will invoke the `grpc-echo-service`: ```shell dapr run \ --app-id app4 \ --app-port 60012 \ --app-protocol grpc \ --components-path ./config \ go run main.go ``` If everything went well, you should see something similar in the `App 4` logs: ```shell == APP == Target: &{Data:[60 84 111 112 105 99 69 118...] ContentType:application/xml} == APP == Response: df450605-4927-407e-a2b2-61233939ee581.0com.dapr.event.sentapp1application/json{"time":1598962950,"id":"745e3ebe-990b-4292-9347-f84ff81f41e6","temperature":31.812637,"humidity":46.894296}eventsfanout-source-pubsub ``` > 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. ## Disclaimer This 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. ## License This software is released under the [MIT](../LICENSE) ================================================ FILE: fan-out/grpc-echo-service/Dockerfile ================================================ FROM golang:1.15.0 as builder WORKDIR /src/ COPY . /src/ ENV GO111MODULE=on RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -a -tags netgo -mod vendor -o ./service . FROM gcr.io/distroless/static:nonroot COPY --from=builder /src/service . ENTRYPOINT ["./service"] ================================================ FILE: fan-out/grpc-echo-service/Makefile ================================================ RELEASE_VERSION =v0.11.1 SERVICE_NAME ?=grpc-echo-service DOCKER_USERNAME ?=$(DOCKER_USER) .PHONY: all all: help .PHONY: tidy tidy: ## Updates the go modules and vendors all dependencies go mod tidy go mod vendor .PHONY: test test: tidy ## Tests the entire project go test -count=1 -race ./... .PHONY: run run: tidy ## Runs uncompiled code in Dapr dapr run \ --app-id $(SERVICE_NAME) \ --app-port 60002 \ --app-protocol grpc \ --dapr-http-port 3500 \ go run main.go .PHONY: invoke invoke: ## Invokes service through Dapr API curl -d '{ "message": "ping" }' \ -H "Content-type: application/json" \ "http://localhost:3500/v1.0/invoke/$(SERVICE_NAME)/method/echo" .PHONY: image image: tidy ## Builds and publish docker image docker build -t "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" . docker push "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" .PHONY: deploy deploy: ## Deploys prebuild image to k8s using currently selected context kubectl apply -f deployment.yaml kubectl rollout restart deployment/grpc-echo-service kubectl rollout status deployment/grpc-echo-service .PHONY: call call: ## Invokes service through Dapr API $(eval API_TOKEN=$(shell kubectl get secret dapr-api-token -o jsonpath="{.data.token}" | base64 --decode)) curl -d '{ "message": "ping" }' \ -H "Content-type: application/json" \ -H "dapr-api-token: $(API_TOKEN)" \ "https://api.cloudylabs.dev/v1.0/invoke/$(SERVICE_NAME)/method/echo" .PHONY: lint lint: ## Lints the entire project golangci-lint run --timeout=3m .PHONY: tag tag: ## Creates release tag git tag $(RELEASE_VERSION) git push origin $(RELEASE_VERSION) .PHONY: clean clean: ## Cleans up generated files go clean rm -fr ./bin rm -fr ./vendor .PHONY: help help: ## Display available commands @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \ 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ================================================ FILE: fan-out/grpc-echo-service/README.md ================================================ # grpc-service For more information about service invocation see the [Dapr docs](https://github.com/dapr/docs/tree/master/concepts/service-invocation) ## Run To run this demo in Dapr, run: ```shell dapr run \ --app-id grpc-service-demo \ --app-port 50001 \ --app-protocol grpc \ --dapr-http-port 3500 \ --components-path ./config \ go run main.go ``` ## Deploy Deploy and wait for the pod to be ready ```shell kubectl apply -f deployment.yaml kubectl rollout status deployment/grpc-echo-service ``` If you have changed an existing component, make sure to reload the ingress and wait until the new version is ready ```shell kubectl rollout restart deployment/nginx-ingress-nginx-controller kubectl rollout status deployment/nginx-ingress-nginx-controller ``` Follow logs ```shell kubectl logs -l app=grpc-echo-service -c service -f ``` In a separate terminal session export API token ```shell export API_TOKEN=$(kubectl get secret dapr-api-token -o jsonpath="{.data.token}" | base64 --decode) ``` And invoke the service ```shell curl -d '{ "message": "ping" }' \ -H "Content-type: application/json" \ -H "dapr-api-token: ${API_TOKEN}" \ "https://api.cloudylabs.dev/v1.0/invoke/grpc-echo-service/method/echo" ``` The response should include the sent message ```json { "message": "ping" } ``` And the logs ```shell Invocation (ContentType:application/json, Verb:POST, QueryString:map[], Data:{ "message": "ping" }) ``` ## Disclaimer This 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. ## License This software is released under the [MIT](../LICENSE) ================================================ FILE: fan-out/grpc-echo-service/deployment.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: grpc-echo-service labels: app: grpc-echo-service demo: echo spec: replicas: 1 selector: matchLabels: app: grpc-echo-service template: metadata: labels: app: grpc-echo-service demo: echo annotations: dapr.io/enabled: "true" dapr.io/app-id: "grpc-echo-service" dapr.io/app-protocol: "grpc" dapr.io/app-port: "60002" dapr.io/config: "tracing" dapr.io/log-as-json: "true" dapr.io/log-level: "debug" spec: containers: - name: service image: mchmarny/grpc-echo-service:v0.11.1 imagePullPolicy: Always ports: - containerPort: 60002 env: - name: ADDRESS value: ":60002" ================================================ FILE: fan-out/grpc-echo-service/go.mod ================================================ module github.com/mchmarny/dapr-demos/grpc-echo-service go 1.15 require github.com/dapr/go-sdk v0.11.0 ================================================ FILE: fan-out/grpc-echo-service/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo= github.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 h1:4X356008q5SA3YXu8PiRap39KFmy4Lf6sGlceJKZQsU= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0 h1:uslsjIdqvZYANxSBQjTI47vZfwMaTN3mLELkMnMIY/A= google.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: fan-out/grpc-echo-service/main.go ================================================ package main import ( "context" "log" "os" "strings" "github.com/dapr/go-sdk/service/common" daprd "github.com/dapr/go-sdk/service/grpc" ) var ( logger = log.New(os.Stdout, "", 0) serviceAddress = getEnvVar("ADDRESS", ":60015") ) func main() { // create serving server s, err := daprd.NewService(serviceAddress) if err != nil { log.Fatalf("failed to start the server: %v", err) } // add handler to the service s.AddServiceInvocationHandler("echo", echoHandler) // start the server to handle incoming events log.Printf("starting server at %s...", serviceAddress) if err := s.Start(); err != nil { log.Fatalf("server error: %v", err) } } func echoHandler(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) { logger.Printf( "Invocation (ContentType:%s, Verb:%s, QueryString:%s, Data:%s)", in.ContentType, in.Verb, in.QueryString, string(in.Data), ) // TODO: implement handling logic here out = &common.Content{ ContentType: in.ContentType, Data: in.Data, } return } func getEnvVar(key, fallbackValue string) string { if val, ok := os.LookupEnv(key); ok { return strings.TrimSpace(val) } return fallbackValue } ================================================ FILE: fan-out/http-format-converter/Dockerfile ================================================ FROM golang:1.15.0 as builder WORKDIR /src/ COPY . /src/ ENV GO111MODULE=on RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -a -tags netgo -mod vendor -o ./service . FROM gcr.io/distroless/static:nonroot COPY --from=builder /src/service . ENTRYPOINT ["./service"] ================================================ FILE: fan-out/http-format-converter/Makefile ================================================ RELEASE_VERSION =v0.11.1 SERVICE_NAME ?=http-format-converter DOCKER_USERNAME ?=$(DOCKER_USER) TARGET_FORMAT =xml .PHONY: all all: help .PHONY: tidy tidy: ## Updates the go modules and vendors all dependencies go mod tidy go mod vendor .PHONY: tidy test: tidy ## Tests the entire project go test -count=1 -race ./... .PHONY: run run: tidy ## Runs uncompiled code in Dapr (make run TARGET_FORMAT=csv) TARGET_FORMAT=$(TARGET_FORMAT) dapr run \ --app-id $(SERVICE_NAME) \ --app-port 60011 \ --app-protocol grpc \ --components-path ./config \ --log-level debug \ go run main.go .PHONY: image image: tidy ## Builds and publish docker image docker build -t "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" . docker push "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" .PHONY: deploy deploy: ## Deploys prebuild image to k8s using currently selected context kubectl apply -f k8s/components.yaml kubectl apply -f k8s/deployment.yaml kubectl rollout restart deployment/$(SERVICE_NAME) .PHONY: lint lint: ## Lints the entire project golangci-lint run --timeout=3m .PHONY: tag tag: ## Creates release tag git tag $(RELEASE_VERSION) git push origin $(RELEASE_VERSION) .PHONY: clean clean: ## Cleans up generated files go clean rm -fr ./bin rm -fr ./vendor .PHONY: help help: ## Display available commands @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \ 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ================================================ FILE: fan-out/http-format-converter/config/source-pubsub.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: fanout-source-pubsub spec: type: pubsub.redis metadata: - name: redisHost value: localhost:6379 - name: redisPassword value: "" ================================================ FILE: fan-out/http-format-converter/config/target-binding.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: fanout-http-target-post-binding spec: type: bindings.http metadata: - name: url value: https://postman-echo.com/post - name: method value: POST ================================================ FILE: fan-out/http-format-converter/go.mod ================================================ module github.com/mchmarny/dapr-demos/fan-out/http-format-converter go 1.15 require ( github.com/dapr/go-sdk v0.11.0 github.com/pkg/errors v0.9.1 ) ================================================ FILE: fan-out/http-format-converter/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo= github.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 h1:4X356008q5SA3YXu8PiRap39KFmy4Lf6sGlceJKZQsU= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0 h1:uslsjIdqvZYANxSBQjTI47vZfwMaTN3mLELkMnMIY/A= google.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: fan-out/http-format-converter/k8s/components.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: grpc-events spec: type: pubsub.redis metadata: - name: redisHost value: redis-master.redis.svc.cluster.local:6379 - name: redisPassword secretKeyRef: name: redis-secret key: password - name: allowedTopics value: "messages" scopes: - xml-converter ================================================ FILE: fan-out/http-format-converter/k8s/deployment.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: grpc-event-subscriber labels: app: grpc-event-subscriber demo: grpc-event spec: selector: matchLabels: app: grpc-event-subscriber template: metadata: labels: app: grpc-event-subscriber demo: grpc-event annotations: dapr.io/enabled: "true" dapr.io/app-id: "grpc-event-subscriber" dapr.io/app-protocol: "grpc" dapr.io/app-port: "60002" dapr.io/config: "tracing" dapr.io/log-as-json: "true" dapr.io/log-level: "debug" spec: containers: - name: service image: mchmarny/grpc-event-subscriber:v0.11.1 ports: - containerPort: 60002 env: - name: ADDRESS value: ":60002" - name: PUBSUB_NAME value: "grpc-events" - name: TOPIC_NAME value: "messages" ================================================ FILE: fan-out/http-format-converter/main.go ================================================ package main import ( "context" "encoding/json" "encoding/xml" "fmt" "log" "os" "strings" "time" dapr "github.com/dapr/go-sdk/client" "github.com/dapr/go-sdk/service/common" daprd "github.com/dapr/go-sdk/service/grpc" "github.com/pkg/errors" ) var ( logger = log.New(os.Stdout, "", 0) client dapr.Client serviceAddress = getEnvVar("ADDRESS", ":60011") sourcePubSubName = getEnvVar("SOURCE_PUBSUB_NAME", "fanout-source-pubsub") sourceTopicName = getEnvVar("SOURCE_TOPIC_NAME", "events") targetBindingName = getEnvVar("TARGET_BINDING", "fanout-http-target-post-binding") targetFormat = getEnvVar("TARGET_FORMAT", "json") ) func main() { // create Dapr service s, err := daprd.NewService(serviceAddress) if err != nil { log.Fatalf("failed to start the server: %v", err) } c, err := dapr.NewClient() if err != nil { log.Fatalf("failed to create Dapr client: %v", err) } client = c defer client.Close() // add handler to the service sub := &common.Subscription{PubsubName: sourcePubSubName, Topic: sourceTopicName} s.AddTopicEventHandler(sub, eventHandler) // start the server to handle incoming events if err := s.Start(); err != nil { log.Fatalf("server error: %v", err) } } // SourceEvent represents the input event type SourceEvent struct { ID string `json:"id"` Temperature float64 `json:"temperature"` Humidity float64 `json:"humidity"` Time int64 `json:"time"` } func eventHandler(ctx context.Context, e *common.TopicEvent) (retry bool, err error) { logger.Printf("Event - PubsubName:%s, Topic:%s, ID:%s", e.PubsubName, e.Topic, e.ID) d, ok := e.Data.([]byte) if !ok { return false, errors.Errorf("invalid event data type: %T", e.Data) } var se SourceEvent if err := json.Unmarshal(d, &se); err != nil { return false, errors.Errorf("error parsing input content: %v", err) } var ( me error b []byte ) switch strings.ToLower(targetFormat) { case "json": b = d case "xml": if b, me = xml.Marshal(&e); me != nil { return false, errors.Errorf("error while converting content: %v", me) } case "csv": b = []byte(fmt.Sprintf(`"%s",%f,%f,"%s"`, se.ID, se.Temperature, se.Humidity, time.Unix(se.Time, 0).Format(time.RFC3339))) default: return false, errors.Errorf("invalid target format: %s", targetFormat) } logger.Printf("Target (%s): %s", targetFormat, b) content := &dapr.BindingInvocation{ Data: b, Metadata: map[string]string{ "record-id": e.ID, "conversion-time": time.Now().UTC().Format(time.RFC3339), }, Name: targetBindingName, Operation: "create", } if err := client.InvokeOutputBinding(ctx, content); err != nil { return true, errors.Wrap(err, "error invoking target binding") } return false, nil } func getEnvVar(key, fallbackValue string) string { if val, ok := os.LookupEnv(key); ok { return strings.TrimSpace(val) } return fallbackValue } ================================================ FILE: fan-out/queue-event-consumer/Dockerfile ================================================ FROM golang:1.15.0 as builder WORKDIR /src/ COPY . /src/ ENV GO111MODULE=on RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -a -tags netgo -mod vendor -o ./service . FROM gcr.io/distroless/static:nonroot COPY --from=builder /src/service . ENTRYPOINT ["./service"] ================================================ FILE: fan-out/queue-event-consumer/Makefile ================================================ RELEASE_VERSION =v0.11.1 SERVICE_NAME ?=queue-format-consumer DOCKER_USERNAME ?=$(DOCKER_USER) TARGET_FORMAT =xml .PHONY: all all: help .PHONY: tidy tidy: ## Updates the go modules and vendors all dependencies go mod tidy go mod vendor .PHONY: tidy test: tidy ## Tests the entire project go test -count=1 -race ./... .PHONY: run run: tidy ## Runs uncompiled code in Dapr dapr run \ --app-id $(SERVICE_NAME) \ --app-port 60030 \ --app-protocol grpc \ --components-path ./config \ --log-level debug \ go run main.go .PHONY: image image: tidy ## Builds and publish docker image docker build -t "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" . docker push "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" .PHONY: deploy deploy: ## Deploys prebuild image to k8s using currently selected context kubectl apply -f k8s/components.yaml kubectl apply -f k8s/deployment.yaml kubectl rollout restart deployment/$(SERVICE_NAME) .PHONY: lint lint: ## Lints the entire project golangci-lint run --timeout=3m .PHONY: tag tag: ## Creates release tag git tag $(RELEASE_VERSION) git push origin $(RELEASE_VERSION) .PHONY: clean clean: ## Cleans up generated files go clean rm -fr ./bin rm -fr ./vendor .PHONY: help help: ## Display available commands @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \ 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ================================================ FILE: fan-out/queue-event-consumer/config/source-pabusb.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: fanout-source-pubsub spec: type: pubsub.redis metadata: - name: redisHost value: localhost:6379 - name: redisPassword value: "" ================================================ FILE: fan-out/queue-event-consumer/go.mod ================================================ module github.com/mchmarny/dapr-demos/fan-out/queue-format-consumer go 1.15 require github.com/dapr/go-sdk v0.11.0 ================================================ FILE: fan-out/queue-event-consumer/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo= github.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 h1:4X356008q5SA3YXu8PiRap39KFmy4Lf6sGlceJKZQsU= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0 h1:uslsjIdqvZYANxSBQjTI47vZfwMaTN3mLELkMnMIY/A= google.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: fan-out/queue-event-consumer/k8s/deployment.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: grpc-event-subscriber labels: app: grpc-event-subscriber demo: grpc-event spec: selector: matchLabels: app: grpc-event-subscriber template: metadata: labels: app: grpc-event-subscriber demo: grpc-event annotations: dapr.io/enabled: "true" dapr.io/app-id: "grpc-event-subscriber" dapr.io/app-protocol: "grpc" dapr.io/app-port: "60002" dapr.io/config: "tracing" dapr.io/log-as-json: "true" dapr.io/log-level: "debug" spec: containers: - name: service image: mchmarny/grpc-event-subscriber:v0.11.1 ports: - containerPort: 60002 env: - name: ADDRESS value: ":60002" - name: PUBSUB_NAME value: "grpc-events" - name: TOPIC_NAME value: "messages" ================================================ FILE: fan-out/queue-event-consumer/k8s/target-kafka.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: fanout-queue-kafka-target spec: type: pubsub.kafka metadata: - name: brokers value: "localhost:9092" - name: authRequired value: "false" scopes: - xml-converter ================================================ FILE: fan-out/queue-event-consumer/k8s/target-redis.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: fanout-queue-kafka-target spec: type: pubsub.kafka metadata: - name: brokers value: "localhost:9092" - name: authRequired value: "false" scopes: - xml-converter ================================================ FILE: fan-out/queue-event-consumer/main.go ================================================ package main import ( "context" "log" "os" "strings" "github.com/dapr/go-sdk/service/common" daprd "github.com/dapr/go-sdk/service/grpc" ) var ( logger = log.New(os.Stdout, "", 0) serviceAddress = getEnvVar("ADDRESS", ":60030") sourcePubSubName = getEnvVar("SOURCE_PUBSUB_NAME", "fanout-source-pubsub") sourceTopicName = getEnvVar("SOURCE_TOPIC_NAME", "test-topic") ) func main() { // create Dapr service s, err := daprd.NewService(serviceAddress) if err != nil { log.Fatalf("failed to start the server: %v", err) } // add handler to the service sub := &common.Subscription{ PubsubName: sourcePubSubName, Topic: sourceTopicName, } s.AddTopicEventHandler(sub, eventHandler) // start the server to handle incoming events if err := s.Start(); err != nil { log.Fatalf("server error: %v", err) } } func eventHandler(ctx context.Context, e *common.TopicEvent) (retry bool, err error) { logger.Printf("Event - PubsubName:%s, Topic:%s, ID:%s", e.PubsubName, e.Topic, e.ID) return false, nil } func getEnvVar(key, fallbackValue string) string { if val, ok := os.LookupEnv(key); ok { return strings.TrimSpace(val) } return fallbackValue } ================================================ FILE: fan-out/queue-event-producer/Dockerfile ================================================ FROM golang:1.15.0 as builder WORKDIR /src/ COPY . /src/ ENV GO111MODULE=on RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -a -tags netgo -mod vendor -o ./service . FROM gcr.io/distroless/static:nonroot COPY --from=builder /src/service . ENTRYPOINT ["./service"] ================================================ FILE: fan-out/queue-event-producer/Makefile ================================================ RELEASE_VERSION =v0.11.1 SERVICE_NAME ?=queue-event-producer DOCKER_USERNAME ?=$(DOCKER_USER) .PHONY: all all: help .PHONY: tidy tidy: ## Updates the go modules and vendors all dependencies go mod tidy go mod vendor .PHONY: tidy test: tidy ## Tests the entire project go test -count=1 -race ./... .PHONY: run run: tidy ## Runs uncompiled code in Dapr (make run TARGET_FORMAT=csv) dapr run \ --app-id $(SERVICE_NAME) \ --app-port 60013 \ --app-protocol grpc \ --components-path ./config \ --log-level debug \ go run main.go .PHONY: image image: tidy ## Builds and publish docker image docker build -t "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" . docker push "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" .PHONY: deploy deploy: ## Deploys prebuild image to k8s using currently selected context kubectl apply -f k8s/components.yaml kubectl apply -f k8s/deployment.yaml kubectl rollout restart deployment/$(SERVICE_NAME) .PHONY: lint lint: ## Lints the entire project golangci-lint run --timeout=3m .PHONY: tag tag: ## Creates release tag git tag $(RELEASE_VERSION) git push origin $(RELEASE_VERSION) .PHONY: clean clean: ## Cleans up generated files go clean rm -fr ./bin rm -fr ./vendor .PHONY: help help: ## Display available commands @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \ 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ================================================ FILE: fan-out/queue-event-producer/config/target-pabusb.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: fanout-source-pubsub spec: type: pubsub.redis metadata: - name: redisHost value: localhost:6379 - name: redisPassword value: "" ================================================ FILE: fan-out/queue-event-producer/go.mod ================================================ module github.com/mchmarny/dapr-demos/fan-out/queue-event-producer go 1.15 require ( github.com/dapr/go-sdk v0.11.0 github.com/google/uuid v1.1.2 github.com/pkg/errors v0.9.1 ) ================================================ FILE: fan-out/queue-event-producer/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo= github.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 h1:4X356008q5SA3YXu8PiRap39KFmy4Lf6sGlceJKZQsU= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0 h1:uslsjIdqvZYANxSBQjTI47vZfwMaTN3mLELkMnMIY/A= google.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: fan-out/queue-event-producer/k8s/deployment.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: grpc-event-subscriber labels: app: grpc-event-subscriber demo: grpc-event spec: selector: matchLabels: app: grpc-event-subscriber template: metadata: labels: app: grpc-event-subscriber demo: grpc-event annotations: dapr.io/enabled: "true" dapr.io/app-id: "grpc-event-subscriber" dapr.io/app-protocol: "grpc" dapr.io/app-port: "60002" dapr.io/config: "tracing" dapr.io/log-as-json: "true" dapr.io/log-level: "debug" spec: containers: - name: service image: mchmarny/grpc-event-subscriber:v0.11.1 ports: - containerPort: 60002 env: - name: ADDRESS value: ":60002" - name: PUBSUB_NAME value: "grpc-events" - name: TOPIC_NAME value: "messages" ================================================ FILE: fan-out/queue-event-producer/k8s/target-kafka.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: fanout-queue-kafka-target spec: type: pubsub.kafka metadata: - name: brokers value: "localhost:9092" - name: authRequired value: "false" scopes: - xml-converter ================================================ FILE: fan-out/queue-event-producer/k8s/target-redis.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: fanout-queue-kafka-target spec: type: pubsub.kafka metadata: - name: brokers value: "localhost:9092" - name: authRequired value: "false" scopes: - xml-converter ================================================ FILE: fan-out/queue-event-producer/main.go ================================================ package main import ( "context" "encoding/json" "log" "math/rand" "os" "strings" "time" dapr "github.com/dapr/go-sdk/client" daprd "github.com/dapr/go-sdk/service/grpc" "github.com/google/uuid" "github.com/pkg/errors" ) var ( logger = log.New(os.Stdout, "", 0) serviceAddress = getEnvVar("ADDRESS", ":60013") targetPubSubName = getEnvVar("TARGET_PUBSUB_NAME", "fanout-source-pubsub") targetTopicName = getEnvVar("TARGET_TOPIC_NAME", "events") threadFreq = getEnvVar("THREAD_PUB_FREQ", "3s") ) func main() { ctx := context.Background() tf, err := time.ParseDuration(threadFreq) if err != nil { logger.Fatalf("invalid thread frequency, expected duration: %s - %v", threadFreq, err) } logger.Printf("thread frequency: %s", tf) // create Dapr service s, err := daprd.NewService(serviceAddress) if err != nil { logger.Fatalf("failed to start the server: %v", err) } defer s.Stop() // dapr client c, err := dapr.NewClient() if err != nil { logger.Fatalf("failed to create Dapr client: %v", err) } defer c.Close() // timer timer := time.NewTicker(tf) defer timer.Stop() // produce go func() { if err := produce(ctx, c, timer); err != nil { logger.Fatalf("error: %v", err) } }() // start the server to handle incoming events if err := s.Start(); err != nil { log.Fatalf("server error: %v", err) } } func produce(ctx context.Context, c dapr.Client, t *time.Ticker) error { for { select { case <-t.C: b, err := json.Marshal(getRoomReading()) if err != nil { return errors.Wrap(err, "error serializing reading") } if err := c.PublishEvent(ctx, targetPubSubName, targetTopicName, b); err != nil { return errors.Wrap(err, "error publishing content") } logger.Printf("published: %s", b) } } } type roomReading struct { ID string `json:"id"` Temperature float64 `json:"temperature"` Humidity float64 `json:"humidity"` Time int64 `json:"time"` } func getRoomReading() interface{} { min := 0.01 max := 100.00 return &roomReading{ ID: uuid.New().String(), Temperature: min + rand.Float64()*(max-min), Humidity: min + rand.Float64()*(max-min), Time: time.Now().UTC().Unix(), } } func getEnvVar(key, fallbackValue string) string { if val, ok := os.LookupEnv(key); ok { return strings.TrimSpace(val) } return fallbackValue } ================================================ FILE: fan-out/queue-format-converter/Dockerfile ================================================ FROM golang:1.15.0 as builder WORKDIR /src/ COPY . /src/ ENV GO111MODULE=on RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -a -tags netgo -mod vendor -o ./service . FROM gcr.io/distroless/static:nonroot COPY --from=builder /src/service . ENTRYPOINT ["./service"] ================================================ FILE: fan-out/queue-format-converter/Makefile ================================================ RELEASE_VERSION =v0.11.1 SERVICE_NAME ?=queue-format-converter DOCKER_USERNAME ?=$(DOCKER_USER) TARGET_FORMAT =xml .PHONY: all all: help .PHONY: tidy tidy: ## Updates the go modules and vendors all dependencies go mod tidy go mod vendor .PHONY: tidy test: tidy ## Tests the entire project go test -count=1 -race ./... .PHONY: run run: tidy ## Runs uncompiled code in Dapr (make run TARGET_FORMAT=csv) TARGET_TOPIC_FORMAT=$(TARGET_FORMAT) dapr run \ --app-id $(SERVICE_NAME) \ --app-port 60010 \ --app-protocol grpc \ --components-path ./config \ --log-level debug \ go run main.go .PHONY: image image: tidy ## Builds and publish docker image docker build -t "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" . docker push "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" .PHONY: deploy deploy: ## Deploys prebuild image to k8s using currently selected context kubectl apply -f k8s/components.yaml kubectl apply -f k8s/deployment.yaml kubectl rollout restart deployment/$(SERVICE_NAME) .PHONY: lint lint: ## Lints the entire project golangci-lint run --timeout=3m .PHONY: tag tag: ## Creates release tag git tag $(RELEASE_VERSION) git push origin $(RELEASE_VERSION) .PHONY: clean clean: ## Cleans up generated files go clean rm -fr ./bin rm -fr ./vendor .PHONY: help help: ## Display available commands @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \ 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ================================================ FILE: fan-out/queue-format-converter/config/kafka.yaml ================================================ version: '2' services: zookeeper: image: wurstmeister/zookeeper:latest ports: - "2181:2181" kafka: image: wurstmeister/kafka:latest ports: - "9092:9092" environment: KAFKA_ADVERTISED_HOST_NAME: 127.0.0.1 KAFKA_CREATE_TOPICS: "events:1:1" KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 ================================================ FILE: fan-out/queue-format-converter/config/source-pabusb.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: fanout-source-pubsub spec: type: pubsub.redis metadata: - name: redisHost value: localhost:6379 - name: redisPassword value: "" ================================================ FILE: fan-out/queue-format-converter/config/target-pubsub.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: fanout-target-pubsub spec: type: pubsub.kafka metadata: - name: brokers value: "localhost:9092" - name: authRequired value: "false" ================================================ FILE: fan-out/queue-format-converter/go.mod ================================================ module github.com/mchmarny/dapr-demos/fan-out/queue-format-converter go 1.15 require ( github.com/dapr/go-sdk v0.11.0 github.com/pkg/errors v0.9.1 ) ================================================ FILE: fan-out/queue-format-converter/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo= github.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 h1:4X356008q5SA3YXu8PiRap39KFmy4Lf6sGlceJKZQsU= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0 h1:uslsjIdqvZYANxSBQjTI47vZfwMaTN3mLELkMnMIY/A= google.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: fan-out/queue-format-converter/k8s/deployment.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: grpc-event-subscriber labels: app: grpc-event-subscriber demo: grpc-event spec: selector: matchLabels: app: grpc-event-subscriber template: metadata: labels: app: grpc-event-subscriber demo: grpc-event annotations: dapr.io/enabled: "true" dapr.io/app-id: "grpc-event-subscriber" dapr.io/app-protocol: "grpc" dapr.io/app-port: "60002" dapr.io/config: "tracing" dapr.io/log-as-json: "true" dapr.io/log-level: "debug" spec: containers: - name: service image: mchmarny/grpc-event-subscriber:v0.11.1 ports: - containerPort: 60002 env: - name: ADDRESS value: ":60002" - name: PUBSUB_NAME value: "grpc-events" - name: TOPIC_NAME value: "messages" ================================================ FILE: fan-out/queue-format-converter/k8s/target-kafka.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: fanout-queue-kafka-target spec: type: pubsub.kafka metadata: - name: brokers value: "localhost:9092" - name: authRequired value: "false" scopes: - xml-converter ================================================ FILE: fan-out/queue-format-converter/k8s/target-redis.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: fanout-queue-kafka-target spec: type: pubsub.kafka metadata: - name: brokers value: "localhost:9092" - name: authRequired value: "false" scopes: - xml-converter ================================================ FILE: fan-out/queue-format-converter/main.go ================================================ package main import ( "context" "encoding/json" "encoding/xml" "fmt" "log" "os" "strings" "time" dapr "github.com/dapr/go-sdk/client" "github.com/dapr/go-sdk/service/common" daprd "github.com/dapr/go-sdk/service/grpc" "github.com/pkg/errors" ) var ( logger = log.New(os.Stdout, "", 0) client dapr.Client serviceAddress = getEnvVar("ADDRESS", ":60010") sourcePubSubName = getEnvVar("SOURCE_PUBSUB_NAME", "fanout-source-pubsub") sourceTopicName = getEnvVar("SOURCE_TOPIC_NAME", "events") targetPubSubName = getEnvVar("TARGET_PUBSUB_NAME", "fanout-target-pubsub") targetTopicName = getEnvVar("TARGET_TOPIC_NAME", "events") targetTopicFormat = getEnvVar("TARGET_TOPIC_FORMAT", "csv") ) func main() { // create Dapr service s, err := daprd.NewService(serviceAddress) if err != nil { log.Fatalf("failed to start the server: %v", err) } c, err := dapr.NewClient() if err != nil { log.Fatalf("failed to create Dapr client: %v", err) } client = c defer client.Close() // add handler to the service sub := &common.Subscription{PubsubName: sourcePubSubName, Topic: sourceTopicName} s.AddTopicEventHandler(sub, eventHandler) // start the server to handle incoming events if err := s.Start(); err != nil { log.Fatalf("server error: %v", err) } } // SourceEvent represents the input event type SourceEvent struct { ID string `json:"id"` Temperature float64 `json:"temperature"` Humidity float64 `json:"humidity"` Time int64 `json:"time"` } func eventHandler(ctx context.Context, e *common.TopicEvent) (retry bool, err error) { logger.Printf("Event - PubsubName:%s, Topic:%s, ID:%s", e.PubsubName, e.Topic, e.ID) d, ok := e.Data.([]byte) if !ok { return false, errors.Errorf("invalid event data type: %T", e.Data) } var se SourceEvent if err := json.Unmarshal(d, &se); err != nil { return false, errors.Errorf("error parsing input content: %v", err) } var ( me error b []byte ) switch strings.ToLower(targetTopicFormat) { case "json": b = d case "xml": if b, me = xml.Marshal(&e); me != nil { return false, errors.Errorf("error while converting content: %v", me) } case "csv": b = []byte(fmt.Sprintf(`"%s",%f,%f,"%s"`, se.ID, se.Temperature, se.Humidity, time.Unix(se.Time, 0).Format(time.RFC3339))) default: return false, errors.Errorf("invalid target format: %s", targetTopicFormat) } logger.Printf("Target (%s): %s", targetTopicFormat, b) if err := client.PublishEvent(ctx, targetPubSubName, targetTopicName, b); err != nil { return true, errors.Wrap(err, "error publishing converted content") } return false, nil } func getEnvVar(key, fallbackValue string) string { if val, ok := os.LookupEnv(key); ok { return strings.TrimSpace(val) } return fallbackValue } ================================================ FILE: fan-out/service-format-converter/Dockerfile ================================================ FROM golang:1.15.0 as builder WORKDIR /src/ COPY . /src/ ENV GO111MODULE=on RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -a -tags netgo -mod vendor -o ./service . FROM gcr.io/distroless/static:nonroot COPY --from=builder /src/service . ENTRYPOINT ["./service"] ================================================ FILE: fan-out/service-format-converter/Makefile ================================================ RELEASE_VERSION =v0.11.1 SERVICE_NAME ?=service-format-converter DOCKER_USERNAME ?=$(DOCKER_USER) TARGET_FORMAT =xml TARGET_SERVICE =grpc-echo-service TARGET_METHOD =echo .PHONY: all all: help .PHONY: tidy tidy: ## Updates the go modules and vendors all dependencies go mod tidy go mod vendor .PHONY: tidy test: tidy ## Tests the entire project go test -count=1 -race ./... .PHONY: run run: tidy ## Runs uncompiled code in Dapr (make run TARGET_FORMAT=csv) TARGET_FORMAT=$(TARGET_FORMAT) \ TARGET_SERVICE=$(TARGET_SERVICE) \ TARGET_METHOD=$(TARGET_METHOD) \ dapr run \ --app-id $(SERVICE_NAME) \ --app-port 60012 \ --app-protocol grpc \ --components-path ./config \ --log-level debug \ go run main.go .PHONY: image image: tidy ## Builds and publish docker image docker build -t "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" . docker push "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" .PHONY: deploy deploy: ## Deploys prebuild image to k8s using currently selected context kubectl apply -f k8s/components.yaml kubectl apply -f k8s/deployment.yaml kubectl rollout restart deployment/$(SERVICE_NAME) .PHONY: lint lint: ## Lints the entire project golangci-lint run --timeout=3m .PHONY: tag tag: ## Creates release tag git tag $(RELEASE_VERSION) git push origin $(RELEASE_VERSION) .PHONY: clean clean: ## Cleans up generated files go clean rm -fr ./bin rm -fr ./vendor .PHONY: help help: ## Display available commands @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \ 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ================================================ FILE: fan-out/service-format-converter/config/source-pubsub.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: fanout-source-pubsub spec: type: pubsub.redis metadata: - name: redisHost value: localhost:6379 - name: redisPassword value: "" ================================================ FILE: fan-out/service-format-converter/go.mod ================================================ module github.com/mchmarny/dapr-demos/fan-out/service-format-converter go 1.15 require ( github.com/dapr/go-sdk v0.11.0 github.com/pkg/errors v0.9.1 ) ================================================ FILE: fan-out/service-format-converter/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo= github.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 h1:4X356008q5SA3YXu8PiRap39KFmy4Lf6sGlceJKZQsU= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0 h1:uslsjIdqvZYANxSBQjTI47vZfwMaTN3mLELkMnMIY/A= google.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: fan-out/service-format-converter/k8s/components.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: grpc-events spec: type: pubsub.redis metadata: - name: redisHost value: redis-master.redis.svc.cluster.local:6379 - name: redisPassword secretKeyRef: name: redis-secret key: password - name: allowedTopics value: "messages" scopes: - xml-converter ================================================ FILE: fan-out/service-format-converter/k8s/deployment.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: grpc-event-subscriber labels: app: grpc-event-subscriber demo: grpc-event spec: selector: matchLabels: app: grpc-event-subscriber template: metadata: labels: app: grpc-event-subscriber demo: grpc-event annotations: dapr.io/enabled: "true" dapr.io/app-id: "grpc-event-subscriber" dapr.io/app-protocol: "grpc" dapr.io/app-port: "60002" dapr.io/config: "tracing" dapr.io/log-as-json: "true" dapr.io/log-level: "debug" spec: containers: - name: service image: mchmarny/grpc-event-subscriber:v0.11.1 ports: - containerPort: 60002 env: - name: ADDRESS value: ":60002" - name: PUBSUB_NAME value: "grpc-events" - name: TOPIC_NAME value: "messages" ================================================ FILE: fan-out/service-format-converter/main.go ================================================ package main import ( "context" "encoding/json" "encoding/xml" "fmt" "log" "os" "strings" "time" dapr "github.com/dapr/go-sdk/client" "github.com/dapr/go-sdk/service/common" daprd "github.com/dapr/go-sdk/service/grpc" "github.com/pkg/errors" ) var ( logger = log.New(os.Stdout, "", 0) client dapr.Client serviceAddress = getEnvVar("ADDRESS", ":60012") sourcePubSubName = getEnvVar("SOURCE_PUBSUB_NAME", "fanout-source-pubsub") sourceTopicName = getEnvVar("SOURCE_TOPIC_NAME", "events") targetServiceID = getEnvVar("TARGET_SERVICE", "grpc-echo-service") targetMethodName = getEnvVar("TARGET_METHOD", "echo") targetFormat = getEnvVar("TARGET_FORMAT", "xml") ) func main() { // create Dapr service s, err := daprd.NewService(serviceAddress) if err != nil { log.Fatalf("failed to start the server: %v", err) } c, err := dapr.NewClient() if err != nil { log.Fatalf("failed to create Dapr client: %v", err) } client = c defer client.Close() // add handler to the service sub := &common.Subscription{PubsubName: sourcePubSubName, Topic: sourceTopicName} s.AddTopicEventHandler(sub, eventHandler) // start the server to handle incoming events if err := s.Start(); err != nil { log.Fatalf("server error: %v", err) } } // SourceEvent represents the input event type SourceEvent struct { ID string `json:"id"` Temperature float64 `json:"temperature"` Humidity float64 `json:"humidity"` Time int64 `json:"time"` } func eventHandler(ctx context.Context, e *common.TopicEvent) (retry bool, err error) { logger.Printf("Event - PubsubName:%s, Topic:%s, ID:%s", e.PubsubName, e.Topic, e.ID) d, ok := e.Data.([]byte) if !ok { return false, errors.Errorf("invalid event data type: %T", e.Data) } var se SourceEvent if err := json.Unmarshal(d, &se); err != nil { return false, errors.Errorf("error parsing input content: %v", err) } var ( me error b []byte ct string ) switch strings.ToLower(targetFormat) { case "json": b = d ct = "application/json" case "xml": if b, me = xml.Marshal(&e); me != nil { return false, errors.Errorf("error while converting content: %v", me) } ct = "application/xml" case "csv": b = []byte(fmt.Sprintf(`"%s",%f,%f,"%s"`, se.ID, se.Temperature, se.Humidity, time.Unix(se.Time, 0).Format(time.RFC3339))) ct = "text/csv" default: return false, errors.Errorf("invalid target format: %s", targetFormat) } logger.Printf("Target (%s): %s", targetFormat, b) content := &dapr.DataContent{Data: b, ContentType: ct} out, err := client.InvokeServiceWithContent(ctx, targetServiceID, targetMethodName, content) if err != nil { return true, errors.Wrap(err, "error invoking target binding") } logger.Printf("Response: %s", out) return false, nil } func getEnvVar(key, fallbackValue string) string { if val, ok := os.LookupEnv(key); ok { return strings.TrimSpace(val) } return fallbackValue } ================================================ FILE: grpc-echo-service/Dockerfile ================================================ FROM golang:1.15.5 as builder WORKDIR /src/ COPY . /src/ ENV GO111MODULE=on RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -a -tags netgo -mod vendor -o ./app . FROM gcr.io/distroless/static:nonroot COPY --from=builder /src/app . ENTRYPOINT ["./app"] ================================================ FILE: grpc-echo-service/Makefile ================================================ RELEASE_VERSION =v1.0.1 SERVICE_NAME ?=echo IMAGE_NAME ?=grpc-echo-service IMAGE_TAG ?=v0.1.4 IMAGE_OWNER ?=$(shell git config --get user.username) .PHONY: all all: help .PHONY: tidy tidy: ## Updates the go modules and vendors all dependencies go mod tidy go mod vendor .PHONY: test test: tidy ## Tests the entire project go test -count=1 -race ./... .PHONY: run run: tidy ## Runs uncompiled code in Dapr dapr run \ --app-id $(SERVICE_NAME) \ --app-port 60002 \ --app-protocol grpc \ --dapr-http-port 3500 \ go run main.go .PHONY: invoke invoke: ## Invokes service through Dapr API curl -d '{ "message": "ping" }' \ -H "Content-type: application/json" \ "http://localhost:3500/v1.0/invoke/$(SERVICE_NAME)/method/echo" .PHONY: image image: tidy ## Builds and publish image docker build -t "ghcr.io/$(IMAGE_OWNER)/$(IMAGE_NAME):$(IMAGE_TAG)" . docker push "ghcr.io/$(IMAGE_OWNER)/$(IMAGE_NAME):$(IMAGE_TAG)" .PHONY: call call: ## Invokes service through Dapr API $(eval API_TOKEN=$(shell kubectl get secret dapr-api-token -o jsonpath="{.data.token}" | base64 --decode)) curl -d '{ "message": "ping" }' \ -H "Content-type: application/json" \ -H "dapr-api-token: $(API_TOKEN)" \ "https://api.cloudylabs.dev/v1.0/invoke/$(SERVICE_NAME)/method/echo" .PHONY: lint lint: ## Lints the entire project golangci-lint run --timeout=3m .PHONY: tag tag: ## Creates release tag git tag $(RELEASE_VERSION) git push origin $(RELEASE_VERSION) .PHONY: clean clean: ## Cleans up generated files go clean rm -fr ./bin rm -fr ./vendor .PHONY: help help: ## Display available commands @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \ 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ================================================ FILE: grpc-echo-service/README.md ================================================ # grpc-service For more information about service invocation see the [Dapr docs](https://github.com/dapr/docs/tree/master/concepts/service-invocation) > 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). ## Run To run this demo in Dapr, run: ```shell dapr run \ --app-id echo \ --app-port 50001 \ --app-protocol grpc \ --dapr-http-port 3500 \ --components-path ./config \ go run main.go ``` ## Deploy To deploy this demo, first setup the `echo` namespace: ```shell kubectl apply -f deployment/space.yaml ``` Then deploy and wait for the `echo-service` app pod to be ready: ```shell kubectl apply -f deployment/app.yaml kubectl rollout status deployment/echo-service -n echo ``` If you have changed an existing component, make sure to reload the ingress and wait until the new version is ready ```shell kubectl rollout restart deployment/echo-service -n echo kubectl rollout status deployment/echo-service -n echo ``` Follow logs ```shell kubectl logs -l app=echo-service -c service -f -n echo ``` In a separate terminal session export API token ```shell export API_TOKEN=$(kubectl get secret dapr-api-token -n nginx -o jsonpath="{.data.token}" | base64 --decode) ``` And invoke the service ```shell curl -d '{ "message": "ping" }' \ -H "Content-type: application/json" \ -H "dapr-api-token: ${API_TOKEN}" \ "https://api.demo.dapr.team/v1.0/invoke/echo-service.echo/method/echo" ``` > Notice the use of `echo-service.echo` namespace in the service invocation The response should include the sent message ```json { "message": "ping" } ``` And the logs ```shell Invocation (ContentType:application/json, Verb:POST, QueryString:map[], Data:{ "message": "ping" }) ``` ## Disclaimer This 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. ## License This software is released under the [MIT](../LICENSE) ================================================ FILE: grpc-echo-service/deployment/app.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: echo-service namespace: echo labels: app: echo-service spec: replicas: 1 selector: matchLabels: app: echo-service template: metadata: labels: app: echo-service annotations: dapr.io/enabled: "true" dapr.io/app-id: "echo-service" dapr.io/app-protocol: "grpc" dapr.io/app-port: "60002" dapr.io/config: "echo-config" dapr.io/log-as-json: "true" dapr.io/log-level: "debug" spec: containers: - name: service image: ghcr.io/mchmarny/grpc-echo-service:v0.1.4 ports: - containerPort: 60002 env: - name: ADDRESS value: ":60002" ================================================ FILE: grpc-echo-service/deployment/space.yaml ================================================ apiVersion: v1 kind: Namespace metadata: name: echo --- apiVersion: dapr.io/v1alpha1 kind: Configuration metadata: name: echo-config namespace: echo spec: tracing: samplingRate: "1" --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: secret-reader namespace: echo rules: - apiGroups: [""] resources: ["secrets"] verbs: ["get"] --- kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: dapr-secret-reader namespace: echo subjects: - kind: ServiceAccount name: default roleRef: kind: Role name: secret-reader apiGroup: rbac.authorization.k8s.io --- apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: zipkin namespace: echo spec: type: exporters.zipkin metadata: - name: enabled value: "true" - name: exporterAddress value: "http://zipkin.dapr-monitoring.svc.cluster.local:9411/api/v2/spans" ================================================ FILE: grpc-echo-service/go.mod ================================================ module github.com/mchmarny/dapr-demos/grpc-echo-service go 1.15 require ( github.com/dapr/go-sdk v1.0.0-rc-1 golang.org/x/sys v0.0.0-20201116194326-cc9327a14d48 // indirect google.golang.org/genproto v0.0.0-20201116205149-79184cff4dfe // indirect ) ================================================ FILE: grpc-echo-service/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dapr/go-sdk v1.0.0-rc-1 h1:aBkgeQfrE9VAabvwL094z4RJWwt41gNKAusE2JoOrB4= github.com/dapr/go-sdk v1.0.0-rc-1/go.mod h1:A53nH+Wc8ahU9+A2Eddz11916eE6opTmwfxAajL0bYo= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201116194326-cc9327a14d48 h1:AYCWBZhgIw6XobZ5CibNJr0Rc4ZofGGKvWa1vcx2IGk= golang.org/x/sys v0.0.0-20201116194326-cc9327a14d48/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20201112120144-2985b7af83de/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201116205149-79184cff4dfe h1:4zuxUDBUB7MD0/Nb3BD2e2+YHXs2BQxZ9ivB5RJd7ng= google.golang.org/genproto v0.0.0-20201116205149-79184cff4dfe/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2 h1:EQyQC3sa8M+p6Ulc8yy9SWSS2GVwyRc83gAbG8lrl4o= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: grpc-echo-service/main.go ================================================ package main import ( "context" "log" "os" "strings" "github.com/dapr/go-sdk/service/common" daprd "github.com/dapr/go-sdk/service/grpc" ) var ( logger = log.New(os.Stdout, "", 0) serviceAddress = getEnvVar("ADDRESS", ":60002") ) func main() { // create serving server s, err := daprd.NewService(serviceAddress) if err != nil { log.Fatalf("failed to start the server: %v", err) } // add handler to the service s.AddServiceInvocationHandler("echo", echoHandler) // start the server to handle incoming events log.Printf("starting server at %s...", serviceAddress) if err := s.Start(); err != nil { log.Fatalf("server error: %v", err) } } func echoHandler(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) { logger.Printf( "Invocation (ContentType:%s, Verb:%s, QueryString:%s, Data:%s)", in.ContentType, in.Verb, in.QueryString, string(in.Data), ) // TODO: implement handling logic here out = &common.Content{ ContentType: in.ContentType, Data: in.Data, } return } func getEnvVar(key, fallbackValue string) string { if val, ok := os.LookupEnv(key); ok { return strings.TrimSpace(val) } return fallbackValue } ================================================ FILE: grpc-event-subscriber/Dockerfile ================================================ FROM golang:1.15.0 as builder WORKDIR /src/ COPY . /src/ ENV GO111MODULE=on RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -a -tags netgo -mod vendor -o ./service . FROM gcr.io/distroless/static:nonroot COPY --from=builder /src/service . ENTRYPOINT ["./service"] ================================================ FILE: grpc-event-subscriber/Makefile ================================================ RELEASE_VERSION =v0.11.1 SERVICE_NAME ?=grpc-event-subscriber DOCKER_USERNAME ?=$(DOCKER_USER) .PHONY: tidy test debug build run jsonevent xmlevent binevent image lint clean tag all: help tidy: ## Updates the go modules and vendors all dependencies go mod tidy go mod vendor test: tidy ## Tests the entire project go test -count=1 -race ./... debug: tidy ## Runs uncompiled code in Dapr dapr run \ --app-id $(SERVICE_NAME) \ --app-port 60002 \ --app-protocol grpc \ --dapr-http-port 3500 \ --components-path ./config \ go run main.go build: tidy ## Builds local release binary CGO_ENABLED=0 go build -a -tags netgo -mod vendor -o bin/$(SERVICE_NAME) . run: build ## Builds binary and runs it in Dapr dapr run \ --app-id $(SERVICE_NAME) \ --app-port 60002 \ --app-protocol grpc \ --dapr-http-port 3500 \ --components-path ./config \ bin/$(SERVICE_NAME) jsonevent: ## Publishes sample JSON message to Dapr pubsub API curl -d '{ "from": "John", "to": "Lary", "message": "hi" }' \ -H "Content-type: application/json" \ "http://localhost:3500/v1.0/publish/grpc-events/messages" xmlevent: ## Publishes sample XML message to Dapr pubsub API curl -d 'JohnLary' \ -H "Content-type: application/xml" \ "http://localhost:3500/v1.0/publish/grpc-events/messages" binevent: ## Publishes sample binary message to Dapr pubsub API curl -d '0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40' \ -H "Content-type: application/octet-stream" \ "http://localhost:3500/v1.0/publish/grpc-events/messages" image: tidy ## Builds and publish docker image docker build -t "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" . docker push "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" deploy: ## Deploys prebuild image to k8s using currently selected context kubectl apply -f k8s/component.yaml kubectl apply -f k8s/deployment.yaml kubectl rollout restart deployment/grpc-event-subscriber kubectl rollout restart deployment/nginx-ingress-nginx-controller kubectl rollout status deployment/nginx-ingress-nginx-controller event: ## Publishes sample JSON message to Dapr pubsub API $(eval API_TOKEN=$(shell kubectl get secret dapr-api-token -o jsonpath="{.data.token}" | base64 --decode)) curl -d '{ "from": "John", "to": "Lary", "message": "hi" }' \ -H "Content-type: application/json" \ -H "dapr-api-token: $(API_TOKEN)" \ "https://api.cloudylabs.dev/v1.0/publish/grpc-events/messages" lint: ## Lints the entire project golangci-lint run --timeout=3m tag: ## Creates release tag git tag $(RELEASE_VERSION) git push origin $(RELEASE_VERSION) clean: ## Cleans up generated files go clean rm -fr ./bin rm -fr ./vendor help: ## Display available commands @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \ 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ================================================ FILE: grpc-event-subscriber/README.md ================================================ # grpc-event-subscriber ## Components ```yaml apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: events spec: type: pubsub.redis metadata: - name: redisHost value: localhost:6379 - name: redisPassword value: "" ``` For more information about pub/sub see the [Dapr docs](https://github.com/dapr/docs/tree/master/concepts/publish-subscribe-messaging) ## Run To run this demo in Dapr, run: ```shell dapr run --app-id grpc-event-subscriber-demo \ --app-port 50001 \ --app-protocol grpc \ --dapr-http-port 3500 \ --components-path ./config \ go run main.go ``` ## Deploy Deploy and wait for the pod to be ready ```shell kubectl apply -f k8s/component.yaml kubectl apply -f k8s/deployment.yaml kubectl rollout status deployment/grpc-event-subscriber ``` If you have changed an existing component, make sure to reload the deployment and wait until the new version is ready ```shell kubectl rollout restart deployment/nginx-ingress-nginx-controller kubectl rollout status deployment/nginx-ingress-nginx-controller ``` Follow logs ```shell kubectl logs -l app=grpc-event-subscriber -c service -f ``` In a separate terminal session export API token ```shell export API_TOKEN=$(kubectl get secret dapr-api-token -o jsonpath="{.data.token}" | base64 --decode) ``` And invoke the service ```shell curl -d '{ "from": "John", "to": "Lary", "message": "hi" }' \ -H "Content-type: application/json" \ -H "dapr-api-token: ${API_TOKEN}" \ "https://api.cloudylabs.dev/v1.0/publish/grpc-events/messages" ``` In the logs, you should see now an entry similar to this. Feel free to edit the message and try again. ```shell event - PubsubName:http-events, Topic:messages, ID:6b6cc665-684d-456c-8880-56e20cdf0519, Data: map[from:John message:hi to:Lary] ``` ## Disclaimer This 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. ## License This software is released under the [MIT](../LICENSE) ================================================ FILE: grpc-event-subscriber/config/pubsub.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: grpc-events spec: type: pubsub.redis metadata: - name: redisHost value: localhost:6379 - name: redisPassword value: "" ================================================ FILE: grpc-event-subscriber/go.mod ================================================ module github.com/mchmarny/dapr-demos/grpc-event-subscriber go 1.15 require github.com/dapr/go-sdk v0.11.0 ================================================ FILE: grpc-event-subscriber/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo= github.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 h1:4X356008q5SA3YXu8PiRap39KFmy4Lf6sGlceJKZQsU= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0 h1:uslsjIdqvZYANxSBQjTI47vZfwMaTN3mLELkMnMIY/A= google.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: grpc-event-subscriber/k8s/component.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: grpc-events spec: type: pubsub.redis metadata: - name: redisHost value: redis-master.redis.svc.cluster.local:6379 - name: redisPassword secretKeyRef: name: redis-secret key: password - name: allowedTopics value: "messages" scopes: - nginx-ingress - grpc-event-subscriber ================================================ FILE: grpc-event-subscriber/k8s/deployment.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: grpc-event-subscriber labels: app: grpc-event-subscriber demo: grpc-event spec: selector: matchLabels: app: grpc-event-subscriber template: metadata: labels: app: grpc-event-subscriber demo: grpc-event annotations: dapr.io/enabled: "true" dapr.io/app-id: "grpc-event-subscriber" dapr.io/app-protocol: "grpc" dapr.io/app-port: "60002" dapr.io/config: "tracing" dapr.io/log-as-json: "true" dapr.io/log-level: "debug" spec: containers: - name: service image: mchmarny/grpc-event-subscriber:v0.11.1 ports: - containerPort: 60002 env: - name: ADDRESS value: ":60002" - name: PUBSUB_NAME value: "grpc-events" - name: TOPIC_NAME value: "messages" ================================================ FILE: grpc-event-subscriber/main.go ================================================ package main import ( "context" "log" "os" "strings" "github.com/dapr/go-sdk/service/common" daprd "github.com/dapr/go-sdk/service/grpc" ) var ( logger = log.New(os.Stdout, "", 0) serviceAddress = getEnvVar("ADDRESS", ":60002") pubSubName = getEnvVar("PUBSUB_NAME", "grpc-events") topicName = getEnvVar("TOPIC_NAME", "messages") ) func main() { // create Dapr service s, err := daprd.NewService(serviceAddress) if err != nil { logger.Fatalf("failed to start the server: %v", err) } // add handler to the service subscription := &common.Subscription{ PubsubName: pubSubName, Topic: topicName, } s.AddTopicEventHandler(subscription, eventHandler) // start the server to handle incoming events if err := s.Start(); err != nil { logger.Fatalf("server error: %v", err) } } func eventHandler(ctx context.Context, e *common.TopicEvent) (retry bool, err error) { logger.Printf( "event - PubsubName:%s, Topic:%s, ID:%s, Data: %s", e.PubsubName, e.Topic, e.ID, e.Data, ) // TODO: do something with the cloud event data return false, nil } func getEnvVar(key, fallbackValue string) string { if val, ok := os.LookupEnv(key); ok { return strings.TrimSpace(val) } return fallbackValue } ================================================ FILE: hardened/Makefile ================================================ .PHONY: all all: help .PHONY: apply apply: ## Applies all and restarts the apps # apply kubectl apply -f k8s/ -n hardened # restart kubectl rollout restart deployment/app1 -n hardened kubectl rollout restart deployment/app2 -n hardened kubectl rollout restart deployment/app3 -n hardened # status kubectl rollout status deployment/app1 -n hardened kubectl rollout status deployment/app2 -n hardened kubectl rollout status deployment/app3 -n hardened .PHONY: ping ping: ## Execute ping on the Dapr API $(eval API_TOKEN=$(shell kubectl get secret dapr-api-token -o jsonpath="{.data.token}" | base64 --decode)) curl -i -d '{ "message": "hello" }' \ -H "Content-type: application/json" \ -H "dapr-api-token: $(API_TOKEN)" \ https://api.thingz.io/v1.0/invoke/app1.hardened/method/ping .PHONY: count count: ## Execute counter on the Dapr API $(eval API_TOKEN=$(shell kubectl get secret dapr-api-token -o jsonpath="{.data.token}" | base64 --decode)) curl -i -d '{ "on": 1603627556200126373, "count": 2 }' \ -H "Content-type: application/json" \ -H "dapr-api-token: $(API_TOKEN)" \ https://api.thingz.io/v1.0/invoke/app2.hardened/method/counter .PHONY: help help: ## Display available commands @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \ 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ================================================ FILE: hardened/README.md ================================================ # Hardening Dapr app demo In 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: * Cross-namespace service invocation with logical domain groups trust relationship management * Secure microservice communication using mTLS and [SPIFFE](https://spiffe.io/) identity verification * Per operation verb access control settings (e.g. deny all except `POST` from `app2` on `/op1`) * Per application component scoping (i.e. which app should be able to access a given component) * Pub/Sub topic scoping (i.e. which app should be able to publish or subscriber to a given topic) * Application-level secret access control (i.e. which secrets the app should be able to access) * Token authentication on cluster ingress for both HTTP and gRPC API ![](img/diagram.png) > 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). ## Setup In 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. > For purposes of this demo, the namespace will be called `hardened` but you can choose your own name. ```shell kubectl apply -f deployment/namespace ``` Also, 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. ```shell kubectl create secret generic redis-secret \ --from-literal=password="${REDIS_PASS}" \ -n hardened ``` > 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. Finally, create one more `demo` secret to illustrate later how Dapr controls application's access to secrets. ```shell kubectl create secret generic demo-secret --from-literal=demo="demo" -n hardened ``` ## Deploy With the namespace configured and the Redis password created, it's time to deploy: * [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. * [pubsub.yaml](./deployment/hardened/pubsub.yaml) and [state.yaml](./deployment/hardened/state.yaml) are the configuration files for PubSub and State components using Redis * [role.yaml](./deployment/hardened/role.yaml) defines the Role and RoleBinding required for Dapr application access the Kubernetes secrets in the `hardened` namespace. > 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. Now, apply the demo resources to the cluster. ```shell kubectl apply -f deployment/hardened -n hardened ``` The response from the above command should confirm that all the resources were configured. ```shell deployment.apps/app1 configured configuration.dapr.io/app1-config configured deployment.apps/app2 configured configuration.dapr.io/app2-config configured deployment.apps/app3 configured configuration.dapr.io/app3-config configured component.dapr.io/pubsub configured component.dapr.io/state configured ``` ## Verify To ensure the rest of the demo goes smoothly, check that everything was deployed correctly. ```shell kubectl get pods -n hardened ``` If 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. ```shell NAME READY STATUS RESTARTS AGE app1-6df587fb45-k46sz 2/2 Running 0 40s app2-685fd94f69-5vkwl 2/2 Running 0 40s app3-6d57778cbd-mxn2k 2/2 Running 0 40s ``` ## Demo The 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. ```shell export API_TOKEN=$(kubectl get secret dapr-api-token -n nginx -o jsonpath="{.data.token}" | base64 --decode) ``` ### Service Invocation The 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: ```yaml annotations: dapr.io/config: "app1-config" ``` In 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`. ```yaml accessControl: defaultAction: deny trustDomain: "hardened" policies: - appId: nginx-ingress namespace: "default" defaultAction: deny operations: - name: /ping httpVerb: ["POST"] action: allow ``` To demo this now, invoke the `ping` method on `app1` in the `hardened` namespace using the Dapr API exposed on the NGNX ingress. > 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. ```shell curl -i -d '{ "message": "hello" }' \ -H "Content-type: application/json" \ -H "dapr-api-token: ${API_TOKEN}" \ https://api.demo.dapr.team/v1.0/invoke/app1.hardened/method/ping ``` Dapr 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. > The count of API invocations is persisted in the Dapr sate store configured in [State component](./deployment/hardened/state.yaml) ```shell HTTP/2 200 date: Sun, 25 Oct 2020 12:05:56 GMT content-type: text/plain; charset=utf-8 content-length: 39 traceparent: 00-ecbbc473826b3e328ea00f5ac0ce222b-0824d3896092d8ce-01 strict-transport-security: max-age=15724800; includeSubDomains { "on": 1603627556200126373, "count": 8 } ``` To demo the active access policy, try also to invoke the `counter` method on `app2` in the `hardened` namespace. ```shell curl -i -d '{ "on": 1603627556200126373, "count": 2 }' \ -H "Content-type: application/json" \ -H "dapr-api-token: ${API_TOKEN}" \ https://api.demo.dapr.team/v1.0/invoke/app2.hardened/method/counter ``` That invocation will result in an error. The response will include `PermissionDenied` message: ```json { "errorCode": "ERR_DIRECT_INVOKE", "message": "rpc error: code = PermissionDenied desc = access control policy has denied access to appid: app2 operation: ping verb: POST" } ``` The 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`: ```yaml policies: - appId: app1 defaultAction: deny trustDomain: "hardened" namespace: "hardened" operations: - name: /counter httpVerb: ["POST"] action: allow ``` To demo this, forward local port to any other Dapr sidecar besides `app1` in that cluster. ```shell kubectl port-forward deployment/app2 3500 -n hardened ``` And then try to invoke the `/ping` method on the `app1`. That too will result in `PermissionDenied` message. ```shell curl -i -d '{ "message": "hello" }' \ -H "Content-type: application/json" \ http://localhost:3500/v1.0/invoke/app1/method/ping ``` > In this configuration, all invocations that are not explicitly permitted in Dapr access policy will be denied! ### Components Just 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`: ```yaml scopes: - app2 ``` > Dapr automatically isolates state between applications by contextualizing to the app that created it, rendering it inaccessible by any other application regardless of policies. ### Topic Publishing and Subscription The 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: ```yaml - name: publishingScopes value: "app2=messages" - name: subscriptionScopes value: "app3=messages" ``` To demo this, while still forwarding local port to the `app2` pod, try publish to any other topic besides `messages`. ```shell curl -i -d '{ "message": "test" }' \ -H "Content-type: application/json" \ http://localhost:3500/v1.0/publish/pubsub/test ``` The above publish will result in error: ```json { "errorCode": "ERR_PUBSUB_PUBLISH_MESSAGE", "message": "topic test is not allowed for app id app2" } ``` You 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. ### Secrets Application 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`: ```yaml secrets: scopes: - storeName: kubernetes defaultAccess: deny allowedSecrets: ["redis-secret"] ``` To demo this, while still forwarding local port to the `app2` pod, and access the `redis-secret`. ```shell curl -i "http://localhost:3500/v1.0/secrets/kubernetes/redis-secret?metadata.namespace=hardened" ``` Now, try access the other secret we created in the `hardened` namespace during setup: `demo-secret`. ```shell curl -i "http://localhost:3500/v1.0/secrets/kubernetes/demo-secret?metadata.namespace=hardened" ``` The 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`. ```json { "errorCode": "ERR_PERMISSION_DENIED", "message": "Access denied by policy to get demo-secret from kubernetes" } ``` ## Tracing Start by generating some requests: ```shell for i in {1..100}; do \ sleep 1; \ curl -i -d '{ "message": "hello" }' \ -H "Content-type: application/json" \ -H "dapr-api-token: ${API_TOKEN}" \ https://api.demo.dapr.team/v1.0/invoke/app1.hardened/method/ping done ``` Then forward the Zipkin port locally: ```shell kubectl port-forward svc/zipkin 9411 -n dapr-monitoring ``` And navigate to the Zipkin UI to review traces http://localhost:9411/ ![](img/trace.png) ## Summary This 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. ## Logging To 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`): ```shell kubectl logs -l app=app1 -c daprd -n hardened ``` > 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 ```shell kubectl logs -l app=app1 -c daprd -n hardened --tail 300 | jq ".msg" ``` Resulting in: ```shell "starting Dapr Runtime -- version 0.11.3 -- commit a1a8e11" "log level set to: debug" "metrics server started on :9090/" "kubernetes mode configured" "app id: app1" ``` ## Restarts If you update components you may have to restart the deployments. ```shell kubectl rollout restart deployment app1 app2 app3 -n hardened kubectl rollout status deployment app1 -n hardened kubectl rollout status deployment app2 -n hardened kubectl rollout status deployment app3 -n hardened ``` ## Cleanup > By deleting the `hardened` Kubernetes will cascade delete all resources created in that namespace. ```shell kubectl delete ns hardened ``` ## Disclaimer This 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. ## License This software is released under the [MIT](../LICENSE) ================================================ FILE: hardened/config/pubsub.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: pubsub namespace: hardened spec: type: pubsub.redis metadata: - name: redisHost value: localhost:6379 - name: redisPassword value: "" - name: publishingScopes value: "app2=messages" - name: subscriptionScopes value: "app3=messages" scopes: - app2 - app3 ================================================ FILE: hardened/config/state.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: state namespace: hardened spec: type: state.redis metadata: - name: redisHost value: localhost:6379 - name: redisPassword value: "" scopes: - app2 ================================================ FILE: hardened/deployment/hardened/app1.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: app1 namespace: hardened labels: app: app1 demo: hardened spec: replicas: 1 selector: matchLabels: app: app1 template: metadata: labels: app: app1 demo: hardened annotations: dapr.io/enabled: "true" dapr.io/app-id: "app1" dapr.io/app-protocol: "http" dapr.io/app-port: "8081" dapr.io/config: "app1-config" dapr.io/log-as-json: "true" dapr.io/log-level: "debug" spec: containers: - name: app image: ghcr.io/mchmarny/hardened-app1:v0.1.4 ports: - containerPort: 8081 env: - name: PUBSUB_NAME value: "pubsub" - name: TOPIC_NAME value: "messages" --- apiVersion: dapr.io/v1alpha1 kind: Configuration metadata: name: app1-config namespace: hardened spec: tracing: samplingRate: "1" secrets: scopes: - storeName: kubernetes defaultAccess: deny accessControl: defaultAction: deny trustDomain: "hardened" policies: - appId: nginx-ingress defaultAction: deny trustDomain: "public" namespace: "nginx" # where NGINX is installed operations: - name: /ping httpVerb: ["POST"] action: allow ================================================ FILE: hardened/deployment/hardened/app2.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: app2 namespace: hardened labels: app: app2 demo: hardened spec: replicas: 1 selector: matchLabels: app: app2 template: metadata: labels: app: app2 demo: hardened annotations: dapr.io/enabled: "true" dapr.io/app-id: "app2" dapr.io/app-protocol: "http" dapr.io/app-port: "8082" dapr.io/config: "app2-config" dapr.io/log-as-json: "true" dapr.io/log-level: "debug" spec: containers: - name: app image: ghcr.io/mchmarny/hardened-app2:v0.1.4 ports: - containerPort: 8082 env: - name: PUBSUB_NAME value: "pubsub" - name: TOPIC_NAME value: "messages" - name: STORE_NAME value: "state" --- apiVersion: dapr.io/v1alpha1 kind: Configuration metadata: name: app2-config namespace: hardened spec: tracing: samplingRate: "1" secrets: scopes: - storeName: kubernetes defaultAccess: deny allowedSecrets: ["redis-secret"] accessControl: defaultAction: deny trustDomain: "hardened" policies: - appId: app1 defaultAction: deny trustDomain: "hardened" namespace: "hardened" operations: - name: /counter httpVerb: ["POST"] action: allow ================================================ FILE: hardened/deployment/hardened/app3.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: app3 namespace: hardened labels: app: app3 demo: hardened spec: replicas: 1 selector: matchLabels: app: app3 template: metadata: labels: app: app3 demo: hardened annotations: dapr.io/enabled: "true" dapr.io/app-id: "app3" dapr.io/app-protocol: "http" dapr.io/app-port: "8083" dapr.io/config: "app3-config" dapr.io/log-as-json: "true" dapr.io/log-level: "debug" spec: containers: - name: app image: ghcr.io/mchmarny/hardened-app3:v0.1.4 ports: - containerPort: 8083 env: - name: PUBSUB_NAME value: "pubsub" - name: TOPIC_NAME value: "messages" --- apiVersion: dapr.io/v1alpha1 kind: Configuration metadata: name: app3-config namespace: hardened spec: tracing: samplingRate: "1" secrets: scopes: - storeName: kubernetes defaultAccess: deny allowedSecrets: ["redis-secret"] ================================================ FILE: hardened/deployment/hardened/pubsub.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: pubsub namespace: hardened spec: type: pubsub.redis metadata: - name: redisHost value: redis-master.redis.svc.cluster.local:6379 - name: redisPassword secretKeyRef: name: redis-secret key: password - name: publishingScopes value: "app2=messages" - name: subscriptionScopes value: "app3=messages" scopes: - app2 - app3 ================================================ FILE: hardened/deployment/hardened/state.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: state namespace: hardened spec: type: state.redis metadata: - name: redisHost value: redis-master.redis.svc.cluster.local:6379 - name: redisPassword secretKeyRef: name: redis-secret key: password scopes: - app2 ================================================ FILE: hardened/deployment/namespace/ns.yaml ================================================ apiVersion: v1 kind: Namespace metadata: name: hardened ================================================ FILE: hardened/deployment/namespace/role.yaml ================================================ apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: secret-reader namespace: hardened rules: - apiGroups: [""] resources: ["secrets"] verbs: ["get"] --- kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: dapr-secret-reader namespace: hardened subjects: - kind: ServiceAccount name: default roleRef: kind: Role name: secret-reader apiGroup: rbac.authorization.k8s.io ================================================ FILE: hardened/deployment/namespace/trace.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: zipkin namespace: hardened spec: type: exporters.zipkin metadata: - name: enabled value: "true" - name: exporterAddress value: "http://zipkin.dapr-monitoring.svc.cluster.local:9411/api/v2/spans" ================================================ FILE: hardened/deployment/simple/app1.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: app1 namespace: hardened labels: app: app1 demo: hardened spec: replicas: 1 selector: matchLabels: app: app1 template: metadata: labels: app: app1 demo: hardened annotations: dapr.io/enabled: "true" dapr.io/app-id: "app1" dapr.io/app-protocol: "http" dapr.io/app-port: "8081" dapr.io/log-as-json: "true" dapr.io/log-level: "debug" spec: containers: - name: app image: ghcr.io/mchmarny/hardened-app1:v0.1.4 ports: - containerPort: 8081 env: - name: PUBSUB_NAME value: "pubsub" - name: TOPIC_NAME value: "messages" ================================================ FILE: hardened/deployment/simple/app2.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: app2 namespace: hardened labels: app: app2 demo: hardened spec: replicas: 1 selector: matchLabels: app: app2 template: metadata: labels: app: app2 demo: hardened annotations: dapr.io/enabled: "true" dapr.io/app-id: "app2" dapr.io/app-protocol: "http" dapr.io/app-port: "8082" dapr.io/log-as-json: "true" dapr.io/log-level: "debug" spec: containers: - name: app image: ghcr.io/mchmarny/hardened-app2:v0.1.4 ports: - containerPort: 8082 env: - name: PUBSUB_NAME value: "pubsub" - name: TOPIC_NAME value: "messages" - name: STORE_NAME value: "state" ================================================ FILE: hardened/deployment/simple/app3.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: app3 namespace: hardened labels: app: app3 demo: hardened spec: replicas: 1 selector: matchLabels: app: app3 template: metadata: labels: app: app3 demo: hardened annotations: dapr.io/enabled: "true" dapr.io/app-id: "app3" dapr.io/app-protocol: "http" dapr.io/app-port: "8083" dapr.io/log-as-json: "true" dapr.io/log-level: "debug" spec: containers: - name: app image: ghcr.io/mchmarny/hardened-app3:v0.1.4 ports: - containerPort: 8083 env: - name: PUBSUB_NAME value: "pubsub" - name: TOPIC_NAME value: "messages" ================================================ FILE: hardened/deployment/simple/pubsub.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: pubsub namespace: hardened spec: type: pubsub.redis metadata: - name: redisHost value: redis-master.redis.svc.cluster.local:6379 - name: redisPassword secretKeyRef: name: redis-secret key: password ================================================ FILE: hardened/deployment/simple/state.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: state namespace: hardened spec: type: state.redis metadata: - name: redisHost value: redis-master.redis.svc.cluster.local:6379 - name: redisPassword secretKeyRef: name: redis-secret key: password scopes: - app2 ================================================ FILE: hardened/src/app1/Dockerfile ================================================ FROM golang:1.15.3 as builder WORKDIR /src/ COPY . /src/ ENV GO111MODULE=on RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -a -tags netgo -mod vendor -o ./app . FROM gcr.io/distroless/static:nonroot COPY --from=builder /src/app . ENTRYPOINT ["./app"] ================================================ FILE: hardened/src/app1/Makefile ================================================ IMAGE_NAME ?=hardened-app1 IMAGE_TAG ?=v0.1.4 IMAGE_OWNER ?=$(shell git config --get user.username) APP_ID ?=app1 APP_PORT ?=8081 .PHONY: all all: help .PHONY: tidy tidy: ## Updates the go modules and vendors all dependencies go mod tidy go mod vendor .PHONY: test test: tidy ## Tests the entire project go test -count=1 -race ./... .PHONY: run run: tidy ## Runs uncompiled code in Dapr dapr run \ --app-id $(APP_ID) \ --app-port $(APP_PORT) \ --app-protocol http \ --dapr-http-port 3500 \ --components-path ../../config \ --log-level debug \ go run main.go .PHONY: image image: tidy ## Builds and publish image docker build -t "ghcr.io/$(IMAGE_OWNER)/$(IMAGE_NAME):$(IMAGE_TAG)" . docker push "ghcr.io/$(IMAGE_OWNER)/$(IMAGE_NAME):$(IMAGE_TAG)" .PHONY: lint lint: ## Lints the entire project golangci-lint run --timeout=3m .PHONY: clean clean: ## Cleans up generated files go clean rm -fr ./bin rm -fr ./vendor .PHONY: help help: ## Display available commands @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \ 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ================================================ FILE: hardened/src/app1/go.mod ================================================ module github.com/mchmarny/dapr-demos/hardened/src/app1 go 1.15 require ( github.com/dapr/go-sdk v0.11.1 github.com/pkg/errors v0.9.1 ) ================================================ FILE: hardened/src/app1/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dapr/go-sdk v0.11.1 h1:83lLYLrOKK1dAI1XhuamY9SkQoo2t5Fd8zlNlDMtwWA= github.com/dapr/go-sdk v0.11.1/go.mod h1:l9aJ4Zqsfzi9adwheYgFND6ZxhPDbb34IF/NecuPYJo= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201022231255-08b38378de70 h1:Z6x4N9mAi4oF0TbHweCsH618MO6OI6UFgV0FP5n0wBY= golang.org/x/net v0.0.0-20201022231255-08b38378de70/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd h1:WgqgiQvkiZWz7XLhphjt2GI2GcGCTIZs9jqXMWmH+oc= golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20201022181438-0ff5f38871d5 h1:YejJbGvoWsTXHab4OKNrzk27Dr7s4lPLnewbHue1+gM= google.golang.org/genproto v0.0.0-20201022181438-0ff5f38871d5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.1 h1:DGeFlSan2f+WEtCERJ4J9GJWk15TxUi8QGagfI87Xyc= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: hardened/src/app1/main.go ================================================ package main import ( "context" "fmt" "log" "os" "strings" "time" dapr "github.com/dapr/go-sdk/client" "github.com/dapr/go-sdk/service/common" daprd "github.com/dapr/go-sdk/service/http" "github.com/pkg/errors" ) const ( methodName = "ping" invocationError = "error invoking" ) var ( logger = log.New(os.Stdout, "", 0) serviceAddress = getEnvVar("ADDRESS", ":8081") targetServiceName = getEnvVar("TARGET_SERVICE_NAME", "app2") targetMethodName = getEnvVar("TARGET_METHOD_NAME", "counter") client dapr.Client ) func main() { s := daprd.NewService(serviceAddress) var clientErr error if client, clientErr = dapr.NewClient(); clientErr != nil { logger.Fatalf("error creating Dapr client: %v", clientErr) } defer client.Close() if err := s.AddServiceInvocationHandler(methodName, handler); err != nil { logger.Fatalf("error adding call invocation handler: %v", err) } logger.Printf("starting server at %s...", serviceAddress) if err := s.Start(); err != nil { logger.Fatalf("error starting server: %v", err) } } func handler(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) { logger.Printf("Method %s invoked (ContentType:%s, Verb:%s, QueryString:%s, Data:%s)", methodName, in.ContentType, in.Verb, in.QueryString, in.Data) m := &dapr.DataContent{ContentType: in.ContentType, Data: in.Data} data, err := client.InvokeServiceWithContent(ctx, targetServiceName, targetMethodName, m) if err != nil { logger.Printf("%s %s/%s: %v", invocationError, targetServiceName, targetMethodName, err) return nil, errors.Wrapf(err, "%s %s/%s", invocationError, targetServiceName, targetMethodName) } logger.Printf("Response from invocation: %s", data) j := []byte(fmt.Sprintf(`{"on": %d, "count": %s}`, time.Now().UTC().UnixNano(), string(data))) out = &common.Content{ContentType: in.ContentType, Data: j} return } func getEnvVar(key, fallbackValue string) string { if val, ok := os.LookupEnv(key); ok { return strings.TrimSpace(val) } return fallbackValue } ================================================ FILE: hardened/src/app2/Dockerfile ================================================ FROM golang:1.15.3 as builder WORKDIR /src/ COPY . /src/ ENV GO111MODULE=on RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -a -tags netgo -mod vendor -o ./app . FROM gcr.io/distroless/static:nonroot COPY --from=builder /src/app . ENTRYPOINT ["./app"] ================================================ FILE: hardened/src/app2/Makefile ================================================ IMAGE_NAME ?=hardened-app2 IMAGE_TAG ?=v0.1.4 IMAGE_OWNER ?=$(shell git config --get user.username) APP_ID ?=app2 APP_PORT ?=8082 all: help .PHONY: tidy tidy: ## Updates the go modules and vendors all dependencies go mod tidy go mod vendor .PHONY: test test: tidy ## Tests the entire project go test -count=1 -race ./... .PHONY: run run: tidy ## Runs uncompiled code in Dapr dapr run \ --app-id $(APP_ID) \ --app-port $(APP_PORT) \ --app-protocol http \ --components-path ../../config \ --log-level debug \ go run main.go .PHONY: image image: tidy ## Builds and publishes image docker build -t "ghcr.io/$(IMAGE_OWNER)/$(IMAGE_NAME):$(IMAGE_TAG)" . docker push "ghcr.io/$(IMAGE_OWNER)/$(IMAGE_NAME):$(IMAGE_TAG)" .PHONY: lint lint: ## Lints the entire project golangci-lint run --timeout=3m .PHONY: clean clean: ## Cleans up generated files go clean rm -fr ./bin rm -fr ./vendor .PHONY: help help: ## Display available commands @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \ 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ================================================ FILE: hardened/src/app2/go.mod ================================================ module github.com/mchmarny/dapr-demos/hardened/src/app2 go 1.15 require ( github.com/dapr/go-sdk v0.11.1 github.com/pkg/errors v0.9.1 ) ================================================ FILE: hardened/src/app2/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dapr/go-sdk v0.11.1 h1:83lLYLrOKK1dAI1XhuamY9SkQoo2t5Fd8zlNlDMtwWA= github.com/dapr/go-sdk v0.11.1/go.mod h1:l9aJ4Zqsfzi9adwheYgFND6ZxhPDbb34IF/NecuPYJo= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201022231255-08b38378de70 h1:Z6x4N9mAi4oF0TbHweCsH618MO6OI6UFgV0FP5n0wBY= golang.org/x/net v0.0.0-20201022231255-08b38378de70/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd h1:WgqgiQvkiZWz7XLhphjt2GI2GcGCTIZs9jqXMWmH+oc= golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20201022181438-0ff5f38871d5 h1:YejJbGvoWsTXHab4OKNrzk27Dr7s4lPLnewbHue1+gM= google.golang.org/genproto v0.0.0-20201022181438-0ff5f38871d5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.1 h1:DGeFlSan2f+WEtCERJ4J9GJWk15TxUi8QGagfI87Xyc= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: hardened/src/app2/main.go ================================================ package main import ( "context" "fmt" "log" "strconv" "net/http" "os" "strings" dapr "github.com/dapr/go-sdk/client" "github.com/dapr/go-sdk/service/common" daprd "github.com/dapr/go-sdk/service/http" "github.com/pkg/errors" ) const ( methodName = "counter" stateCounterKey = "count" addInvokeHandlerError = "error adding invocation handler" startingServiceError = "error starting service" clientCreateError = "error creating Dapr client" publisingError = "error publishing" getStateError = "error getting state" saveStateError = "error saving state" intParsingError = "error parsing data" ) var ( logger = log.New(os.Stdout, "", 0) address = getEnvVar("ADDRESS", ":8082") pubSubName = getEnvVar("PUBSUB_NAME", "pubsub") topicName = getEnvVar("TOPIC_NAME", "messages") storeName = getEnvVar("STORE_NAME", "state") client dapr.Client ) func main() { s := daprd.NewService(address) var clientErr error if client, clientErr = dapr.NewClient(); clientErr != nil { logger.Fatalf("%s: %v", clientCreateError, clientErr) } defer client.Close() if err := s.AddServiceInvocationHandler(methodName, handler); err != nil { logger.Fatalf("%s: %v", addInvokeHandlerError, err) } if err := s.Start(); err != nil && err != http.ErrServerClosed { logger.Fatalf("%s: %v", startingServiceError, err) } } func handler(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) { logger.Printf("Method %s invoked (ContentType:%s, Verb:%s, QueryString:%s, Data:%s)", methodName, in.ContentType, in.Verb, in.QueryString, string(in.Data)) // get previous number data, err := client.GetState(ctx, storeName, stateCounterKey) if err != nil { logger.Printf("%s: %v", getStateError, err) return nil, errors.Wrap(err, getStateError) } // convert the content to number var count int64 = 0 var cnvErr error if data != nil && data.Value != nil { count, cnvErr = strconv.ParseInt(fmt.Sprintf("%s", data.Value), 10, 64) if cnvErr != nil { logger.Printf("%s %v: %v", intParsingError, data.Value, cnvErr) return nil, errors.Wrap(cnvErr, intParsingError) } } // increment count = count + 1 logger.Printf("New count: %d", count) // data from counter b := []byte(fmt.Sprintf("%d", count)) // save new number item := &dapr.SetStateItem{ Etag: data.Etag, // using the retreaved etag to ensure count consistency Key: stateCounterKey, Value: b, Options: &dapr.StateOptions{ Concurrency: dapr.StateConcurrencyLastWrite, Consistency: dapr.StateConsistencyStrong, }, } if err := client.SaveStateItems(ctx, storeName, item); err != nil { logger.Printf("%s: %v", saveStateError, err) return nil, errors.Wrap(err, saveStateError) } // publish the results for processing down the stream if err := client.PublishEvent(ctx, pubSubName, topicName, b); err != nil { logger.Printf("%s to %s/%s: %v", publisingError, pubSubName, topicName, err) return nil, errors.Wrapf(err, "%s to %s/%s", publisingError, pubSubName, topicName) } out = &common.Content{ContentType: in.ContentType, Data: b} return } func getEnvVar(key, fallbackValue string) string { if val, ok := os.LookupEnv(key); ok { return strings.TrimSpace(val) } return fallbackValue } ================================================ FILE: hardened/src/app3/Dockerfile ================================================ FROM golang:1.15.3 as builder WORKDIR /src/ COPY . /src/ ENV GO111MODULE=on RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -a -tags netgo -mod vendor -o ./app . FROM gcr.io/distroless/static:nonroot COPY --from=builder /src/app . ENTRYPOINT ["./app"] ================================================ FILE: hardened/src/app3/Makefile ================================================ IMAGE_NAME ?=hardened-app3 IMAGE_TAG ?=v0.1.4 IMAGE_OWNER ?=$(shell git config --get user.username) APP_ID ?=app3 APP_PORT ?=8083 all: help .PHONY: tidy tidy: ## Updates the go modules and vendors all dependencies go mod tidy go mod vendor .PHONY: test test: tidy ## Tests the entire project go test -count=1 -race ./... .PHONY: run run: tidy ## Runs uncompiled code in Dapr dapr run \ --app-id $(APP_ID) \ --app-port $(APP_PORT) \ --app-protocol http \ --components-path ../../config \ --log-level debug \ go run main.go .PHONY: image image: tidy ## Builds and publishes image docker build -t "ghcr.io/$(IMAGE_OWNER)/$(IMAGE_NAME):$(IMAGE_TAG)" . docker push "ghcr.io/$(IMAGE_OWNER)/$(IMAGE_NAME):$(IMAGE_TAG)" .PHONY: lint lint: ## Lints the entire project golangci-lint run --timeout=3m .PHONY: clean clean: ## Cleans up generated files go clean rm -fr ./bin rm -fr ./vendor .PHONY: help help: ## Display available commands @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \ 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ================================================ FILE: hardened/src/app3/go.mod ================================================ module github.com/mchmarny/dapr-demos/hardened/src/app3 go 1.15 require ( github.com/dapr/go-sdk v0.11.1 github.com/pkg/errors v0.9.1 ) ================================================ FILE: hardened/src/app3/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dapr/go-sdk v0.11.1 h1:83lLYLrOKK1dAI1XhuamY9SkQoo2t5Fd8zlNlDMtwWA= github.com/dapr/go-sdk v0.11.1/go.mod h1:l9aJ4Zqsfzi9adwheYgFND6ZxhPDbb34IF/NecuPYJo= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201022231255-08b38378de70 h1:Z6x4N9mAi4oF0TbHweCsH618MO6OI6UFgV0FP5n0wBY= golang.org/x/net v0.0.0-20201022231255-08b38378de70/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd h1:WgqgiQvkiZWz7XLhphjt2GI2GcGCTIZs9jqXMWmH+oc= golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20201022181438-0ff5f38871d5 h1:YejJbGvoWsTXHab4OKNrzk27Dr7s4lPLnewbHue1+gM= google.golang.org/genproto v0.0.0-20201022181438-0ff5f38871d5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.1 h1:DGeFlSan2f+WEtCERJ4J9GJWk15TxUi8QGagfI87Xyc= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: hardened/src/app3/main.go ================================================ package main import ( "context" "fmt" "log" "strconv" "net/http" "os" "strings" "github.com/dapr/go-sdk/service/common" daprd "github.com/dapr/go-sdk/service/http" "github.com/pkg/errors" ) const ( eventDataParsingError = "error parsing event data" addSubscriptionError = "error adding topic subscription" startingServiceError = "error starting service" intParsingError = "error parsing data" ) var ( logger = log.New(os.Stdout, "", 0) address = getEnvVar("ADDRESS", ":8083") pubSubName = getEnvVar("PUBSUB_NAME", "pubsub") topicName = getEnvVar("TOPIC_NAME", "messages") ) func main() { s := daprd.NewService(address) subscription := &common.Subscription{ PubsubName: pubSubName, Topic: topicName, Route: fmt.Sprintf("/%s", topicName), } if err := s.AddTopicEventHandler(subscription, handler); err != nil { logger.Fatalf("%s: %v", addSubscriptionError, err) } if err := s.Start(); err != nil && err != http.ErrServerClosed { logger.Fatalf("%s: %v", startingServiceError, err) } } func handler(ctx context.Context, e *common.TopicEvent) (retry bool, err error) { logger.Printf("Event received (PubsubName:%s, Topic:%s, Data: %v", e.PubsubName, e.Topic, e.Data) var ( count int64 = 0 cnvErr error ) data := fmt.Sprintf("%v", e.Data) count, cnvErr = strconv.ParseInt(data, 10, 64) if cnvErr != nil { logger.Printf("%s %v: %v", intParsingError, data, cnvErr) return false, errors.Wrap(cnvErr, intParsingError) } logger.Printf("Even: %v", count%2 == 0) return false, nil } func getEnvVar(key, fallbackValue string) string { if val, ok := os.LookupEnv(key); ok { return strings.TrimSpace(val) } return fallbackValue } ================================================ FILE: http-echo-service/Dockerfile ================================================ FROM golang:1.15.0 as builder WORKDIR /src/ COPY . /src/ ENV GO111MODULE=on RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -a -tags netgo -mod vendor -o ./service . FROM gcr.io/distroless/static:nonroot COPY --from=builder /src/service . ENTRYPOINT ["./service"] ================================================ FILE: http-echo-service/Makefile ================================================ RELEASE_VERSION =v0.11.1 SERVICE_NAME ?=http-echo-service DOCKER_USERNAME ?=$(DOCKER_USER) .PHONY: all all: help .PHONY: tidy tidy: ## Updates the go modules and vendors all dependencies go mod tidy go mod vendor .PHONY: test test: tidy ## Tests the entire project go test -count=1 -race ./... .PHONY: run run: tidy ## Runs uncompiled code in Dapr dapr run \ --app-id $(SERVICE_NAME) \ --app-port 8080 \ --app-protocol http \ --dapr-http-port 3500 \ go run main.go .PHONY: invoke invoke: ## Invokes service through Dapr API curl -d '{ "message": "ping" }' \ -H "Content-type: application/json" \ "http://localhost:3500/v1.0/invoke/$(SERVICE_NAME)/method/echo" .PHONY: image image: tidy ## Builds and publish docker image docker build -t "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" . docker push "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" .PHONY: deploy deploy: ## Deploys prebuild image to k8s using currently selected context kubectl apply -f deployment.yaml kubectl rollout restart deployment/grpc-echo-service kubectl rollout status deployment/grpc-echo-service .PHONY: call call: ## Invokes service through Dapr API $(eval API_TOKEN=$(shell kubectl get secret dapr-api-token -o jsonpath="{.data.token}" | base64 --decode)) curl -d '{ "message": "ping" }' \ -H "Content-type: application/json" \ -H "dapr-api-token: $(API_TOKEN)" \ "https://api.cloudylabs.dev/v1.0/invoke/$(SERVICE_NAME)/method/echo" .PHONY: lint lint: ## Lints the entire project golangci-lint run --timeout=3m .PHONY: tag tag: ## Creates release tag git tag $(RELEASE_VERSION) git push origin $(RELEASE_VERSION) .PHONY: clean clean: ## Cleans up generated files go clean rm -fr ./bin rm -fr ./vendor .PHONY: help help: ## Display available commands @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \ 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ================================================ FILE: http-echo-service/README.md ================================================ # http-service For more information about service invocation see the [Dapr docs](https://github.com/dapr/docs/tree/master/concepts/service-invocation) ## Run To run this demo in Dapr, run: ```shell dapr run \ --app-id http-service-demo \ --app-port 8080 \ --app-protocol http \ --dapr-http-port 3500 \ --components-path ./config \ go run main.go ``` ## Deploy Deploy and wait for the pod to be ready ```shell kubectl apply -f deployment.yaml kubectl rollout status deployment/http-echo-service ``` If you have changed an existing component, make sure to reload the ingress and wait until the new version is ready ```shell kubectl rollout restart deployment/nginx-ingress-nginx-controller kubectl rollout status deployment/nginx-ingress-nginx-controller ``` Follow logs ```shell kubectl logs -l app=http-echo-service -c service -f ``` In a separate terminal session export API token ```shell export API_TOKEN=$(kubectl get secret dapr-api-token -o jsonpath="{.data.token}" | base64 --decode) ``` And invoke the service ```shell curl -d '{ "message": "ping" }' \ -H "Content-type: application/json" \ -H "dapr-api-token: ${API_TOKEN}" \ "https://api.cloudylabs.dev/v1.0/invoke/http-echo-service/method/echo" ``` The response should include the sent message ```json { "message": "ping" } ``` And the logs ```shell Invocation (ContentType:application/json, Verb:POST, QueryString:map[], Data:{ "message": "ping" }) ``` ## Disclaimer This 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. ## License This software is released under the [MIT](../LICENSE) ================================================ FILE: http-echo-service/deployment.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: http-echo-service labels: app: http-echo-service demo: echo spec: replicas: 1 selector: matchLabels: app: http-echo-service template: metadata: labels: app: http-echo-service demo: echo annotations: dapr.io/enabled: "true" dapr.io/app-id: "http-echo-service" dapr.io/app-protocol: "http" dapr.io/app-port: "8080" dapr.io/config: "tracing" dapr.io/log-as-json: "true" dapr.io/log-level: "debug" spec: containers: - name: service image: mchmarny/http-echo-service:v0.11.1 imagePullPolicy: Always ports: - containerPort: 8080 env: - name: ADDRESS value: ":8080" ================================================ FILE: http-echo-service/go.mod ================================================ module github.com/mchmarny/dapr-demos/http-echo-service go 1.15 require github.com/dapr/go-sdk v0.11.0 ================================================ FILE: http-echo-service/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo= github.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: http-echo-service/main.go ================================================ package main import ( "context" "log" "os" "strings" "github.com/dapr/go-sdk/service/common" daprd "github.com/dapr/go-sdk/service/http" ) var ( logger = log.New(os.Stdout, "", 0) serviceAddress = getEnvVar("ADDRESS", ":8080") ) func main() { // create serving server s := daprd.NewService(serviceAddress) // add handler to the service s.AddServiceInvocationHandler("echo", echoHandler) // start the server to handle incoming events log.Printf("starting server at %s...", serviceAddress) if err := s.Start(); err != nil { log.Fatalf("server error: %v", err) } } func echoHandler(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) { logger.Printf( "Invocation (ContentType:%s, Verb:%s, QueryString:%s, Data:%s)", in.ContentType, in.Verb, in.QueryString, string(in.Data), ) // TODO: implement handling logic here out = &common.Content{ ContentType: in.ContentType, Data: in.Data, } return } func getEnvVar(key, fallbackValue string) string { if val, ok := os.LookupEnv(key); ok { return strings.TrimSpace(val) } return fallbackValue } ================================================ FILE: http-event-subscriber/Dockerfile ================================================ FROM golang:1.15.0 as builder WORKDIR /src/ COPY . /src/ ENV GO111MODULE=on RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -a -tags netgo -mod vendor -o ./service . FROM gcr.io/distroless/static:nonroot COPY --from=builder /src/service . ENTRYPOINT ["./service"] ================================================ FILE: http-event-subscriber/Makefile ================================================ RELEASE_VERSION =v0.11.1 SERVICE_NAME ?=http-event-subscriber DOCKER_USERNAME ?=$(DOCKER_USER) .PHONY: tidy test debug build run jsonevent xmlevent binevent image lint clean tag all: help tidy: ## Updates the go modules and vendors all dependencies go mod tidy go mod vendor test: tidy ## Tests the entire project go test -count=1 -race ./... debug: tidy ## Runs uncompiled code in Dapr dapr run \ --app-id $(SERVICE_NAME) \ --app-port 8080 \ --app-protocol http \ --dapr-http-port 3500 \ --components-path ./config \ --log-level debug \ go run main.go build: tidy ## Builds local release binary CGO_ENABLED=0 go build -a -tags netgo -mod vendor -o bin/$(SERVICE_NAME) . run: build ## Builds binary and runs it in Dapr dapr run \ --app-id $(SERVICE_NAME) \ --app-port 8080 \ --app-protocol http \ --dapr-http-port 3500 \ --components-path ./config \ --log-level debug \ bin/$(SERVICE_NAME) ce: ## Publishes CloudEvent message to Dapr pubsub API curl -d@clooudevent.json \ -H "Content-type: application/json" \ "http://localhost:3500/v1.0/publish/http-events/messages" jsonevent: ## Publishes sample JSON message to Dapr pubsub API curl -d '{ "from": "John", "to": "Lary", "message": "hi" }' \ -H "Content-type: application/json" \ "http://localhost:3500/v1.0/publish/http-events/messages" xmlevent: ## Publishes sample XML message to Dapr pubsub API curl -d 'JohnLary' \ -H "Content-type: application/xml" \ "http://localhost:3500/v1.0/publish/http-events/messages" binevent: ## Publishes sample binary message to Dapr pubsub API curl -d '0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40' \ -H "Content-type: application/octet-stream" \ "http://localhost:3500/v1.0/publish/http-events/messages" image: tidy ## Builds and publishes docker image docker build -t "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" . docker push "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" deploy: ## Deploys prebuild image to k8s using currently selected context kubectl apply -f k8s/component.yaml kubectl apply -f k8s/deployment.yaml kubectl rollout restart deployment/http-event-subscriber kubectl rollout restart deployment/nginx-ingress-nginx-controller kubectl rollout status deployment/nginx-ingress-nginx-controller event: ## Publishes sample JSON message to Dapr pubsub API $(eval API_TOKEN=$(shell kubectl get secret dapr-api-token -o jsonpath="{.data.token}" | base64 --decode)) curl -d '{ "from": "John", "to": "Lary", "message": "hi" }' \ -H "Content-type: application/json" \ -H "dapr-api-token: $(API_TOKEN)" \ "https://api.cloudylabs.dev/v1.0/publish/http-events/messages" lint: ## Lints the entire project golangci-lint run --timeout=3m tag: ## Creates release tag git tag $(RELEASE_VERSION) git push origin $(RELEASE_VERSION) clean: ## Cleans up generated files go clean rm -fr ./bin rm -fr ./vendor help: ## Display available commands @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \ 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ================================================ FILE: http-event-subscriber/README.md ================================================ # dapr-event-subscriber-template ## Components ```yaml apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: events spec: type: pubsub.redis metadata: - name: redisHost value: localhost:6379 - name: redisPassword value: "" ``` For more information about pub/sub see the [Dapr docs](https://github.com/dapr/docs/tree/master/concepts/publish-subscribe-messaging) ## Run To run this demo in Dapr, run: ```shell dapr run \ --app-id grpc-event-subscriber-demo \ --app-port 50001 \ --app-protocol http \ --dapr-http-port 3500 \ --components-path ./config \ go run main.go ``` ## Deploy Deploy and wait for the pod to be ready ```shell kubectl apply -f k8s/component.yaml kubectl apply -f k8s/deployment.yaml kubectl rollout status deployment/nginx-ingress-nginx-controller ``` If you have changed an existing component, make sure to reload the deployment and wait until the new version is ready ```shell kubectl rollout restart deployment/nginx-ingress-nginx-controller kubectl rollout status deployment/nginx-ingress-nginx-controller ``` Follow logs ```shell kubectl logs -l app=http-event-subscriber -c service -f ``` In a separate terminal session export API token ```shell export API_TOKEN=$(kubectl get secret dapr-api-token -o jsonpath="{.data.token}" | base64 --decode) ``` And invoke the service ```shell curl -d '{ "from": "John", "to": "Lary", "message": "hi" }' \ -H "Content-type: application/json" \ -H "dapr-api-token: ${API_TOKEN}" \ "https://api.cloudylabs.dev/v1.0/publish/http-events/messages" ``` In the logs, you should see now an entry similar to this. Feel free to edit the message and try again. ```shell event - PubsubName:http-events, Topic:messages, ID:6b6cc665-684d-456c-8880-56e20cdf0519, Data: map[from:John message:hi to:Lary] ``` ## Disclaimer This 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. ## License This software is released under the [MIT](../LICENSE) ================================================ FILE: http-event-subscriber/clooudevent.json ================================================ { "specversion" : "1.0", "type" : "some.other.operation", "source" : "https://domain.com/cloudevents/operation", "subject" : "test operation", "id" : "id-1234-5678-9011", "time" : "2020-09-23T06:23:21Z", "comexampleextension1" : "value", "comexampleothervalue" : 5, "datacontenttype" : "text/xml", "data" : "User1user2hi" } ================================================ FILE: http-event-subscriber/config/pubsub.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: http-events spec: type: pubsub.redis metadata: - name: redisHost value: localhost:6379 - name: redisPassword value: "" ================================================ FILE: http-event-subscriber/go.mod ================================================ module github.com/mchmarny/dapr-demos/http-event-subscriber go 1.15 require github.com/dapr/go-sdk v0.11.0 ================================================ FILE: http-event-subscriber/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo= github.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: http-event-subscriber/k8s/component.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: http-events spec: type: pubsub.redis metadata: - name: redisHost value: redis-master.redis.svc.cluster.local:6379 - name: redisPassword secretKeyRef: name: redis-secret key: password - name: allowedTopics value: "messages" scopes: - nginx-ingress - http-event-subscriber ================================================ FILE: http-event-subscriber/k8s/deployment.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: http-event-subscriber labels: app: http-event-subscriber demo: http-event spec: selector: matchLabels: app: http-event-subscriber template: metadata: labels: app: http-event-subscriber demo: http-event annotations: dapr.io/enabled: "true" dapr.io/app-id: "http-event-subscriber" dapr.io/app-port: "8080" dapr.io/config: "tracing" dapr.io/log-as-json: "true" dapr.io/log-level: "debug" spec: containers: - name: service image: mchmarny/http-event-subscriber:v0.11.1 ports: - containerPort: 8080 env: - name: PORT value: "8080" - name: DAPR_HTTP_PORT value: "3500" - name: PUBSUB_NAME value: "http-events" - name: TOPIC_NAME value: "messages" ================================================ FILE: http-event-subscriber/main.go ================================================ package main import ( "context" "fmt" "log" "net/http" "os" "strings" "github.com/dapr/go-sdk/service/common" daprd "github.com/dapr/go-sdk/service/http" ) var ( logger = log.New(os.Stdout, "", 0) address = getEnvVar("ADDRESS", ":8080") pubSubName = getEnvVar("PUBSUB_NAME", "http-events") topicName = getEnvVar("TOPIC_NAME", "messages") ) func main() { // create a Dapr service s := daprd.NewService(address) // add some topic subscriptions subscription := &common.Subscription{ PubsubName: pubSubName, Topic: topicName, Route: fmt.Sprintf("/%s", topicName), } if err := s.AddTopicEventHandler(subscription, eventHandler); err != nil { logger.Fatalf("error adding topic subscription: %v", err) } // start the service if err := s.Start(); err != nil && err != http.ErrServerClosed { logger.Fatalf("error starting service: %v", err) } } func eventHandler(ctx context.Context, e *common.TopicEvent) (retry bool, err error) { logger.Printf( "event - PubsubName:%s, Topic:%s, ID:%s, Data: %v", e.PubsubName, e.Topic, e.ID, e.Data, ) // TODO: do something with the cloud event data return false, nil } func getEnvVar(key, fallbackValue string) string { if val, ok := os.LookupEnv(key); ok { return strings.TrimSpace(val) } return fallbackValue } ================================================ FILE: order-cancellation/README.md ================================================ # Dapr integrations demo > WIP: This demo is being currently updated to Dapr v1.0.0.rc-1 ## Use-case Order 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: ![Use-case](img/usecase.png) ## Component Overview * **Dapr API** endpoint published with JWT token auth in **Daprized Ngnx** ingress * **Dapr Workflows** to orchestrate cancellation process using **Logic Apps** runtime * **Dapr Functions** extensions to create and persist audit state into **Mongo DB** * **Dapr Eventing** using **Redis Queue** for order message queue * **Daprized Web App** as order processing dashboard * **Dapr Binding** to send confirmation emails using **SendGrid** * **Dapr Distributed Tracing** to capture and forward traces to **Zipkin** ![Draft Demo Flow Diagram](img/diagram.png) ## Demo Dapr integration demo consists of: 1. Starting the order processing dashboard 2. Submitting cancellation request 3. Viewing processed request in the dashboard 4. Querying the state store for cancellation data 5. Showing order cancellation confirmation email 6. Review of the distributed traces for entire process > Note, instructions on how to setup a Kubernetes cluster for this demo are located [here](../setup/README.md) ### 1. Dashboard > 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. Navigate 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. ![Initial UI](img/ui1.png) ### 2. Submit Cancellation Submit the order [cancellation.json](demo/data/cancellation.json) file using `curl` ```shell API_TOKEN=$(kubectl get secret dapr-api-token -n nginx -o jsonpath="{.data.token}" | base64 --decode) curl -i \ -d @demo/data/cancellation.json \ -H "Content-type: application/json" \ -H "dapr-api-token: ${API_TOKEN}" \ "https://api.demo.dapr.team/v1.0/invoke/workflows.order/method/order-cancel" ``` ### 3. Dashboard (updated) View the dashboard again at https://order.demo.dapr.team to see the orders ### 4. Email Check configured email box for cancellation confirmation after the processed completed > Make sure to check junk mail! ### 5. Observability Forward local ports: ```shell kubectl port-forward svc/kibana-kibana 5601 -n dapr-monitoring & kubectl port-forward svc/grafana 8888:80 -n dapr-monitoring & kubectl port-forward svc/zipkin 9411 -n dapr-monitoring & ``` #### Traces Navigate to Zipkin: http://localhost:9411 #### Logs Navigate to Kibana: http://localhost:5601 #### Metrics Navigate to Grafana: http://localhost:8888 ## Setup For script through this demo see [setup](./demo). ================================================ FILE: order-cancellation/demo/Makefile ================================================ .PHONY: help, workflowport, fnport, postmeta, postmail, postcancel, postgateway all: help workflowport: ## Forwards Dapr Workflow locally kubectl port-forward deployment/dapr-workflows-host 3500:3500 fnport: ## Forwards Dapr Functions locally kubectl port-forward deployment/auditor 3500:3500 postmeta: ## Invokes cancel on the workflow curl -v -d @data/meta.json -H "Content-type: application/json" \ http://localhost:3500/v1.0/publish/cancellations postmail: ## Invokes email on the workflow curl -v -d @data/email.json -H "Content-type: application/json" \ http://localhost:3500/v1.0/bindings/email postcancel: ## Invokes cancel on the workflow curl -v -d @data/cancellation.json \ -H "Content-type: application/json" \ http://localhost:3500/v1.0/invoke/workflows/method/order-cancel postgateway: ## Invokes cancel on the gateway $(eval API_TOKEN=$(shell kubectl get secret dapr-api-token -o jsonpath="{.data.token}" | base64 --decode)) curl -v -d @data/cancellation.json \ -H "Content-type: application/json" \ -H "dapr-api-token: $(API_TOKEN)" \ "https://api.cloudylabs.dev/v1.0/publish/queue/processed" help: ## Display available commands @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \ 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ================================================ FILE: order-cancellation/demo/README.md ================================================ # Dapr integrations demo setup > Note, this setup assumes Kubernetes cluster created using the [demo cluster setup instructions](../../setup/) ## Components Start by create the `order` namespace: ```shell kubectl apply -f ./deployment/space.yaml ``` SandGrid secret > the queue and store secrets were already created during the cluster setup ```shell kubectl create secret generic email --from-literal=api-key="${SENDGRID_KEY}" -n order ``` Now deploy the queue, state and workflow stores, and email components ```shell kubectl apply -f ./component ``` ## Deployments ### Auditor Deploy Dapr auditor functions and wait for it to be ready ```shell kubectl apply -f deployment/auditor.yaml kubectl rollout status deployment/order-auditor -n order ``` Check logs for errors from both containers ```shell kubectl logs -l app=order-auditor -c daprd -n order --tail 300 kubectl logs -l app=order-auditor -c auditor -n order ``` ### Workflow Create the config map to hold the Dapr workflow definition ```shell kubectl create cm workflows --from-file config/order-cancel.json -n order ``` Create the Azure storage account ```shell az storage account create --name daprintdemo --sku Standard_LRS export AZSAKEY=$(az storage account keys list --account-name daprintdemo --query "[0].value" --output tsv) ``` Create secret to hold the workflow Azure storage account key > TODO: Remove Azure storage account dependency ```shell kubectl create secret generic dapr-workflows \ --from-literal=accountName=daprintdemo \ --from-literal=accountKey=$AZSAKEY \ -n order ``` Deploy Dapr Workflows host and wait for it to be ready ```shell kubectl apply -f deployment/workflow.yaml kubectl rollout status deployment/workflows-host ``` Check logs for errors from both containers ```shell kubectl logs -l app=workflows-host -c daprd -n order --tail 300 kubectl logs -l app=workflows-host -c host -n order ``` ### Viewer Deploy Dashboard ```shell kubectl apply -f deployment/viewer.yaml kubectl rollout status deployment/order-viewer ``` Create the TLS certs for this domain > `demo.dapr.team` is the domain I'm using for this demo ```shell kubectl create secret tls tls-secret \ -n order \ --key ../../setup/certs/demo.dapr.team/cert-pk.pem \ --cert ../../setup/certs/demo.dapr.team/cert-ca.pem ``` Deploy ingress for `order` ```shell kubectl apply -f deployment/ingress.yaml ``` Test it: https://order.demo.dapr.team ## Cleanup ```shell kubectl delete -f ./deployment kubectl delete -f ./component kubectl delete secret email -n order kubectl delete secret dapr-workflows -n order kubectl delete configmap workflows -n order az storage account delete --name daprintdemo ``` ================================================ FILE: order-cancellation/demo/component/audit-store.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: order-audit-store namespace: order spec: type: state.mongodb metadata: - name: host value: mongo-mongodb-headless.mongo.svc.cluster.local:27017 - name: username value: dapr - name: password secretKeyRef: name: mongo-secret key: password - name: databaseName value: dapr - name: collectionName value: audit scopes: - order-auditor ================================================ FILE: order-cancellation/demo/component/email.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: order-email namespace: order spec: type: bindings.twilio.sendgrid metadata: - name: emailFrom value: "demo@thingz.io" - name: apiKey secretKeyRef: name: email key: api-key scopes: - workflows ================================================ FILE: order-cancellation/demo/component/queue.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: order-queue namespace: order spec: type: pubsub.redis metadata: - name: redisHost value: redis-master.redis.svc.cluster.local:6379 - name: redisPassword secretKeyRef: name: redis-secret key: password scopes: - order-auditor - workflows - order-viewer ================================================ FILE: order-cancellation/demo/component/workflow-store.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: order-store namespace: order spec: type: state.mongodb metadata: - name: host value: mongo-mongodb-headless.mongo.svc.cluster.local:27017 - name: username value: dapr - name: password secretKeyRef: name: mongo-secret key: password - name: databaseName value: dapr - name: collectionName value: record scopes: - workflows ================================================ FILE: order-cancellation/demo/config/order-cancel.json ================================================ { "definition": { "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", "actions": { "PublishCancellationsTopic": { "type": "Http", "inputs": { "method": "POST", "uri": "http://localhost:3500/v1.0/publish/order-queue/cancellations", "body": { "id": "@triggerBody().id", "note": "@triggerBody().note", "submitted_on": "@triggerBody().submitted_on" }, "headers": { "Content-Type": "application/json" } }, "runAfter": {} }, "SaveState": { "type": "Http", "inputs": { "method": "POST", "uri": "http://localhost:3500/v1.0/state/order-store", "body": [ { "key": "@triggerBody().id", "value": "@triggerBody()" } ], "headers": { "Content-Type": "application/json" } }, "runAfter": { "PublishCancellationsTopic": [ "Succeeded" ] } }, "PublishProcessedTopic": { "type": "Http", "inputs": { "method": "POST", "uri": "http://localhost:3500/v1.0/publish/order-queue/processed", "body": "@triggerBody()", "headers": { "Content-Type": "application/json" } }, "runAfter": { "SaveState": [ "Succeeded" ] } }, "SendConfirmationEmail": { "type": "Http", "inputs": { "method": "POST", "uri": "http://localhost:3500/v1.0/bindings/order-email", "body": { "operation": "create", "metadata": { "emailTo": "mchmarny@microsoft.com", "subject": "Order cancellation confirmation" }, "data": "

Order cancellation confirmed

Your order has been canceled.

Thank you and we hope you come back.

" }, "headers": { "Content-Type": "application/json" } }, "runAfter": { "PublishProcessedTopic": [ "Succeeded" ] } }, "Response": { "inputs": { "body": { "id:": "@triggerBody().id", "submitted_on": "@triggerBody().submitted_on", "approved": true }, "statusCode": 200 }, "runAfter": { "PublishProcessedTopic": [ "Succeeded" ] }, "type": "Response" } }, "contentVersion": "1.0.0.0", "outputs": {}, "parameters": {}, "triggers": { "manual": { "inputs": { "schema": {} }, "kind": "Http", "type": "Request" } } } } ================================================ FILE: order-cancellation/demo/data/cancellation.json ================================================ { "id": "f727735e-b64e-11ea-b3de-0242ac130004", "note": "Customer made a mistake", "submitted_on": "2020-06-24T13:02:15Z", "refund": { "shipping": { "full_refund": true }, "refund_line_items": [ { "line_item_id": 466157049, "quantity": 1, "restock_type": "cancel", "location_id": 48752903 } ], "transactions": [ { "parent_id": 1072844675, "amount": "10.00", "kind": "refund", "gateway": "bogus" }, { "parent_id": 1072844676, "amount": "100.00", "kind": "refund", "gateway": "gift_card" } ] } } ================================================ FILE: order-cancellation/demo/data/email.json ================================================ { "operation": "create", "metadata": { "emailTo": "mchmarny@microsoft.com", "subject": "Order cancellation confirmation" }, "data": "

Order cancellation confirmed

Your order from 2020-06-24T13:02:15Z has been canceled.

Thank you and we hope you come back.

Order: b76b547e-b64e-11ea-b3de-0242ac130004

" } ================================================ FILE: order-cancellation/demo/data/meta.json ================================================ { "id": "b76b547e-b64e-11ea-b3de-0242ac130004", "note": "Customer made a mistake", "submitted_on": "2020-06-24T13:02:15Z" } ================================================ FILE: order-cancellation/demo/deployment/auditor.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: order-auditor namespace: order labels: app: order-auditor spec: replicas: 1 selector: matchLabels: app: order-auditor template: metadata: labels: app: order-auditor annotations: dapr.io/enabled: "true" dapr.io/app-id: "order-auditor" dapr.io/app-protocol: "http" dapr.io/app-port: "3001" dapr.io/config: "order-config" dapr.io/log-as-json: "true" spec: containers: - name: auditor image: mchmarny/auditor:v0.2.10 ports: - containerPort: 3001 env: - name: StateStore value: "order-audit-store" - name: StateKey value: "id" - name: PubSubName value: "order-queue" - name: TopicName value: "cancellations" ================================================ FILE: order-cancellation/demo/deployment/ingress.yaml ================================================ apiVersion: extensions/v1beta1 kind: Ingress metadata: name: order-ingress-rules namespace: order annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/rewrite-target: / spec: tls: - hosts: - order.demo.dapr.team secretName: tls-secret rules: - host: order.demo.dapr.team http: paths: - path: / backend: serviceName: order-viewer servicePort: 80 ================================================ FILE: order-cancellation/demo/deployment/space.yaml ================================================ apiVersion: v1 kind: Namespace metadata: name: order --- apiVersion: dapr.io/v1alpha1 kind: Configuration metadata: name: order-config namespace: order spec: tracing: samplingRate: "1" --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: secret-reader namespace: order rules: - apiGroups: [""] resources: ["secrets"] verbs: ["get"] --- kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: dapr-secret-reader namespace: order subjects: - kind: ServiceAccount name: default roleRef: kind: Role name: secret-reader apiGroup: rbac.authorization.k8s.io --- apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: zipkin namespace: order spec: type: exporters.zipkin metadata: - name: enabled value: "true" - name: exporterAddress value: "http://zipkin.dapr-monitoring.svc.cluster.local:9411/api/v2/spans" ================================================ FILE: order-cancellation/demo/deployment/viewer.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: order-viewer namespace: order labels: app: order-viewer spec: replicas: 1 selector: matchLabels: app: order-viewer template: metadata: labels: app: order-viewer annotations: dapr.io/enabled: "true" dapr.io/app-id: "order-viewer" dapr.io/app-protocol: "http" dapr.io/app-port: "8080" dapr.io/config: "order-config" dapr.io/log-as-json: "true" dapr.io/log-level: "debug" spec: containers: - name: viewer image: mchmarny/order-viewer:v0.11.1 ports: - containerPort: 8080 env: - name: ADDRESS value: ":8080" - name: PUBSUB_NAME value: order-queue - name: TOPIC_NAME value: processed --- apiVersion: v1 kind: Service metadata: name: order-viewer namespace: order spec: selector: app: order-viewer ports: - protocol: TCP port: 80 targetPort: 8080 ================================================ FILE: order-cancellation/demo/deployment/workflow.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: workflows-host namespace: order labels: app: workflows-host spec: replicas: 1 selector: matchLabels: app: workflows-host template: metadata: labels: app: workflows-host annotations: dapr.io/app-id: "workflows" dapr.io/enabled: "true" dapr.io/app-port: "50003" dapr.io/app-protocol: "grpc" dapr.io/config: "order-config" dapr.io/log-level: "debug" dapr.io/log-as-json: "true" spec: containers: - name: host image: daprio/workflows:0.2.2 env: - name: STORAGE_ACCOUNT_KEY valueFrom: secretKeyRef: name: dapr-workflows key: accountKey - name: STORAGE_ACCOUNT_NAME valueFrom: secretKeyRef: name: dapr-workflows key: accountName command: ["dotnet"] args: ["app/Dapr.Workflows.dll", "--workflows-path", "/workflows"] ports: - containerPort: 50003 imagePullPolicy: Always volumeMounts: - name: workflows mountPath: /workflows volumes: - name: workflows configMap: name: workflows ================================================ FILE: order-cancellation/src/fn/.gitignore ================================================ # Build generated files in src /objd/**/* /obj/**/* /bin/**/* /gen/**/* /.vs/**/* /.vscode/**/* ================================================ FILE: order-cancellation/src/fn/DaprAzFn.csproj ================================================  netcoreapp3.0 v3 DaprAzFn PreserveNewest PreserveNewest Never ================================================ FILE: order-cancellation/src/fn/Dockerfile ================================================ FROM mcr.microsoft.com/dotnet/core/sdk:3.0 AS installer-env COPY . /src/DaprAzFnSample RUN cd /src/DaprAzFnSample && \ mkdir -p /home/site/wwwroot && \ dotnet publish *.csproj --output /home/site/wwwroot # To enable ssh & remote debugging on app service change the base image to the one below # FROM mcr.microsoft.com/azure-functions/dotnet:3.0-appservice FROM mcr.microsoft.com/azure-functions/dotnet:3.0 ENV AzureWebJobsScriptRoot=/home/site/wwwroot \ AzureFunctionsJobHost__Logging__Console__IsEnabled=true COPY --from=installer-env ["/home/site/wwwroot", "/home/site/wwwroot"] ================================================ FILE: order-cancellation/src/fn/Makefile ================================================ APP_NAME =auditor RELEASE_VERSION =v0.2.10 DOCKER_USERNAME ?=$(DOCKER_USER) .PHONY: test mod run dapr lint image tag clean help all: test restore: ## Restores the project dotnet restore run: ## Runs function locally dapr run --app-id fn \ --app-port 3001 \ --port 3501 \ --components-path config \ env PubSubName=queue TopicName=cancellations StateStore=store func host start event: ## Publishes event onto the Dapr topic curl -d @./config/meta.json \ -H "Content-type: application/json" \ http://localhost:3501/v1.0/publish/queue/cancellations test: restore ## Tests the entire project dotnet test image: restore ## Builds docker image and publishes it to Dockerhub docker build -t $(DOCKER_USERNAME)/$(APP_NAME):$(RELEASE_VERSION) . docker push $(DOCKER_USERNAME)/$(APP_NAME):$(RELEASE_VERSION) help: ## Display available commands @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \ 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ================================================ FILE: order-cancellation/src/fn/ReceiveEvent.cs ================================================ // ------------------------------------------------------------ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. // ------------------------------------------------------------ namespace DaprAzFn.Sample { using System; using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using CloudNative.CloudEvents; using Microsoft.Azure.WebJobs; using Dapr.AzureFunctions.Extension; using Microsoft.Azure.WebJobs.Extensions.Http; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; public static class ReceiveTopicMessage { /// /// Subscribes to topic and saves it to state store /// [FunctionName("ReceiveTopicMessage")] public static async Task Run( [DaprTopicTrigger("%PubSubName%", Topic = "%TopicName%")] CloudEvent cloudEvent, [DaprState("%StateStore%")] IAsyncCollector state, ILogger log) { // Get data from CloudEvent log.LogInformation($"Received message: {cloudEvent.Data}."); var cancellationData = cloudEvent.Data as JToken; // TODO: Implement cancellation validaiton logic. // Parse state ID and Save var keyname = Environment.GetEnvironmentVariable("StateKey") ?? "id"; var key = cancellationData.Value(keyname); var stateRec = new DaprStateRecord(key, cancellationData); await state.AddAsync(stateRec); return new OkResult(); } } } ================================================ FILE: order-cancellation/src/fn/config/meta.json ================================================ { "id": "b76b547e-b64e-11ea-b3de-0242ac130004", "note": "Customer made a mistake", "submitted_on": "2020-06-24T13:02:15Z" } ================================================ FILE: order-cancellation/src/fn/config/pubsub.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: queue spec: type: pubsub.redis metadata: - name: redisHost value: localhost:6379 - name: redisPassword value: "" ================================================ FILE: order-cancellation/src/fn/config/statestore.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: store spec: type: state.redis metadata: - name: redisHost value: localhost:6379 - name: redisPassword value: "" ================================================ FILE: order-cancellation/src/fn/host.json ================================================ { "version": "2.0" } ================================================ FILE: order-cancellation/src/fn/local.settings.json ================================================ { "IsEncrypted": false, "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "dotnet", "StateStore": "statestore", "StateKey": "id", "TopicName": "cancellations" } } ================================================ FILE: order-cancellation/src/viewer/Dockerfile ================================================ FROM golang:1.15.0 as builder WORKDIR /src/ COPY . /src/ ARG RELEASE_VERSION=v0.0.1-default ENV RELEASE_VERSION=$RELEASE_VERSION ENV GO111MODULE=on RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -a -tags netgo -ldflags \ "-w -extldflags '-static' -X main.AppVersion=${RELEASE_VERSION}" \ -mod vendor -o ./service . FROM gcr.io/distroless/static:nonroot COPY --from=builder /src/service . COPY --from=builder /src/resource ./resource/ ENTRYPOINT ["./service"] ================================================ FILE: order-cancellation/src/viewer/Makefile ================================================ APP_NAME =order-viewer RELEASE_VERSION =v0.11.1 DOCKER_USERNAME ?=$(DOCKER_USER) .PHONY: test mod run dapr lint image tag clean help all: test tidy: ## Updates the go modules and vendors go mod tidy go mod vendor test: mod ## Tests the entire project go test -v -count=1 -race ./... # go test -v -count=1 -run NameOfSingleTest ./... run: mod ## Runs the uncompiled code by itsef go run -v main.go build: mod ## Build app binary locally CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -a -tags netgo -ldflags \ "-w -extldflags '-static' -X main.AppVersion=$(RELEASE_VERSION)" \ -mod vendor -o ./bin/service . dapr: mod ## Runs the uncompiled code in Dapr dapr run --app-id viewer \ --app-port 8083 \ --app-protocol http \ --dapr-http-port 3500 \ --components-path ./config \ go run main.go image: mod ## Builds docker image and publishes it to Dockerhub docker build --build-arg APP_VERSION=$(RELEASE_VERSION) \ -t $(DOCKER_USERNAME)/$(APP_NAME):$(RELEASE_VERSION) . docker push $(DOCKER_USERNAME)/$(APP_NAME):$(RELEASE_VERSION) lint: ## Lints the entire project golangci-lint run --timeout=3m tag: ## Creates release tag git tag $(RELEASE_VERSION) git push origin $(RELEASE_VERSION) clean: ## Cleans go and generated files in ./dapr/proto/ go clean rm -fr ./vendor help: ## Display available commands @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \ 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ================================================ FILE: order-cancellation/src/viewer/config/queue.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: queue spec: type: pubsub.azure.servicebus metadata: - name: connectionString value: Endpoint=sb://daprdemo.servicebus.windows.net/;SharedAccessKeyName=view;SharedAccessKey=RbTBiNnoj7wfxHqG8XeHWYn3sL4VFbdBpn0cTNi3Hyo=;EntityPath=processed ================================================ FILE: order-cancellation/src/viewer/go.mod ================================================ module github.com/mchmarny/dapr-demos/order-cancellation/src/viewer go 1.15 require ( github.com/dapr/go-sdk v0.11.0 github.com/gorilla/websocket v1.4.2 // indirect github.com/pkg/errors v0.9.1 gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376 ) ================================================ FILE: order-cancellation/src/viewer/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo= github.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376 h1:sY2a+y0j4iDrajJcorb+a0hJIQ6uakU5gybjfLWHlXo= gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376/go.mod h1:BHKOc1m5wm8WwQkMqYBoo4vNxhmF7xg8+xhG8L+Cy3M= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: order-cancellation/src/viewer/main.go ================================================ package main import ( "context" "encoding/json" "fmt" "html/template" "log" "net/http" "os" "strings" "gopkg.in/olahol/melody.v1" "github.com/dapr/go-sdk/service/common" daprd "github.com/dapr/go-sdk/service/http" "github.com/pkg/errors" ) var ( // AppVersion will be overritten during build AppVersion = "v0.0.1-default" // service logger = log.New(os.Stdout, "", 0) address = getEnvVar("ADDRESS", ":8083") pubSubName = getEnvVar("PUBSUB_NAME", "queue") topicName = getEnvVar("TOPIC_NAME", "processed") broadcaster *melody.Melody templates *template.Template ) func main() { // server mux mux := http.NewServeMux() // static content mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("resource/static")))) mux.HandleFunc("/favicon.ico", faviconHandler) // tempalates templates = template.Must(template.ParseGlob("resource/template/*")) // websocket upgrade broadcaster = melody.New() broadcaster.Upgrader.CheckOrigin = func(r *http.Request) bool { return true } // other handlers mux.HandleFunc("/", rootHandler) mux.HandleFunc("/ws", wsHandler) // create a Dapr service s := daprd.NewServiceWithMux(address, mux) // add some topic subscriptions subscription := &common.Subscription{ PubsubName: pubSubName, Topic: topicName, Route: fmt.Sprintf("/%s", topicName), } if err := s.AddTopicEventHandler(subscription, eventHandler); err != nil { logger.Fatalf("error adding topic subscription: %v", err) } // start the service if err := s.Start(); err != nil && err != http.ErrServerClosed { logger.Fatalf("error starting service: %v", err) } } func wsHandler(w http.ResponseWriter, r *http.Request) { broadcaster.HandleRequest(w, r) } func rootHandler(w http.ResponseWriter, r *http.Request) { proto := r.Header.Get("x-forwarded-proto") if proto == "" { proto = "http" } data := map[string]string{ "host": r.Host, "proto": proto, "version": AppVersion, } err := templates.ExecuteTemplate(w, "index", data) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } func faviconHandler(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "./resource/static/img/favicon.ico") } func eventHandler(ctx context.Context, e *common.TopicEvent) (retry bool, err error) { logger.Printf( "event - PubsubName:%s, Topic:%s, ID:%s, Data: %v", e.PubsubName, e.Topic, e.ID, e.Data, ) b, err := json.Marshal(e.Data) if err != nil { return false, errors.Wrap(err, "error marshaling data") } broadcaster.Broadcast(b) return false, nil } func getEnvVar(key, fallbackValue string) string { if val, ok := os.LookupEnv(key); ok { return strings.TrimSpace(val) } return fallbackValue } ================================================ FILE: order-cancellation/src/viewer/resource/static/css/app.css ================================================ html, body { height: 100%; margin: 0; padding: 0; background-color: #fff; font-family: Geneva, Verdana, sans-serif; } #wrapper { padding: 0; margin: 0; height: 100%; text-align: center; widows: 100%; } #page-header { padding: 0; margin: 10px 0 0 0; clear: both; break-after: always; height: 100px; } #page-header-image { border: 0; margin-left: 25px; } #page-header-image img { width: 100px; height: 100px; float: left; } #connection { float: right; margin: 10px; font-size: 1em; } #middle-section { margin: 0; padding: 10px; height: 700px; overflow: auto; } #middle-section div { margin: 5px; } #page-footer { widows: 80%; margin: 30px 0 0 0; font-size: 1em; } #items { background-color: #20329b; padding: 0; margin: 5px; height: 690px; overflow: auto; } .item { padding: 3px; } .meta-item { font-size: 0.9em; padding: 0; } .meta-item b { font-weight: bolder; } .meta-item i { font-style: normal; } .error { color: red; } .item { clear:both; padding: 5px; text-align: left; background-color: #fff; overflow: auto; } ================================================ FILE: order-cancellation/src/viewer/resource/static/js/app.js ================================================ window.onload = function () { console.log("Protocol: " + location.protocol); var wsURL = "ws://" + document.location.host + "/ws" if (location.protocol == 'https:') { wsURL = "wss://" + document.location.host + "/ws" } console.log("WS URL: " + wsURL); var log = document.getElementById("items"); function appendLog(item) { var doScroll = log.scrollTop > log.scrollHeight - log.clientHeight - 1; log.appendChild(item); if (doScroll) { log.scrollTop = log.scrollHeight - log.clientHeight; } } if (log) { sock = new WebSocket(wsURL); var temp = document.getElementsByTagName("template")[0]; var connDiv = document.getElementById("connection-status"); connDiv.innerText = "closed"; sock.onopen = function () { console.log("connected to " + wsURL); connDiv.innerText = "open"; }; sock.onclose = function (e) { console.log("connection closed (" + e.code + ")"); connDiv.innerText = "closed"; }; sock.onmessage = function (e) { console.log(e); var order = JSON.parse(e.data); console.log(order); var item = temp.content.cloneNode(true); var divs = item.querySelectorAll("div"); divs[1].querySelector("a").textContent = order.id; divs[2].querySelector("a").textContent = order.submitted_on; divs[3].textContent = order.note; appendLog(item); }; } // if log }; ================================================ FILE: order-cancellation/src/viewer/resource/template/footer.html ================================================ {{ define "footer" }} {{ end }} ================================================ FILE: order-cancellation/src/viewer/resource/template/header.html ================================================ {{ define "header" }} Dapr Event Viewer
{{end}} ================================================ FILE: order-cancellation/src/viewer/resource/template/index.html ================================================ {{ define "index" }} {{ template "header" . }}
{{ template "footer" . }} {{ end }} ================================================ FILE: pipeline/README.md ================================================ # Dapr pipeline demo Dapr 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. ![alt text](./img/overview.png "Pipeline Overview") ## Run in standalone mode ### Setup To 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). ```json { "Twitter": { "ConsumerKey": "", "ConsumerSecret": "", "AccessToken": "", "AccessSecret": "" }, "Azure": { "CognitiveAPIKey": "" } } ``` ### Start tweet viewer app Navigate to the [tweet-viewer](./tweet-viewer) directory and run: ```shell cd tweet-viewer dapr run \ --app-id tweet-viewer \ --app-port 8084 \ --app-protocol http \ --components-path ./config \ go run main.go ``` Once 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. ### Start sentiment scoring service Navigate to the [sentiment-scorer](./sentiment-scorer) directory and run: ```shell cd sentiment-scorer dapr run \ --app-id sentiment-scorer \ --app-port 60005 \ --app-protocol grpc \ --components-path ./config \ go run main.go ``` The last line from the above command should be ```shell ✅ You're up and running! Both Dapr and your app logs will appear here. ``` ### Start tweet processing service Navigate to the [tweet-processor](./tweet-processor) directory and run: ```shell cd tweet-processor dapr run \ --app-id tweet-processor \ --app-port 60002 \ --app-protocol grpc \ --components-path ./config \ go run main.go ``` The last line from the above command should be ```shell ✅ You're up and running! Both Dapr and your app logs will appear here. ``` ### Start tweet provider Navigate to the [tweet-provider](./tweet-provider) directory and run: ```shell cd tweet-provider dapr run \ --app-id tweet-provider \ --app-port 8080 \ --app-protocol http \ --components-path ./config \ go run main.go ``` The last line from the above command should be ```shell ✅ You're up and running! Both Dapr and your app logs will appear here. ``` ### View sentiment scored tweets in the UI Navigate once more to http://localhost:8084/ and provided there were tweets matching your query you should now see tweets displayed in the UI. ![](./img/ui.png) ## Run on Kubernetes > Note, these instructions assume cluster created using the [demo setup](../setup). ### namespace Start by setting up the namespace for the `pipeline`: ```shell kubectl apply -f k8s/space.yaml ``` ### tweet-processor Next, deploy `tweet-processor` and wait for it to be ready ```shell kubectl apply -f k8s/process-pubsub.yaml kubectl apply -f k8s/tweeter-pubsub.yaml kubectl apply -f k8s/processor.yaml kubectl rollout status deployment/tweet-processor -n pipeline ``` Check the Dapr logs to make sure the components were registered correctly ```shell kubectl logs -l app=tweet-processor -c daprd -n pipeline --tail 200 ``` ### sentiment-scorer Create a secret for Azure Cognitive Services ```shell kubectl create secret generic sentiment-secret \ -n pipeline \ --from-literal=token="your-azure-cognitive-service-token" ``` Deploy `sentiment-scorer` and wait for it to be ready ```shell kubectl apply -f k8s/scorer.yaml kubectl rollout status deployment/sentiment-scorer -n pipeline ``` Check the logs to make sure Dapr was started correctly ```shell kubectl logs -l app=sentiment-scorer -c daprd -n pipeline --tail 200 ``` To test the service, you can first export the API token ```shell export API_TOKEN=$(kubectl get secret dapr-api-token -o jsonpath="{.data.token}" -n nginx | base64 --decode) ``` And then invoke the service manually ```shell curl -i -d '{ "text": "dapr is the best" }' \ -H "Content-type: application/json" \ -H "dapr-api-token: ${API_TOKEN}" \ "https://api.demo.dapr.team/v1.0/invoke/sentiment-scorer.pipeline/method/sentiment" ``` Response should look something like this ```json { "sentiment":"positive", "confidence":1 } ``` ### tweet-viewer Create the TLS certs for this domain > `demo.dapr.team` is the domain I'm using for this demo ```shell kubectl create secret tls tls-secret \ -n pipeline \ --key ../setup/certs/demo.dapr.team/cert-pk.pem \ --cert ../setup/certs/demo.dapr.team/cert-ca.pem ``` Deploy `tweet-viewer` along with its component ```shell kubectl apply -f k8s/viewer.yaml kubectl apply -f k8s/ingress.yaml kubectl rollout status deployment/tweet-viewer -n pipeline ``` Check that the ingress was updated ```shell kubectl get ingress -n pipeline ``` Should include `viewer.` ```shell NAME HOSTS ADDRESS PORTS AGE ingress-rules tweets.demo.dapr.team x.x.x.x 80, 443 1m ``` Check in browser: https://tweets.demo.dapr.team ### tweet-provider Create secret for `tweet-provider` to connect to Twitter API > Check [twitter developer portal](https://developer.twitter.com/en/portal/dashboard) if you need this info ```shell kubectl create secret generic twitter-secret -n pipeline \ --from-literal=consumerKey="" \ --from-literal=consumerSecret="" \ --from-literal=accessToken="" \ --from-literal=accessSecret="" ``` Deploy the `tweet-provider` service and its components ```shell kubectl apply -f k8s/state.yaml kubectl apply -f k8s/twitter.yaml kubectl apply -f k8s/provider.yaml kubectl rollout status deployment/tweet-provider -n pipeline ``` Check Dapr to make sure components were registered correctly ```shell kubectl logs -l app=tweet-provider -c daprd -n pipeline --tail 200 ``` ## View Navigate back to: https://tweets.demo.dapr.team If everything went well, you should see some tweets appear. > 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 ## Restart deployments If you change the components you need to apply rolling upgrades to the deployments ```shell kubectl rollout restart deployment/sentiment-scorer -n pipeline kubectl rollout restart deployment/tweet-processor -n pipeline kubectl rollout restart deployment/tweet-provider -n pipeline kubectl rollout restart deployment/tweet-viewer -n pipeline kubectl rollout status deployment/sentiment-scorer -n pipeline kubectl rollout status deployment/tweet-processor -n pipeline kubectl rollout status deployment/tweet-provider -n pipeline kubectl rollout status deployment/tweet-viewer -n pipeline ``` ## Debug Start by reviewing that all the pods are running: ```shell kubectl get pods -n pipeline ``` The response should look something like this. Notice the `2/2` container readiness in each pod and the status `Running` ```shell NAME READY STATUS RESTARTS AGE sentiment-scorer-64b9b8fb48-czn5h 2/2 Running 0 10m tweet-processor-75cff98984-skmx8 2/2 Running 0 10m tweet-provider-b47b566b5-7vs9r 2/2 Running 0 10m tweet-viewer-5cf465d4d8-crfk2 2/2 Running 0 10m ``` Next check the `daprd` and `service` logs on each deployment to make sure all the components loaded correctly. The `tweet-provider` for example would be: ```shell kubectl logs -l app=tweet-provider -c daprd -n pipeline --tail 300 kubectl logs -l app=tweet-provider -c service -n pipeline --tail 300 ``` ## Disclaimer This 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. ## License This software is released under the [MIT](../LICENSE) ================================================ FILE: pipeline/k8s/ingress.yaml ================================================ apiVersion: extensions/v1beta1 kind: Ingress metadata: name: tweets-ingress-rules namespace: pipeline annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/rewrite-target: / spec: tls: - hosts: - tweets.demo.dapr.team secretName: tls-secret rules: - host: tweets.demo.dapr.team http: paths: - path: / backend: serviceName: tweet-viewer servicePort: 80 ================================================ FILE: pipeline/k8s/process-pubsub.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: processed-tweets-pubsub namespace: pipeline spec: type: pubsub.redis metadata: - name: redisHost value: redis-master.redis.svc.cluster.local:6379 - name: redisPassword secretKeyRef: name: redis-secret key: password - name: allowedTopics value: "processed-tweets" scopes: - tweet-processor - tweet-viewer ================================================ FILE: pipeline/k8s/processor.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: tweet-processor namespace: pipeline labels: app: tweet-processor demo: pipeline spec: selector: matchLabels: app: tweet-processor template: metadata: labels: app: tweet-processor demo: pipeline annotations: dapr.io/enabled: "true" dapr.io/app-id: "tweet-processor" dapr.io/app-protocol: "grpc" dapr.io/app-port: "60002" dapr.io/config: "space-config" dapr.io/log-as-json: "true" dapr.io/log-level: "debug" spec: containers: - name: service image: mchmarny/tweet-processor:v0.11.1 ports: - containerPort: 60002 env: - name: SOURCE_PUBSUB_NAME value: "tweeter-pubsub" - name: SOURCE_TOPIC_NAME value: "tweets" - name: RESULT_PUBSUB_NAME value: "processed-tweets-pubsub" - name: RESULT_TOPIC_NAME value: "processed-tweets" - name: SENTIMENT_SERVICE_NAME value: "sentiment-scorer" ================================================ FILE: pipeline/k8s/provider.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: tweet-provider namespace: pipeline labels: app: tweet-provider demo: pipeline spec: selector: matchLabels: app: tweet-provider template: metadata: labels: app: tweet-provider demo: pipeline annotations: dapr.io/enabled: "true" dapr.io/app-id: "tweet-provider" dapr.io/app-port: "8080" dapr.io/config: "space-config" dapr.io/log-as-json: "true" dapr.io/log-level: "debug" spec: containers: - name: service image: mchmarny/tweet-provider:v0.11.1 ports: - containerPort: 8080 env: - name: ADDRESS value: ":8080" - name: PUBSUB_NAME value: "tweeter-pubsub" - name: TOPIC_NAME value: "tweets" ================================================ FILE: pipeline/k8s/scorer.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: sentiment-scorer namespace: pipeline labels: app: sentiment-scorer demo: pipeline spec: selector: matchLabels: app: sentiment-scorer template: metadata: labels: app: sentiment-scorer demo: pipeline annotations: dapr.io/enabled: "true" dapr.io/app-id: "sentiment-scorer" dapr.io/app-protocol: "grpc" dapr.io/app-port: "60002" dapr.io/config: "space-config" dapr.io/log-as-json: "true" dapr.io/log-level: "debug" spec: containers: - name: service image: mchmarny/sentiment-scorer:v0.11.1 ports: - containerPort: 60002 env: - name: ADDRESS value: ":60002" - name: API_DOMAIN value: "tweet-sentiment" - name: API_TOKEN valueFrom: secretKeyRef: name: sentiment-secret key: token ================================================ FILE: pipeline/k8s/space.yaml ================================================ apiVersion: v1 kind: Namespace metadata: name: pipeline --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: secret-reader namespace: pipeline rules: - apiGroups: [""] resources: ["secrets"] verbs: ["get"] --- kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: dapr-secret-reader namespace: pipeline subjects: - kind: ServiceAccount name: default roleRef: kind: Role name: secret-reader apiGroup: rbac.authorization.k8s.io --- apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: zipkin namespace: pipeline spec: type: exporters.zipkin metadata: - name: enabled value: "true" - name: exporterAddress value: "http://zipkin.dapr-monitoring.svc.cluster.local:9411/api/v2/spans" --- apiVersion: dapr.io/v1alpha1 kind: Configuration metadata: name: space-config namespace: pipeline spec: tracing: samplingRate: "1" ================================================ FILE: pipeline/k8s/state.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: tweet-store namespace: pipeline spec: type: state.mongodb metadata: - name: host value: mongo-mongodb-headless.mongo.svc.cluster.local:27017 - name: username value: dapr - name: password secretKeyRef: name: mongo-secret key: password - name: databaseName value: dapr - name: collectionName value: audit scopes: - tweet-provider ================================================ FILE: pipeline/k8s/tweeter-pubsub.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: tweeter-pubsub namespace: pipeline spec: type: pubsub.redis metadata: - name: redisHost value: redis-master.redis.svc.cluster.local:6379 - name: redisPassword secretKeyRef: name: redis-secret key: password - name: allowedTopics value: "tweets" scopes: - tweet-provider - tweet-processor ================================================ FILE: pipeline/k8s/twitter.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: tweets namespace: pipeline spec: type: bindings.twitter metadata: - name: consumerKey secretKeyRef: name: twitter-secret key: consumerKey - name: consumerSecret secretKeyRef: name: twitter-secret key: consumerSecret - name: accessToken secretKeyRef: name: twitter-secret key: accessToken - name: accessSecret secretKeyRef: name: twitter-secret key: accessSecret - name: query value: "dapr" # use more common term for demo scopes: - tweet-provider ================================================ FILE: pipeline/k8s/viewer.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: tweet-viewer namespace: pipeline labels: app: tweet-viewer demo: pipeline spec: selector: matchLabels: app: tweet-viewer template: metadata: labels: app: tweet-viewer demo: pipeline annotations: dapr.io/enabled: "true" dapr.io/app-id: "tweet-viewer" dapr.io/app-protocol: "http" dapr.io/app-port: "8084" dapr.io/config: "space-config" dapr.io/log-as-json: "true" dapr.io/log-level: "debug" spec: containers: - name: service image: mchmarny/tweet-viewer:v0.11.1 ports: - containerPort: 8084 env: - name: ADDRESS value: ":8084" - name: PUBSUB_NAME value: "processed-tweets-pubsub" - name: TOPIC_NAME value: "processed-tweets" --- apiVersion: v1 kind: Service metadata: name: tweet-viewer namespace: pipeline spec: selector: app: tweet-viewer ports: - protocol: TCP port: 80 targetPort: 8084 ================================================ FILE: pipeline/sentiment-scorer/Dockerfile ================================================ FROM golang:1.15.0 as builder WORKDIR /src/ COPY . /src/ ENV GO111MODULE=on RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -a -tags netgo -mod vendor -o ./service . FROM gcr.io/distroless/static:nonroot COPY --from=builder /src/service . ENTRYPOINT ["./service"] ================================================ FILE: pipeline/sentiment-scorer/Makefile ================================================ RELEASE_VERSION =v0.11.1 SERVICE_NAME ?=sentiment-scorer DOCKER_USERNAME ?=$(DOCKER_USER) .PHONY: tidy debug invoke image deploy call lint clean tag all: help tidy: ## Updates the go modules and vendors all dependencies go mod tidy go mod vendor run: tidy ## Runs uncompiled code in Dapr dapr run \ --app-id $(SERVICE_NAME) \ --app-port 60005 \ --app-protocol grpc \ --dapr-http-port 3505 \ --components-path ./config \ --log-level debug \ go run main.go invoke: ## Invokes service through Dapr API curl -i -d '{ "text": "dapr is the best" }' \ -H "Content-type: application/json" \ "http://localhost:3505/v1.0/invoke/$(SERVICE_NAME)/method/sentiment" curl -i -d '{ "text": "spinach is the worst" }' \ -H "Content-type: application/json" \ "http://localhost:3505/v1.0/invoke/$(SERVICE_NAME)/method/sentiment" curl -i -d '{ "text": "happy alram make me sad" }' \ -H "Content-type: application/json" \ "http://localhost:3505/v1.0/invoke/$(SERVICE_NAME)/method/sentiment" image: tidy ## Builds and publish docker image docker build -t "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" . docker push "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" deploy: ## Deploys prebuild image to k8s using currently selected context kubectl apply -f deployment.yaml kubectl rollout restart deployment/sentiment-scorer kubectl rollout restart deployment/nginx-ingress-nginx-controller kubectl rollout status deployment/nginx-ingress-nginx-controller api-invoke: ## Invokes service through Dapr API $(eval API_TOKEN=$(shell kubectl get secret dapr-api-token -o jsonpath="{.data.token}" | base64 --decode)) curl -d '{ "text": "dapr is the best" }' \ -H "Content-type: application/json" \ -H "dapr-api-token: $(API_TOKEN)" \ "https://api.cloudylabs.dev/v1.0/invoke/$(SERVICE_NAME)/method/sentiment" lint: ## Lints the entire project golangci-lint run --timeout=3m tag: ## Creates release tag git tag $(RELEASE_VERSION) git push origin $(RELEASE_VERSION) clean: ## Cleans up generated files go clean rm -fr ./bin rm -fr ./vendor help: ## Display available commands @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \ 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ================================================ FILE: pipeline/sentiment-scorer/README.md ================================================ # grpc-service For more information about service invocation see the [Dapr docs](https://github.com/dapr/docs/tree/master/concepts/service-invocation) ## Run To run this demo in Dapr, run: ```shell API_TOKEN="your-azure-cognitive-service-token" dapr run \ --app-id sentiment-scorer \ --app-port 60001 \ --app-protocol grpc \ --dapr-http-port 3500 \ --components-path ./config \ go run main.go ``` ## Deploy Create a `sentiment-secret` ```shell kubectl create secret generic sentiment-secret --from-literal=token="your-azure-cognitive-service-token" ``` Deploy and wait for the pod to be ready ```shell kubectl apply -f deployment.yaml kubectl rollout restart deployment/sentiment-scorer kubectl rollout restart deployment/nginx-ingress-nginx-controller kubectl rollout status deployment/nginx-ingress-nginx-controller ``` Follow logs ```shell kubectl logs -l demo=sentiment -c service -f ``` In a separate terminal session export API token ```shell export API_TOKEN=$(kubectl get secret dapr-api-token -o jsonpath="{.data.token}" | base64 --decode) ``` And invoke the service ```shell curl -d '{ "text": "dapr is the best" }' \ -H "Content-type: application/json" \ -H "dapr-api-token: ${API_TOKEN}" \ "https://api.cloudylabs.dev/v1.0/invoke/sentiment-scorer/method/sentiment" ``` Response should look something like this ```json { "sentiment":"positive", "confidence":1 } ``` ## Disclaimer This 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. ## License This software is released under the [MIT](./LICENSE) ================================================ FILE: pipeline/sentiment-scorer/config/secret.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: pipeline-secrets spec: type: secretstores.local.file metadata: - name: secretsFile value: "/etc/dapr/secrets.json" ================================================ FILE: pipeline/sentiment-scorer/go.mod ================================================ module github.com/mchmarny/dapr-demos/pipeline/sentiment-scorer go 1.15 require ( github.com/dapr/go-sdk v0.11.0 github.com/pkg/errors v0.9.1 golang.org/x/net v0.0.0-20200930145003-4acb6c075d10 // indirect golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f // indirect google.golang.org/genproto v0.0.0-20201002142447-3860012362da // indirect ) ================================================ FILE: pipeline/sentiment-scorer/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo= github.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200930145003-4acb6c075d10 h1:YfxMZzv3PjGonQYNUaeU2+DhAdqOxerQ30JFB6WgAXo= golang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 h1:4X356008q5SA3YXu8PiRap39KFmy4Lf6sGlceJKZQsU= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0 h1:uslsjIdqvZYANxSBQjTI47vZfwMaTN3mLELkMnMIY/A= google.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201002142447-3860012362da h1:DTQYk4u7nICKkkVZsBv0/0po0ChISxAJ5CTAfUhO0PQ= google.golang.org/genproto v0.0.0-20201002142447-3860012362da/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: pipeline/sentiment-scorer/main.go ================================================ package main import ( "bytes" "context" "encoding/json" "fmt" "log" "net/http" "os" "strings" "time" dapr "github.com/dapr/go-sdk/client" "github.com/dapr/go-sdk/service/common" daprd "github.com/dapr/go-sdk/service/grpc" "github.com/pkg/errors" ) const ( languageDefault = "en" secretStoreName = "pipeline-secrets" secretStoreKey = "Azure:CognitiveAPIKey" ) var ( logger = log.New(os.Stdout, "", 0) serviceAddress = getEnvVar("ADDRESS", ":60005") apiToken = getEnvVar("API_TOKEN", "") apiDomain = getEnvVar("API_DOMAIN", "tweet-sentiment") apiURL = fmt.Sprintf("https://%s.cognitiveservices.azure.com/text/analytics/v3.0/sentiment", apiDomain) ) func main() { // create serving server s, err := daprd.NewService(serviceAddress) if err != nil { log.Fatalf("failed to start the server: %v", err) } // add handler to the service s.AddServiceInvocationHandler("sentiment", sentimentHandler) // start the server to handle incoming events log.Printf("starting server at %s...", serviceAddress) if err := s.Start(); err != nil { log.Fatalf("server error: %v", err) } } func sentimentHandler(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) { logger.Printf("Processing: %s", in.Data) var req map[string]string if err := json.Unmarshal(in.Data, &req); err != nil { return nil, errors.Wrapf(err, "error deserializing data: %s", in.Data) } score, err := getSentiment(ctx, req["language"], req["text"]) if err != nil { logger.Printf("error scoring sentiment: %v", err) return nil, errors.Wrapf(err, "error scoring sentiment: %s", in.Data) } b, err := json.Marshal(score) if err != nil { return nil, errors.Wrapf(err, "error serializing score: %v", score) } logger.Printf("Processed: %s", b) return &common.Content{ ContentType: "application/json", Data: b, }, nil } // SentimentScore represents sentiment result type SentimentScore struct { Sentiment string `json:"sentiment"` Confidence float64 `json:"confidence"` } func getSentiment(ctx context.Context, lang, text string) (out *SentimentScore, err error) { if text == "" { return nil, errors.New("text required") } if lang == "" { lang = languageDefault } if apiToken == "" { apiToken = getSecret(secretStoreName, secretStoreKey) } r := fmt.Sprintf(`{ "documents": [{ "language": "%s", "id": "1", "text": "%s" }] }`, lang, text) req, err := http.NewRequest(http.MethodPost, apiURL, bytes.NewBuffer([]byte(r))) if err != nil { return nil, errors.Wrapf(err, "error creating request from: %v", r) } req = req.WithContext(ctx) req.Header.Add("Content-Type", "application/json") req.Header.Add("Ocp-Apim-Subscription-Key", apiToken) client := http.Client{Timeout: time.Second * 5} res, err := client.Do(req) if err != nil { return nil, errors.Wrapf(err, "error posting to: %s", apiURL) } if res.StatusCode != http.StatusOK { return nil, fmt.Errorf("invalid API response status: %d", res.StatusCode) } defer res.Body.Close() // dump, _ := httputil.DumpResponse(res, true) // logger.Printf("response: %s", dump) var rez struct { Documents []struct { Sentiment string `json:"sentiment"` Scores struct { Positive float64 `json:"positive"` Neutral float64 `json:"neutral"` Negative float64 `json:"negative"` Mixed float64 `json:"mixed"` } `json:"confidenceScores"` } `json:"documents"` } if err := json.NewDecoder(res.Body).Decode(&rez); err != nil { return nil, errors.Wrap(err, "error decoding API response") } if len(rez.Documents) != 1 { return nil, errors.Wrapf(err, "invalid response, expected 1 document, got %d", len(rez.Documents)) } doc := rez.Documents[0] out = &SentimentScore{ Sentiment: doc.Sentiment, } switch out.Sentiment { case "positive": out.Confidence = rez.Documents[0].Scores.Positive case "negative": out.Confidence = rez.Documents[0].Scores.Negative case "neutral": out.Confidence = rez.Documents[0].Scores.Neutral case "mixed": out.Confidence = rez.Documents[0].Scores.Mixed default: return nil, fmt.Errorf("invalid sentiment: %s", out.Sentiment) } return } func getEnvVar(key, fallbackValue string) string { if val, ok := os.LookupEnv(key); ok { return strings.TrimSpace(val) } return fallbackValue } func getSecret(store, key string) string { // try to find it in Dapr secret store c, err := dapr.NewClient() if err != nil { logger.Fatal("unable to create Dapr client") } if m, err := c.GetSecret(context.Background(), store, key, map[string]string{}); err == nil { return m[key] } logger.Fatalf("no item found in Dapr secret store for %s", key) return "" } ================================================ FILE: pipeline/tweet-processor/Dockerfile ================================================ FROM golang:1.15.0 as builder WORKDIR /src/ COPY . /src/ ENV GO111MODULE=on RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -a -tags netgo -mod vendor -o ./service . FROM gcr.io/distroless/static:nonroot COPY --from=builder /src/service . ENTRYPOINT ["./service"] ================================================ FILE: pipeline/tweet-processor/Makefile ================================================ RELEASE_VERSION =v0.11.1 SERVICE_NAME ?=tweet-processor DOCKER_USERNAME ?=$(DOCKER_USER) .PHONY: tidy test debug build run jsonevent xmlevent binevent image lint clean tag all: help tidy: ## Updates the go modules and vendors all dependencies go mod tidy go mod vendor debug: tidy ## Runs uncompiled code in Dapr dapr run \ --app-id $(SERVICE_NAME) \ --app-port 60002 \ --app-protocol grpc \ --dapr-http-port 3500 \ --components-path ./config \ --log-level debug \ go run main.go image: tidy ## Builds and publish docker image docker build -t "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" . docker push "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" deploy: ## Deploys prebuild image to k8s using currently selected context kubectl apply -f k8s/source-pubsub.yaml kubectl apply -f k8s/result-pubsub.yaml kubectl apply -f k8s/deployment.yaml kubectl rollout restart deployment/tweet-processor kubectl rollout status deployment/tweet-processor event: ## Publishes sample JSON message to Dapr pubsub API $(eval API_TOKEN=$(shell kubectl get secret dapr-api-token -o jsonpath="{.data.token}" | base64 --decode)) curl -d '{ "from": "John", "to": "Lary", "message": "hi" }' \ -H "Content-type: application/json" \ -H "dapr-api-token: $(API_TOKEN)" \ "https://api.cloudylabs.dev/v1.0/publish/grpc-events/messages" lint: ## Lints the entire project golangci-lint run --timeout=3m tag: ## Creates release tag git tag $(RELEASE_VERSION) git push origin $(RELEASE_VERSION) clean: ## Cleans up generated files go clean rm -fr ./bin rm -fr ./vendor help: ## Display available commands @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \ 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ================================================ FILE: pipeline/tweet-processor/config/result-pubsub.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: processed-tweets-pubsub spec: type: pubsub.redis metadata: - name: redisHost value: localhost:6379 - name: redisPassword value: "" ================================================ FILE: pipeline/tweet-processor/config/source-pubsub.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: tweeter-pubsub spec: type: pubsub.redis metadata: - name: redisHost value: localhost:6379 - name: redisPassword value: "" ================================================ FILE: pipeline/tweet-processor/go.mod ================================================ module github.com/mchmarny/dapr-demos/pipeline/tweet-processor go 1.15 require ( github.com/dapr/go-sdk v0.11.0 github.com/pkg/errors v0.9.1 golang.org/x/net v0.0.0-20200930145003-4acb6c075d10 // indirect golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f // indirect google.golang.org/genproto v0.0.0-20201002142447-3860012362da // indirect ) ================================================ FILE: pipeline/tweet-processor/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo= github.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200930145003-4acb6c075d10 h1:YfxMZzv3PjGonQYNUaeU2+DhAdqOxerQ30JFB6WgAXo= golang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 h1:4X356008q5SA3YXu8PiRap39KFmy4Lf6sGlceJKZQsU= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0 h1:uslsjIdqvZYANxSBQjTI47vZfwMaTN3mLELkMnMIY/A= google.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201002142447-3860012362da h1:DTQYk4u7nICKkkVZsBv0/0po0ChISxAJ5CTAfUhO0PQ= google.golang.org/genproto v0.0.0-20201002142447-3860012362da/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: pipeline/tweet-processor/main.go ================================================ package main import ( "context" "encoding/json" "fmt" "log" "os" "strings" dapr "github.com/dapr/go-sdk/client" "github.com/dapr/go-sdk/service/common" daprd "github.com/dapr/go-sdk/service/grpc" "github.com/pkg/errors" ) var ( logger = log.New(os.Stdout, "", 0) serviceAddress = getEnvVar("ADDRESS", ":60002") srcPubSubName = getEnvVar("SOURCE_PUBSUB_NAME", "tweeter-pubsub") srcTopicName = getEnvVar("SOURCE_TOPIC_NAME", "tweets") resultPubSubName = getEnvVar("RESULT_PUBSUB_NAME", "processed-tweets-pubsub") resultTopicName = getEnvVar("RESULT_TOPIC_NAME", "processed-tweets") sentimentServiceName = getEnvVar("SENTIMENT_SERVICE_NAME", "sentiment-scorer") client dapr.Client ) func main() { // create Dapr service s, err := daprd.NewService(serviceAddress) if err != nil { log.Fatalf("failed to start the server: %v", err) } c, err := dapr.NewClient() if err != nil { log.Fatalf("error creating Dapr client: %v", err) } client = c defer client.Close() // add handler to the service subscription := &common.Subscription{ PubsubName: srcPubSubName, Topic: srcTopicName, } s.AddTopicEventHandler(subscription, tweetHandler) // start the server to handle incoming events if err := s.Start(); err != nil { log.Fatalf("server error: %v", err) } } func topicDataToSentimentRequest(b []byte) (s *SentimentRequest, err error) { var t TweetText if err := json.Unmarshal(b, &t); err != nil { return nil, errors.Wrapf(err, "error deserializing tweet into request: %s", b) } s = &SentimentRequest{ Text: t.Text, Language: t.Lang, } if t.Extended.Text != "" { s.Text = t.Extended.Text } return } func getSentimentScore(ctx context.Context, req *SentimentRequest) (score *SentimentScore, err error) { cb, err := json.Marshal(req) if err != nil { return nil, errors.Wrap(err, "unable to serialize sentiment request") } c := &dapr.DataContent{ContentType: "application/json", Data: cb} b, err := client.InvokeServiceWithContent(ctx, sentimentServiceName, "sentiment", c) if err != nil { return nil, errors.Wrap(err, "error invoking sentiment service") } var s SentimentScore if err := json.Unmarshal(b, &s); err != nil { return nil, errors.Wrap(err, "error deserializing sentiment") } return &s, nil } func tweetHandler(ctx context.Context, e *common.TopicEvent) (retry bool, err error) { logger.Printf("Processing pubsub:%s/topic:%s id:%s", e.PubsubName, e.Topic, e.ID) b, ok := e.Data.([]byte) if !ok { return false, fmt.Errorf("invalid data type, expected []bytes: %T", e.Data) } sentReq, err := topicDataToSentimentRequest(b) if err != nil { return false, errors.Wrap(err, "error getting tweet text") } sentScore, err := getSentimentScore(ctx, sentReq) if err != nil { return true, errors.Wrap(err, "error getting sentiment score") } var tweetMap map[string]interface{} if err := json.Unmarshal(b, &tweetMap); err != nil { return true, errors.Wrap(err, "error deserializing content into map") } tweetMap["sentiment"] = sentScore content, err := json.Marshal(tweetMap) if err != nil { return true, errors.Wrap(err, "unable to serialize tweet map content") } if err := client.PublishEvent(ctx, resultPubSubName, resultTopicName, content); err != nil { return true, errors.Wrapf(err, "error publishing to %s/%s", resultPubSubName, resultTopicName) } logger.Printf("Processed tweet:%s - %v", e.ID, sentScore) return false, nil } // TweetText represents only the text of tweet for sentiment type TweetText struct { Text string `json:"text"` Lang string `json:"lang"` Extended struct { Text string `json:"full_text"` } `json:"extended_tweet"` } // SentimentRequest represents the sentiment request type SentimentRequest struct { Text string `json:"text"` Language string `json:"language"` } // SentimentScore represents sentiment result type SentimentScore struct { Sentiment string `json:"sentiment"` Confidence float64 `json:"confidence"` } func getEnvVar(key, fallbackValue string) string { if val, ok := os.LookupEnv(key); ok { return strings.TrimSpace(val) } return fallbackValue } ================================================ FILE: pipeline/tweet-provider/Dockerfile ================================================ FROM golang:1.15.0 as builder WORKDIR /src/ COPY . /src/ ENV GO111MODULE=on RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -a -tags netgo -mod vendor -o ./service . FROM gcr.io/distroless/static:nonroot COPY --from=builder /src/service . ENTRYPOINT ["./service"] ================================================ FILE: pipeline/tweet-provider/Makefile ================================================ RELEASE_VERSION =v0.11.1 SERVICE_NAME ?=tweet-provider DOCKER_USERNAME ?=$(DOCKER_USER) .PHONY: help tidy build run image lint tag clean all: help tidy: ## Updates the go modules and vendors all dependencies go mod tidy go mod vendor run: ## Runs uncompiled code it in Dapr in debug mode dapr run \ --app-id $(SERVICE_NAME) \ --app-port 8080 \ --app-protocol http \ --components-path ./config \ go run main.go image: tidy ## Builds and publish docker image docker build -t "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" . docker push "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" deploy: ## Deploys prebuild image to k8s using currently selected context kubectl apply -f k8s/pubsub.yaml kubectl apply -f k8s/twitter.yaml kubectl apply -f k8s/deployment.yaml kubectl rollout restart deployment/tweet-provider kubectl rollout status deployment/tweet-provider lint: ## Lints the entire project golangci-lint run --timeout=3m tag: ## Creates release tag git tag $(RELEASE_VERSION) git push origin $(RELEASE_VERSION) clean: ## Cleans up generated files go clean rm -fr ./bin rm -fr ./vendor help: ## Display available commands @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \ 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ================================================ FILE: pipeline/tweet-provider/README.md ================================================ # Tweet Provider Demo ## Setup To 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). ```json { "Twitter": { "ConsumerKey": "", "ConsumerSecret": "", "AccessToken": "", "AccessSecret": "" } } ``` ## Run it ### Standalone Mode Navigate to the [tweet-provider](./tweet-provider) directory and run: ```shell cd tweet-provider dapr run \ --app-id tweet-provider \ --app-port 8080 \ --app-protocol http \ --components-path ./config \ go run main.go ``` The last line from the above command should be ```shell ✅ You're up and running! Both Dapr and your app logs will appear here. ``` Your tweets should appear in the logs now ### Kubernetes Create secret for `tweet-provider` to connect to Twitter API ```shell kubectl create secret generic twitter-secret \ --from-literal=consumerKey="" \ --from-literal=consumerSecret="" \ --from-literal=accessToken="" \ --from-literal=accessSecret="" ``` Deploy the `tweet-provider` service and its components ```shell kubectl apply -f tweet-provider/k8s/state.yaml kubectl apply -f tweet-provider/k8s/pubsub.yaml kubectl apply -f tweet-provider/k8s/twitter.yaml kubectl apply -f tweet-provider/k8s/deployment.yaml kubectl rollout status deployment/tweet-provider ``` If you have changed an existing component, make sure to reload the deployment and wait until the new version is ready ```shell kubectl rollout restart deployment/tweet-provider kubectl rollout status deployment/tweet-provider ``` Check Dapr to make sure components were registered correctly ```shell kubectl logs -l app=tweet-provider -c daprd --tail 200 ``` ## Disclaimer This 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. ## License This software is released under the [MIT](../LICENSE) ================================================ FILE: pipeline/tweet-provider/config/pubsub.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: tweeter-pubsub spec: type: pubsub.redis metadata: - name: redisHost value: localhost:6379 - name: redisPassword value: "" ================================================ FILE: pipeline/tweet-provider/config/secret.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: pipeline-secrets spec: type: secretstores.local.file metadata: - name: secretsFile value: "/etc/dapr/secrets.json" ================================================ FILE: pipeline/tweet-provider/config/state.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: tweet-store spec: type: state.redis metadata: - name: redisHost value: localhost:6379 - name: redisPassword value: "" ================================================ FILE: pipeline/tweet-provider/config/twitter.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: tweets spec: type: bindings.twitter metadata: - name: consumerKey secretKeyRef: name: Twitter:ConsumerKey - name: consumerSecret secretKeyRef: name: Twitter:ConsumerSecret - name: accessToken secretKeyRef: name: Twitter:AccessToken - name: accessSecret secretKeyRef: name: Twitter:AccessSecret - name: query value: "football" # common term for demo auth: secretStore: pipeline-secrets ================================================ FILE: pipeline/tweet-provider/go.mod ================================================ module github.com/mchmarny/dapr-demos/pipeline/tweet-provider go 1.15 require ( github.com/dapr/go-sdk v0.11.0 github.com/pkg/errors v0.9.1 golang.org/x/net v0.0.0-20200930145003-4acb6c075d10 // indirect golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f // indirect google.golang.org/genproto v0.0.0-20201002142447-3860012362da // indirect ) ================================================ FILE: pipeline/tweet-provider/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo= github.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200930145003-4acb6c075d10 h1:YfxMZzv3PjGonQYNUaeU2+DhAdqOxerQ30JFB6WgAXo= golang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 h1:4X356008q5SA3YXu8PiRap39KFmy4Lf6sGlceJKZQsU= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0 h1:uslsjIdqvZYANxSBQjTI47vZfwMaTN3mLELkMnMIY/A= google.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201002142447-3860012362da h1:DTQYk4u7nICKkkVZsBv0/0po0ChISxAJ5CTAfUhO0PQ= google.golang.org/genproto v0.0.0-20201002142447-3860012362da/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: pipeline/tweet-provider/main.go ================================================ package main import ( "context" "encoding/json" "fmt" "log" "net/http" "os" "strings" dapr "github.com/dapr/go-sdk/client" "github.com/dapr/go-sdk/service/common" daprd "github.com/dapr/go-sdk/service/http" "github.com/pkg/errors" ) var ( logger = log.New(os.Stdout, "", 0) address = getEnvVar("ADDRESS", ":8080") pubSubName = getEnvVar("PUBSUB_NAME", "tweeter-pubsub") topicName = getEnvVar("TOPIC_NAME", "tweets") storeName = getEnvVar("STORE_NAME", "tweet-store") client dapr.Client ) func main() { // create a Dapr service s := daprd.NewService(address) // create a Dapr client c, err := dapr.NewClient() if err != nil { logger.Fatalf("error creating Dapr client: %v", err) } client = c defer client.Close() // add twitter input binding handler if err := s.AddBindingInvocationHandler("tweets", tweetHandler); err != nil { logger.Fatalf("error adding binding handler: %v", err) } // start the service if err := s.Start(); err != nil && err != http.ErrServerClosed { logger.Fatalf("error starting service: %v", err) } } func tweetHandler(ctx context.Context, in *common.BindingEvent) (out []byte, err error) { logger.Printf("Tweet (query: %s, traceID: %s)", in.Metadata["Query"], in.Metadata["Traceparent"]) var m map[string]interface{} if err := json.Unmarshal(in.Data, &m); err != nil { return nil, errors.Wrap(err, "error deserializing event data") } k := fmt.Sprintf("tw-%s", m["id_str"]) if err := client.SaveState(ctx, storeName, k, in.Data); err != nil { return nil, errors.Wrapf(err, "error saving to store:%s with key:%s", storeName, k) } logger.Printf("Tweet saved in store: %s: %s", storeName, k) if err := client.PublishEvent(ctx, pubSubName, topicName, in.Data); err != nil { return nil, errors.Wrapf(err, "error publishing to %s/%s", pubSubName, topicName) } logger.Printf("Tweet published to %s/%s", pubSubName, topicName) return } func getEnvVar(key, fallbackValue string) string { if val, ok := os.LookupEnv(key); ok { return strings.TrimSpace(val) } return fallbackValue } ================================================ FILE: pipeline/tweet-viewer/Dockerfile ================================================ FROM golang:1.15.0 as builder WORKDIR /src/ COPY . /src/ ARG APP_VERSION=v0.0.1-default ENV APP_VERSION=$APP_VERSION ENV GO111MODULE=on RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -a -tags netgo -ldflags \ "-w -extldflags '-static' -X main.AppVersion=${APP_VERSION}" \ -mod vendor -o ./service . FROM gcr.io/distroless/static:nonroot COPY --from=builder /src/service . COPY --from=builder /src/resource ./resource/ ENTRYPOINT ["./service"] ================================================ FILE: pipeline/tweet-viewer/Makefile ================================================ RELEASE_VERSION =v0.11.1 SERVICE_NAME ?=tweet-viewer DOCKER_USERNAME ?=$(DOCKER_USER) .PHONY: tidy test debug build run jsonevent xmlevent binevent image lint clean tag all: help tidy: ## Updates the go modules and vendors all dependencies go mod tidy go mod vendor test: tidy ## Tests the entire project go test -count=1 -race ./... debug: tidy ## Runs uncompiled code in Dapr dapr run \ --app-id $(SERVICE_NAME) \ --app-port 8084 \ --app-protocol http \ --dapr-http-port 3500 \ --components-path ./config \ --log-level debug \ go run main.go post: ## Posts sample tweet curl -v -d @tweet.json \ -H "Content-type: application/json" \ http://localhost:3500/v1.0/publish/processed-tweets-pubsub/processed-tweets image: tidy ## Builds and publish docker image docker build --build-arg APP_VERSION=$(RELEASE_VERSION) -t "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" . docker push "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" deploy: ## Deploys prebuild image to k8s using currently selected context kubectl apply -f k8s/source-pubsub.yaml kubectl apply -f k8s/deployment.yaml kubectl rollout restart deployment/tweet-viewer kubectl rollout status deployment/tweet-viewer api-patch: ## Patch API gatewaty to add route map kubectl patch ingress ingress-rules --type json -p "$(cat k8s/ingress.json)" lint: ## Lints the entire project golangci-lint run --timeout=3m tag: ## Creates release tag git tag $(RELEASE_VERSION) git push origin $(RELEASE_VERSION) clean: ## Cleans up generated files go clean rm -fr ./bin rm -fr ./vendor help: ## Display available commands @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \ 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ================================================ FILE: pipeline/tweet-viewer/config/source-pubsub.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: processed-tweets-pubsub spec: type: pubsub.redis metadata: - name: redisHost value: localhost:6379 - name: redisPassword value: "" ================================================ FILE: pipeline/tweet-viewer/go.mod ================================================ module github.com/mchmarny/dapr-demos/pipeline/tweet-viewer go 1.15 require ( github.com/dapr/go-sdk v0.11.0 github.com/gorilla/websocket v1.4.2 // indirect github.com/pkg/errors v0.9.1 gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376 ) ================================================ FILE: pipeline/tweet-viewer/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo= github.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376 h1:sY2a+y0j4iDrajJcorb+a0hJIQ6uakU5gybjfLWHlXo= gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376/go.mod h1:BHKOc1m5wm8WwQkMqYBoo4vNxhmF7xg8+xhG8L+Cy3M= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: pipeline/tweet-viewer/main.go ================================================ package main import ( "context" "encoding/json" "fmt" "html/template" "log" "net/http" "os" "strings" "gopkg.in/olahol/melody.v1" "github.com/dapr/go-sdk/service/common" daprd "github.com/dapr/go-sdk/service/http" "github.com/pkg/errors" ) var ( // AppVersion will be overritten during build AppVersion = "v0.0.1-default" // service logger = log.New(os.Stdout, "", 0) address = getEnvVar("ADDRESS", ":8084") pubSubName = getEnvVar("PUBSUB_NAME", "processed-tweets-pubsub") topicName = getEnvVar("TOPIC_NAME", "processed-tweets") broadcaster *melody.Melody templates *template.Template ) func main() { // server mux mux := http.NewServeMux() // static content mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("resource/static")))) mux.HandleFunc("/favicon.ico", faviconHandler) // tempalates templates = template.Must(template.ParseGlob("resource/template/*")) // websocket upgrade broadcaster = melody.New() broadcaster.Upgrader.CheckOrigin = func(r *http.Request) bool { return true } // other handlers mux.HandleFunc("/", rootHandler) mux.HandleFunc("/ws", wsHandler) // create a Dapr service s := daprd.NewServiceWithMux(address, mux) // add some topic subscriptions subscription := &common.Subscription{ PubsubName: pubSubName, Topic: topicName, Route: fmt.Sprintf("/%s", topicName), } if err := s.AddTopicEventHandler(subscription, eventHandler); err != nil { logger.Fatalf("error adding topic subscription: %v", err) } // start the service if err := s.Start(); err != nil && err != http.ErrServerClosed { logger.Fatalf("error starting service: %v", err) } } func wsHandler(w http.ResponseWriter, r *http.Request) { broadcaster.HandleRequest(w, r) } func rootHandler(w http.ResponseWriter, r *http.Request) { proto := r.Header.Get("x-forwarded-proto") if proto == "" { proto = "http" } data := map[string]string{ "host": r.Host, "proto": proto, "version": AppVersion, } err := templates.ExecuteTemplate(w, "index", data) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } func faviconHandler(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "./resource/static/img/favicon.ico") } func eventHandler(ctx context.Context, e *common.TopicEvent) (retry bool, err error) { b, err := json.Marshal(e.Data) if err != nil { return false, errors.Wrap(err, "error marshaling data") } broadcaster.Broadcast(b) return false, nil } func getEnvVar(key, fallbackValue string) string { if val, ok := os.LookupEnv(key); ok { return strings.TrimSpace(val) } return fallbackValue } ================================================ FILE: pipeline/tweet-viewer/resource/static/css/app.css ================================================ /* Knative colors Dark Blue: #0865ad Light Blue: #6695ca */ html, body { height: 100%; margin: 0; padding: 0; background-color: #fff; } #wrapper { padding: 0; margin: 0; height: 100%; text-align: center; widows: 100%; } #page-header { padding: 0; margin: 10px 0 0 0; clear: both; break-after: always; height: 100px; } #page-header-image { border: 0; margin-left: 25px; } #page-header-image img { width: 100px; height: 100px; float: left; } #connection { float: right; margin: 10px; font-family: Geneva, Verdana, sans-serif; font-size: 1em; } #middle-section { margin: 0; padding: 10px; height: 700px; overflow: auto; } #middle-section div { margin: 5px; } #page-footer { widows: 80%; margin: 30px 0 0 0; font-family: Geneva, Verdana, sans-serif; font-size: 1em; } #tweets { background-color: #20329b; padding: 0; margin: 5px; height: 690px; overflow: auto; } img.profile-pic { width: 72px; height: 72px; margin: 0 5px 5px 0; float: left; } div.item-text { margin-left: 10px; padding-left: 10px; } #tweets b { font-family: Geneva, Verdana, sans-serif; font-size: 1em; color: #20329b; margin: 0; } #tweets i { font-family: Geneva, Verdana, sans-serif; font-size: 0.8em; font-style: normal; } #tweets i.small { font-size: 0.7em; color: #666666; } img.sentiment { width: 18px; height: 18px; margin: 0 3px 0 0; vertical-align: baseline; } img.tweet-link { width: 25px; height: 25px; margin: 0 0 0 3px; vertical-align: baseline; } .error { color: red; } .item { clear:both; padding: 5px; text-align: left; background-color: #fff; overflow: auto; } ================================================ FILE: pipeline/tweet-viewer/resource/static/js/app.js ================================================ window.onload = function () { console.log("Protocol: " + location.protocol); var wsURL = "ws://" + document.location.host + "/ws" if (location.protocol == 'https:') { wsURL = "wss://" + document.location.host + "/ws" } console.log("WS URL: " + wsURL); var log = document.getElementById("tweets"); function appendLog(item) { var doScroll = log.scrollTop > log.scrollHeight - log.clientHeight - 1; log.appendChild(item); if (doScroll) { log.scrollTop = log.scrollHeight - log.clientHeight; } } if (log) { sock = new WebSocket(wsURL); var connDiv = document.getElementById("connection-status"); connDiv.innerText = "closed"; sock.onopen = function () { console.log("connected to " + wsURL); connDiv.innerText = "open"; }; sock.onclose = function (e) { console.log("connection closed (" + e.code + ")"); connDiv.innerText = "closed"; }; sock.onmessage = function (e) { console.log(e); var t = JSON.parse(e.data); console.log(t); var scoreStr = "neutral"; var scoreAlt = "neutral: 0" if (t.hasOwnProperty("sentiment")) { console.log(t.sentiment); if (t.sentiment.sentiment.length > 0) { scoreStr = t.sentiment.sentiment; scoreAlt = scoreStr + ": " + t.sentiment.confidence; } } var tweetText = t.text; if(t.extended_tweet != null) { tweetText = t.extended_tweet.full_text; } var item = document.createElement("div"); item.className = "item"; // TODO: template this var tmsg = "" + "
" + t.user.screen_name + "" + "
" + tweetText + "
"; item.innerHTML = tmsg appendLog(item); }; } // if log }; ================================================ FILE: pipeline/tweet-viewer/resource/template/footer.html ================================================ {{ define "footer" }}
{{ end }} ================================================ FILE: pipeline/tweet-viewer/resource/template/header.html ================================================ {{ define "header" }} dapr event viewer
{{end}} ================================================ FILE: pipeline/tweet-viewer/resource/template/index.html ================================================ {{ define "index" }} {{ template "header" . }}
{{ template "footer" . }} {{ end }} ================================================ FILE: pipeline/tweet-viewer/tweet.json ================================================ { "created_at": "Sun Feb 25 18:11:01 +0000 2018", "id": 967824267948773377, "id_str": "967824267948773377", "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", "truncated": false, "entities": { "hashtags": [], "symbols": [], "user_mentions": [], "urls": [ { "url": "https://t.co/FjPEWnh804", "expanded_url": "https://twitter.com/i/web/status/967824267948773377", "display_url": "twitter.com/i/web/status/9…", "indices": [ 117, 140 ] } ] }, "metadata": { "result_type": "popular", "iso_language_code": "en" }, "user": { "id": 11348282, "id_str": "11348282", "name": "NASA", "screen_name": "NASA", "location": "", "description": "Explore the universe and discover our home planet with @NASA. We usually post in EST (UTC-5)", "url": "https://t.co/TcEE6NS8nD", "entities": { "url": { "urls": [ { "url": "https://t.co/TcEE6NS8nD", "expanded_url": "http://www.nasa.gov", "display_url": "nasa.gov", "indices": [ 0, 23 ] } ] }, "description": { "urls": [] } }, "protected": false, "followers_count": 28605561, "friends_count": 270, "listed_count": 90405, "created_at": "Wed Dec 19 20:20:32 +0000 2007", "favourites_count": 2960, "utc_offset": -18000, "time_zone": "Eastern Time (US & Canada)", "geo_enabled": false, "verified": true, "statuses_count": 50713, "lang": "en", "contributors_enabled": false, "is_translator": false, "is_translation_enabled": false, "profile_background_color": "000000", "profile_background_image_url": "http://pbs.twimg.com/profile_background_images/590922434682880000/3byPYvqe.jpg", "profile_background_image_url_https": "https://pbs.twimg.com/profile_background_images/590922434682880000/3byPYvqe.jpg", "profile_background_tile": false, "profile_image_url": "http://pbs.twimg.com/profile_images/188302352/nasalogo_twitter_normal.jpg", "profile_image_url_https": "https://pbs.twimg.com/profile_images/188302352/nasalogo_twitter_normal.jpg", "profile_banner_url": "https://pbs.twimg.com/profile_banners/11348282/1518798395", "profile_link_color": "205BA7", "profile_sidebar_border_color": "000000", "profile_sidebar_fill_color": "F3F2F2", "profile_text_color": "000000", "profile_use_background_image": true, "has_extended_profile": true, "default_profile": false, "default_profile_image": false, "following": null, "follow_request_sent": null, "notifications": null, "translator_type": "regular" }, "geo": null, "coordinates": null, "place": null, "contributors": null, "is_quote_status": false, "retweet_count": 988, "favorite_count": 3875, "favorited": false, "retweeted": false, "possibly_sensitive": false, "lang": "en", "sentiment": { "sentiment": "positive", "confidence": 0.98 } } ================================================ FILE: setup/Makefile ================================================ DOMAIN ?=example.com CLUSTER_NAME ?=demo DAPR_RELEASE ?=1.0.0-rc.1 DAPR_HA ?=true DAPR_LOG_AS_JSON ?=true .PHONY: all all: @echo === ACTIVE CONFIGURATION === @echo "DOMAIN: ${DOMAIN}" @echo "CLUSTER_NAME: ${CLUSTER_NAME}" @echo "DAPR_RELEASE: ${DAPR_RELEASE}" @echo "DAPR_HA: ${DAPR_HA}" @echo "DAPR_LOG_AS_JSON: ${DAPR_LOG_AS_JSON}" @echo @echo "Export these as environment variables to change their values" @echo .PHONY: node-list node-list: ## Print node resources and their usage kubectl top nodes .PHONY: certs certs: ## Create wildcard TLS certificates using letsencrypt mkdir -p certs/$(DOMAIN) sudo certbot certonly --manual --preferred-challenges dns -d "*.$(DOMAIN)" sudo cp "/etc/letsencrypt/live/$(DOMAIN)/fullchain.pem" certs/$(DOMAIN)/cert-ca.pem sudo cp "/etc/letsencrypt/live/$(DOMAIN)/privkey.pem" certs/$(DOMAIN)/cert-pk.pem sudo chmod 644 certs/$(DOMAIN)/*.pem .PHONY: dapr dapr: dapr-install keda-install observe-install ## Install dapr, keda, and observability .PHONY: dapr-install dapr-install: ## Install and configures Dapr # Updating Help repos... helm repo add dapr https://dapr.github.io/helm-charts/ helm repo update # Installing Dapr... kubectl create ns dapr-system helm install dapr dapr/dapr -n dapr-system \ --version $(DAPR_RELEASE) \ --set global.logAsJson=$(DAPR_LOG_AS_JSON) \ --set global.ha.enabled=$(DAPR_HA) # Wait for everything to finish installing kubectl rollout status deployment/dapr-operator -n dapr-system kubectl rollout status deployment/dapr-dashboard -n dapr-system kubectl rollout status deployment/dapr-sentry -n dapr-system kubectl rollout status deployment/dapr-sidecar-injector -n dapr-system .PHONY: keda-install keda-install: ## Install and configures Keda # Updating Help repos... helm repo add kedacore https://kedacore.github.io/charts helm repo update # Installing Keda kubectl create ns keda helm install keda kedacore/keda -n keda --set logLevel=debug # Wait for everything to finish installing kubectl rollout status deployment/keda-operator -n keda kubectl rollout status deployment/keda-operator-metrics-apiserver -n keda .PHONY: observe-install observe-install: ## Install observability stack # Updating Help repos... helm repo add stable https://charts.helm.sh/stable helm repo add elastic https://helm.elastic.co helm repo update # Installing observabiliity... kubectl create ns dapr-monitoring kubectl apply -f config/fluentd-config.yaml -f config/fluentd.yaml kubectl apply -f config/zipkin.yaml -n dapr-monitoring helm install elasticsearch elastic/elasticsearch -n dapr-monitoring helm install dapr-prom stable/prometheus -n dapr-monitoring helm install grafana stable/grafana -n dapr-monitoring \ --set persistence.enabled=true \ --set persistence.accessModes={ReadWriteOnce} \ --set persistence.size=8Gi helm install kibana elastic/kibana -n dapr-monitoring # Wait for everything to be ready... kubectl rollout status deployment/dapr-prom-kube-state-metrics -n dapr-monitoring kubectl rollout status deployment/dapr-prom-prometheus-alertmanager -n dapr-monitoring kubectl rollout status deployment/dapr-prom-prometheus-pushgateway -n dapr-monitoring kubectl rollout status deployment/dapr-prom-prometheus-server -n dapr-monitoring kubectl rollout status deployment/grafana -n dapr-monitoring kubectl rollout status deployment/kibana-kibana -n dapr-monitoring .PHONY: config config: ports ## Configure Dapr after install $(eval GRAFANA_PASS=$(shell kubectl get secret -n dapr-monitoring grafana -o jsonpath="{.data.admin-password}" | base64 --decode)) # Check that everything is ready... kubectl rollout status deployment/dapr-prom-kube-state-metrics -n dapr-monitoring kubectl rollout status deployment/dapr-prom-prometheus-alertmanager -n dapr-monitoring kubectl rollout status deployment/dapr-prom-prometheus-pushgateway -n dapr-monitoring kubectl rollout status deployment/dapr-prom-prometheus-server -n dapr-monitoring kubectl rollout status deployment/grafana -n dapr-monitoring kubectl rollout status deployment/kibana-kibana -n dapr-monitoring # Configure grafana curl -X POST -s -k -u "admin:$(GRAFANA_PASS)" \ -H "Content-Type: application/json" \ -d '{ "name":"Dapr", "type":"prometheus", "url":"http://dapr-prom-prometheus-server.dapr-monitoring", "access":"proxy", "basicAuth":false }' \ http://localhost:8888/api/datasources curl -X POST -s -k -u "admin:$(GRAFANA_PASS)" \ -H "Content-Type: application/json" \ -d @config/system-services-dashboard.json \ http://localhost:8888/api/dashboards/db curl -X POST -s -k -u "admin:$(GRAFANA_PASS)" \ -H "Content-Type: application/json" \ -d @config/sidecar-dashboard.json \ http://localhost:8888/api/dashboards/db curl -X POST -s -k -u "admin:$(GRAFANA_PASS)" \ -H "Content-Type: application/json" \ -d @config/actor-dashboard.json \ http://localhost:8888/api/dashboards/db # Configure kibana curl -X POST -H "kbn-xsrf: true" \ -H "Content-Type: application/json" \ -d '{"attributes":{"title":"dapr*","timeFieldName":"@timestamp"}}' \ "http://localhost:5601/api/saved_objects/index-pattern/dapr" curl -X POST -H "kbn-xsrf: true" \ -H "Content-Type: application/json" \ -d '{"value":"dapr"}' \ "http://localhost:5601/api/kibana/settings/defaultIndex" .PHONY: ingress ingress: ## Install and configures Ngnx ingress, configure SSL termination, Dapr API auth # Updating Help repos... helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx helm repo update # Configure nginx namespace sed "s/NSNAME/nginx/g" config/namespace-template.yml > config/namespace.yml kubectl apply -f config/namespace.yml # Gen Dapr API token $(eval API_TOKEN=$(shell openssl rand -base64 32)) kubectl create secret generic dapr-api-token --from-literal=token="$(API_TOKEN)" -n nginx # Apply ingress config kubectl apply -f config/ingress-config.yaml -n nginx # Deploy nginx... helm install nginx ingress-nginx/ingress-nginx \ --set controller.replicaCount=2 \ --set controller.metrics.enabled=true \ -f config/ingress-annotations.yaml \ -n nginx kubectl rollout status deployment/nginx-ingress-nginx-controller -n nginx # Install cert secrets kubectl create secret tls tls-secret \ --key certs/$(DOMAIN)/cert-pk.pem \ --cert certs/$(DOMAIN)/cert-ca.pem \ -n nginx sed "s/DOMAINNAME/${DOMAIN}/g" config/ingress-template.yaml > config/ingress.yaml # Apply configured ingress kubectl apply -f config/ingress.yaml -n nginx .PHONY: dns dns: ## Check DNS resolution for cluster IP dig api.$(DOMAIN) $(eval LB_IP=$(shell kubectl get svc nginx-ingress-nginx-controller -n nginx -o jsonpath='{.status.loadBalancer.ingress[0].ip}')) @echo === DNS CHECK === @echo @echo "Ensure the A record for 'api.${DOMAIN}' in the ANSWER SECTION resolves to:" @echo @echo " ${LB_IP}" @echo @echo If not, update DNS with below entry and re-run this test before moving to the next step @echo @echo " Hostname: *" @echo " IP address: ${LB_IP}" @echo " TTL: 1m" @echo .PHONY: test test: ## Test deployment and execute Dapr API health checks $(eval API_TOKEN=$(shell kubectl get secret dapr-api-token -n nginx -o jsonpath="{.data.token}" | base64 --decode)) curl -v \ -H "Content-type: application/json" \ -H "dapr-api-token: $(API_TOKEN)" \ "https://api.$(DOMAIN)/v1.0/healthz" @echo @echo === DNS CHECK === @echo Ensure server certificate has: @echo @echo " subject: CN=*.${DOMAIN}" @echo " subjectAltName: host 'api.${DOMAIN}' matched cert's '*.${DOMAIN}'" @echo " SSL certificate verify ok" @echo @echo And that the response status from Dapr health checks was '200' @echo @echo " HTTP/2 200" @echo .PHONY: token token: ## Print Dapr API token $(eval API_TOKEN=$(shell kubectl get secret dapr-api-token -n nginx -o jsonpath="{.data.token}" | base64 --decode)) @echo @echo Dapr API token is: @echo @echo " ${API_TOKEN}" @echo .PHONY: pass pass: ## Print Grafana admin password $(eval GPASS=$(shell kubectl get secret -n dapr-monitoring grafana -o jsonpath="{.data.admin-password}" | base64 --decode)) @echo @echo Grafana admin password is: @echo @echo " ${GPASS}" @echo .PHONY: ports ports: portstop ## Forward observability ports kubectl port-forward svc/kibana-kibana 5601 -n dapr-monitoring & kubectl port-forward svc/grafana 8888:80 -n dapr-monitoring & kubectl port-forward svc/zipkin 9411 -n dapr-monitoring & @echo Ports forwarded: @echo @echo kibana - http://localhost:5601 @echo grafana - http://localhost:8888 @echo zipkin - http://localhost:9411 @echo @echo "To stop forwarding run 'make portstop'" @echo .PHONY: reload reload: ## Reloads API to pickup new components kubectl rollout restart deployment/nginx-ingress-nginx-controller -n nginx kubectl rollout status deployment/nginx-ingress-nginx-controller -n nginx .PHONY: redis redis: ## Install Redis into the cluster # Updating Help repos... helm repo add bitnami https://charts.bitnami.com/bitnami helm repo update kubectl create ns redis # redis helm install redis bitnami/redis -n redis # Waiting for redis to be ready... kubectl rollout status statefulset.apps/redis-master -n redis kubectl rollout status statefulset.apps/redis-slave -n redis .PHONY: mongo mongo: ## Install Mongo into the cluster # Updating Help repos... helm repo add bitnami https://charts.bitnami.com/bitnami helm repo update kubectl create ns mongo # mongo helm install mongo \ --set architecture=replicaset \ --set auth.username=dapr \ --set auth.database=dapr \ --set replicaSetName=staters0 \ --set replicaCount=3 \ bitnami/mongodb \ -n mongo # Waiting for mongo to be ready... kubectl rollout status statefulset.apps/mongo-mongodb -n mongo kubectl rollout status statefulset.apps/mongo-mongodb-arbiter -n mongo .PHONY: kafka kafka: ## Install Kafka into the cluster # Updating Help repos... helm repo add confluentinc https://confluentinc.github.io/cp-helm-charts/ helm repo update kubectl create ns kafka # kafka helm install kafka confluentinc/cp-helm-charts -n kafka \ --set cp-schema-registry.enabled=false \ --set cp-kafka-rest.enabled=false \ --set cp-kafka-connect.enabled=false # wait for the deployment kubectl rollout status deployment.apps/kafka-cp-control-center -n kafka kubectl rollout status deployment.apps/kafka-cp-ksql-server -n kafka kubectl rollout status statefulset.apps/kafka-cp-kafka -n kafka kubectl rollout status statefulset.apps/kafka-cp-zookeeper -n kafka .PHONY: namespace namespace: ## Configures namespace (make namespace NSNAME=default) # Create namespace if one doesn't exists sed "s/NSNAME/${NSNAME}/g" config/namespace-template.yml > config/namespace.yml kubectl apply -f config/namespace.yml .PHONY: namespace-pass namespace-pass: ## Configures Mongo and Redis passwords in namespace (make namespace-pass NSNAME=default) # Configure Redis password $(eval REDIS_PASSWORD=$(shell kubectl get secret -n redis redis -o jsonpath="{.data.redis-password}" | base64 --decode)) kubectl create secret generic redis-secret --from-literal=password="$(REDIS_PASSWORD)" -n $(NSNAME) # Configre Mongo password $(eval MONGO_PASSWORD=$(shell kubectl get secret -n mongo mongo-mongodb -o jsonpath="{.data.mongodb-password}" | base64 --decode)) kubectl create secret generic mongo-secret --from-literal=password="$(MONGO_PASSWORD)" -n $(NSNAME) .PHONY: portstop portstop: ## Stop previously forwarded observability ports if pgrep kubectl &> /dev/null ; then pkill kubectl -9 ; fi .PHONY: upgrade upgrade: ## Upgrades Dapr to specific release version (make upgrade DAPR_RELEASE="0.11.0-rc.3") kubectl delete clusterrolebinding dapr-operator dapr mtls export -o ./certs helm upgrade dapr -n=dapr-system \ --set-string global.tag=$(DAPR_RELEASE) \ --set-string global.registry=docker.io/daprio \ --set-file dapr_sentry.tls.root.certPEM=./certs/ca.crt \ --set-file dapr_sentry.tls.issuer.certPEM=./certs/issuer.crt \ --set-file dapr_sentry.tls.issuer.keyPEM=./certs/issuer.key \ --reset-values ./charts/dapr .PHONY: help help: ## Display available commands @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \ 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ================================================ FILE: setup/README.md ================================================ # Dapr Cluster Setup An opinionated deployment of Dapr on Kubernetes, configured with: * Ingress with custom domain and TLS termination * [NGINX](https://nginx.org/en/) for ingress controller and TLS to service mapping * [letsencrypt](https://letsencrypt.org/) as certificate provider * [KEDA](https://keda.sh/) for autoscaling * Metrics Monitoring * [Prometheus](https://prometheus.io/) for metrics aggregation * [Grafana](https://grafana.com/) for metrics visualization with Dapr monitoring dashboards * Log Management * [Fluentd](https://www.fluentd.org/) for log collection and forwarding * [Elasticsearch](https://www.elastic.co/) for log aggregation and query execution * [Kibana](https://www.elastic.co/products/kibana) for full-text log query and visualization * Distributed Tracing * [Jaeger](https://www.jaegertracing.io/) for capturing traces, latency and dependency viewing > All demos in the [dapr-demo](../) repository are validated on this deployment ## Prerequisites * 1.15+ Kubernates cluster. If needed, you can setup cluster on: * [AKS](./aks/) * [GKE](./gke/) * AKS (coming) * Tooling on the machine where you will be running this setup: * [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) to do k8s stuff (`brew install kubectl`) * [Helm 3](https://helm.sh/docs/intro/install/) to install Dapr and its dependencies (`brew install helm`) * [certbot](https://certbot.eff.org/lets-encrypt/osx-other.html) to generate wildcard cert (`brew install certbot`) * 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) ## Setup The following parameters can be used to configure your deployment. Define these as environment variables to set or override the default value: ```shell DOMAIN # default: example.com DAPR_HA # default: true DAPR_LOG_AS_JSON # default: true ``` > 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`. ## Usage Start by navigate to the [setup](./setup) directory > Run `make` by itself to see the active configuration To deploy and configure Dapr * `make dapr` to install Dapr, KEDA, and the entire observability stack * `make config` to perform post-install configurations > Optionally you can use `make upgrade` to in place upgrade Dapr to specific version To configure external access * `make ip` (optional) to create static IP in the cluster resource group * `make certs` to create TLS certs using letsencrypt * `make ingress` to configures NGINX ingress, SSL termination, Dapr API auth * `make dns` to configure your DNS service for custom domain support * `make test` to test deployment To deploy in-cluster data services * `make redis` to install Redis into the cluster * `make mongo` to install Mongo into the cluster * `make kafka` to install Kafka into the cluster And few cluster operations helpers * `node-list` to print node resources and their usage * `make ports` to forward observability dashboards ports * `make pass` to print the Grafana password (username: admin) Then for each namespace you want to deploy Dapr apps to * `make namespace` to create/configure namespace with service secrets ## Accessing observability dashboards To get access to the Kibana, Grafana, Zipkin dashboards run: ```shell make ports ``` This will forward the necessary ports so you can access the dashboards using: * kibana - http://localhost:5601 * grafana - http://localhost:8888 * zipkin - http://localhost:9411 To stop port forwarding run ```shell make portstop ``` ## Help To find the list of all the commands with their short descriptions run: ```shell make help ``` ## Disclaimer This 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. ## License This software is released under the [MIT](../LICENSE) ================================================ FILE: setup/aks/Makefile ================================================ CLUSTER_NAME ?=demo CLUSTER_VERSION ?=1.18.10 NODE_COUNT ?=3 NODE_TYPE ?=Standard_D4_v2 .PHONY: all all: @echo === ACTIVE CONFIGURATION === @echo "CLUSTER_NAME: ${CLUSTER_NAME}" @echo "CLUSTER_VERSION: ${CLUSTER_VERSION}" @echo "NODE_COUNT: ${NODE_COUNT}" @echo "NODE_TYPE: ${NODE_TYPE}" @echo @echo "Export these as environment variables to change their values" @echo .PHONY: cluster-list cluster-list: ## List AKS clusters az aks list -o table .PHONY: version-list version-list: ## List Kubernetes versions supported in AKS az aks get-versions -o json --query 'orchestrators[].orchestratorVersion' .PHONY: cluster cluster: ## Create AKS cluster az aks create \ --name $(CLUSTER_NAME) \ --node-count $(NODE_COUNT) \ --node-vm-size $(NODE_TYPE) \ --kubernetes-version $(CLUSTER_VERSION) az aks get-credentials --name $(CLUSTER_NAME) .PHONY: cluster-down cluster-down: cluster-list ## Delete previously created AKS cluster (make clusterdown CLUSTER_NAME=demo) az aks delete --name $(CLUSTER_NAME) .PHONY: ip ip: ## Create Static IP for the existing AKS cluster $(eval CLUSTERRC=$(shell az aks show -n $(CLUSTER_NAME) -o tsv --query nodeResourceGroup)) az network public-ip create \ -g $(CLUSTERRC) \ -n "${CLUSTER_NAME}IP" \ --sku STANDARD az network public-ip show \ -g $(CLUSTERRC) \ -n "${CLUSTER_NAME}IP" \ -o tsv --query ipAddress .PHONY: node-pool node-pool: ## Add new AKS node pool $(eval NODE_SUFIX=$(shell date +"%d%m")) az aks nodepool add \ --cluster-name $(CLUSTER_NAME) \ --name nodepool$(NODE_SUFIX) \ --node-count $(NODE_COUNT) \ --node-vm-size $(NODE_TYPE) \ --mode System \ --no-wait .PHONY: help help: ## Display available commands @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \ 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ================================================ FILE: setup/aks/README.md ================================================ # AKS Cluster Setup The following parameters can be used to configure your deployment. Define these as environment variables to set or override the default value: ```shell CLUSTER_NAME # default: demo CLUSTER_VERSION # default: 1.18.8 NODE_COUNT # default: 3 NODE_TYPE # default: Standard_D4_v2 ``` > Note, this assumes your default Azure resource group and location are already defined. If not, run ```shell az account set --subscription az configure --defaults location= group= ``` ## Usage Start by navigating to the [setup/aks](./setup/aks) directory > Run `make` by itself to see the active configuration * `make cluster` to create a cluster on AKS (make cluster CLUSTER_NAME=demo) * `make ip` (optional) to create static IP in the cluster resource group * `make node-pool` (optional) to add new AKS node pool * `make node-list` to print node resource usage * `make cluster-list` to list your AKS clusters * `make version-list` to list Kubernetes versions supported on AKS Once cluster is created, you can follow [these instructions](../) to configure Dapr. ## Cleanup To lists previously created clusters run ```shell make cluster-list ``` To delete any of the previously created clusters run > yes, there will be a prompt to confirm before deleting ```shell make cluster-down CLUSTER_NAME=name ``` ## Help To find the list of all the commands with their short descriptions run: ```shell make help ``` ## Disclaimer This 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. ## License This software is released under the [MIT](../../LICENSE) ================================================ FILE: setup/config/actor-dashboard.json ================================================ { "dashboard": { "__inputs": [ { "name": "DS_DAPR", "label": "Dapr", "description": "", "type": "datasource", "pluginId": "prometheus", "pluginName": "Prometheus" } ], "__requires": [ { "type": "grafana", "id": "grafana", "name": "Grafana", "version": "6.7.3" }, { "type": "panel", "id": "graph", "name": "Graph", "version": "" }, { "type": "datasource", "id": "prometheus", "name": "Prometheus", "version": "1.0.0" } ], "annotations": { "list": [ { "$$hashKey": "object:487", "builtIn": 1, "datasource": "Dapr", "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "limit": 100, "name": "Annotations & Alerts", "showIn": 0, "type": "dashboard" } ] }, "description": "This is the dashboard for Dapr Actor", "editable": true, "gnetId": null, "graphTooltip": 0, "id": null, "iteration": 1591551276071, "links": [], "panels": [ { "collapsed": true, "datasource": "Dapr", "gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 }, "id": 21, "panels": [ { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Dapr", "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.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 1 }, "hiddenSeries": false, "id": 19, "legend": { "alignAsTable": false, "avg": false, "current": true, "max": false, "min": false, "rightSide": false, "show": true, "sideWidth": 150, "total": false, "values": true }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pluginVersion": "6.6.2", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "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))", "format": "time_series", "instant": false, "interval": "", "legendFormat": "[95p] {{method}} {{path}} ({{app_id}})", "refId": "A" }, { "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))", "format": "time_series", "instant": false, "interval": "", "legendFormat": "[75p] {{method}} {{path}} ({{app_id}})", "refId": "B" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Actor API Latency (App->Dapr) (95p, 75p)", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "$$hashKey": "object:1089", "decimals": 2, "format": "ms", "label": "", "logBase": 1, "max": null, "min": null, "show": true }, { "$$hashKey": "object:1090", "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": false } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Dapr", "description": "This charts shows completed request rate (rps) when User app calls Dapr Actor APIs.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 12, "y": 1 }, "hiddenSeries": false, "id": 31, "legend": { "alignAsTable": false, "avg": false, "current": false, "max": false, "min": false, "rightSide": false, "show": true, "sideWidth": 150, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pluginVersion": "6.6.2", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "sum by (app_id, method, path) (rate(dapr_http_server_response_count{path=~\"/v1.0/actors/$dapr_actor_type/.*\", kubernetes_namespace=\"$namespace\"}[5m]))", "format": "time_series", "instant": false, "interval": "", "legendFormat": "{{method}} {{path}} ({{app_id}})", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Actor API request rate (App -> Dapr) (RPS)", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "$$hashKey": "object:191", "decimals": 2, "format": "reqps", "label": "", "logBase": 1, "max": null, "min": null, "show": true }, { "$$hashKey": "object:192", "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": false } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Dapr", "description": "This charts shows the inbound request latency from Dapr to App. You can analyze App's API endpoint performance.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 9 }, "hiddenSeries": false, "id": 18, "legend": { "avg": false, "current": true, "max": false, "min": false, "rightSide": false, "show": true, "sideWidth": 150, "total": false, "values": true }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pluginVersion": "6.6.2", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "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))", "format": "time_series", "instant": false, "interval": "", "legendFormat": "[95p] {{method}} /{{path}} ({{app_id}})", "refId": "A" }, { "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))", "format": "time_series", "instant": false, "interval": "", "legendFormat": "[75p] {{method}} /{{path}} ({{app_id}})", "refId": "B" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Actor Callback latency (Dapr -> App) (95p, 75p)", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "$$hashKey": "object:334", "decimals": 2, "format": "ms", "label": "", "logBase": 1, "max": null, "min": null, "show": true }, { "$$hashKey": "object:335", "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": false } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Dapr", "description": "This charts shows request rate (rps) when Dapr runtime calls User app HTTP endpoints.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 12, "y": 9 }, "hiddenSeries": false, "id": 32, "interval": "", "legend": { "alignAsTable": false, "avg": false, "current": false, "max": false, "min": false, "rightSide": false, "show": true, "sideWidth": 150, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pluginVersion": "6.6.2", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "sum by (app_id, method, path) (rate(dapr_http_client_completed_count{path=~\"actors/$dapr_actor_type/.*/method/.*\", kubernetes_namespace=\"$namespace\"}[5m]))", "format": "time_series", "instant": false, "interval": "", "legendFormat": "{{method}} {{path}} ({{app_id}})", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Actor Callback request rate (Dapr -> App) (RPS)", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "$$hashKey": "object:411", "decimals": 2, "format": "reqps", "label": "", "logBase": 1, "max": null, "min": null, "show": true }, { "$$hashKey": "object:412", "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": false } ], "yaxis": { "align": false, "alignLevel": null } } ], "title": "Throughput/latency", "type": "row" }, { "collapsed": false, "datasource": "Dapr", "gridPos": { "h": 1, "w": 24, "x": 0, "y": 1 }, "id": 71, "panels": [], "title": "Timer & Reminder", "type": "row" }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Dapr", "description": "Timer trigger status", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 2 }, "hiddenSeries": false, "id": 72, "legend": { "avg": false, "current": false, "max": false, "min": false, "rightSide": false, "show": true, "sideWidth": 150, "total": false, "values": false }, "lines": true, "linewidth": 2, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pluginVersion": "6.6.2", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "sum(rate(dapr_http_client_roundtrip_latency_bucket{path=~\"actors/$dapr_actor_type/.*/method/timer/.*\", kubernetes_namespace=\"$namespace\"}[5m])) by (path)", "format": "time_series", "instant": false, "interval": "", "legendFormat": "{{path}}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Actor timer trigger", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "$$hashKey": "object:1552", "decimals": 1, "format": "none", "label": "", "logBase": 1, "max": null, "min": null, "show": true }, { "$$hashKey": "object:1553", "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": false } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Dapr", "description": "Reminder trigger status", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 12, "y": 2 }, "hiddenSeries": false, "id": 73, "legend": { "avg": false, "current": false, "max": false, "min": false, "rightSide": false, "show": true, "sideWidth": 150, "total": false, "values": false }, "lines": true, "linewidth": 2, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pluginVersion": "6.6.2", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "sum(rate(dapr_http_client_roundtrip_latency_bucket{path=~\"actors/$dapr_actor_type/.*/method/remind/.*\", kubernetes_namespace=\"$namespace\"}[5m])) by (path)", "format": "time_series", "instant": false, "interval": "", "legendFormat": "{{path}}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Actor reminder trigger", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "$$hashKey": "object:1552", "decimals": 1, "format": "none", "label": "", "logBase": 1, "max": null, "min": null, "show": true }, { "$$hashKey": "object:1553", "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": false } ], "yaxis": { "align": false, "alignLevel": null } }, { "collapsed": false, "datasource": "Dapr", "gridPos": { "h": 1, "w": 24, "x": 0, "y": 10 }, "id": 69, "panels": [], "title": "Concurrency", "type": "row" }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Dapr", "description": "It contains the number of pending actor calls that are waiting to acquire the per-actor lock that enforces turn-based concurrency.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 11 }, "hiddenSeries": false, "id": 67, "legend": { "alignAsTable": false, "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "dapr_runtime_actor_pending_actor_calls{actor_type=~\"$dapr_actor_type\"}", "interval": "", "legendFormat": "{{actor_type}}.{{actor_id}}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Pending locks Per Actor", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "$$hashKey": "object:977", "format": "short", "label": "Pending Locks", "logBase": 1, "max": null, "min": null, "show": true }, { "$$hashKey": "object:978", "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } } ], "refresh": "10s", "schemaVersion": 22, "style": "dark", "tags": [ "autosetup" ], "templating": { "list": [ { "allValue": null, "current": {}, "datasource": "Dapr", "definition": "label_values(dapr_runtime_component_loaded,kubernetes_namespace)", "hide": 0, "includeAll": false, "index": -1, "label": "NAMESPACE", "multi": false, "name": "namespace", "options": [], "query": "label_values(dapr_runtime_component_loaded,kubernetes_namespace)", "refresh": 1, "regex": "", "skipUrlSync": false, "sort": 0, "tagValuesQuery": "", "tags": [], "tagsQuery": "", "type": "query", "useTags": false }, { "allValue": null, "current": {}, "datasource": "Dapr", "definition": "label_values(dapr_runtime_actor_pending_actor_calls,actor_type)", "hide": 0, "includeAll": false, "index": -1, "label": "ACTOR TYPE", "multi": true, "name": "dapr_actor_type", "options": [], "query": "label_values(dapr_runtime_actor_pending_actor_calls,actor_type)", "refresh": 1, "regex": "", "skipUrlSync": false, "sort": 1, "tagValuesQuery": "", "tags": [], "tagsQuery": "", "type": "query", "useTags": false } ] }, "time": { "from": "now-3h", "to": "now" }, "timepicker": { "refresh_intervals": [ "5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d" ] }, "timezone": "browser", "title": "Dapr Actor Dashboard", "variables": { "list": [] }, "version": 0 }, "overwrite": true } ================================================ FILE: setup/config/fluentd-config.yaml ================================================ apiVersion: v1 kind: ConfigMap metadata: name: fluentd-config namespace: kube-system data: fluent.conf: | @type null @type null @type null @type null @type tail path /var/log/containers/*.log pos_file fluentd-docker.pos time_format %Y-%m-%dT%H:%M:%S tag kubernetes.* @type multi_format format json time_key time time_type string time_format "%Y-%m-%dT%H:%M:%S.%NZ" keep_time_key false format regexp expression /^(? @type kubernetes_metadata @id filter_kube_metadata @type parser @type json format json time_key time time_type string time_format "%Y-%m-%dT%H:%M:%S.%NZ" keep_time_key false key_name log replace_invalid_sequence true emit_invalid_record_to_error true reserve_data true @type elasticsearch @id out_es @log_level info include_tag_key true host "#{ENV['FLUENT_ELASTICSEARCH_HOST']}" port "#{ENV['FLUENT_ELASTICSEARCH_PORT']}" path "#{ENV['FLUENT_ELASTICSEARCH_PATH']}" scheme "#{ENV['FLUENT_ELASTICSEARCH_SCHEME'] || 'http'}" ssl_verify "#{ENV['FLUENT_ELASTICSEARCH_SSL_VERIFY'] || 'true'}" ssl_version "#{ENV['FLUENT_ELASTICSEARCH_SSL_VERSION'] || 'TLSv1_2'}" user "#{ENV['FLUENT_ELASTICSEARCH_USER'] || use_default}" password "#{ENV['FLUENT_ELASTICSEARCH_PASSWORD'] || use_default}" reload_connections "#{ENV['FLUENT_ELASTICSEARCH_RELOAD_CONNECTIONS'] || 'false'}" reconnect_on_error "#{ENV['FLUENT_ELASTICSEARCH_RECONNECT_ON_ERROR'] || 'true'}" reload_on_failure "#{ENV['FLUENT_ELASTICSEARCH_RELOAD_ON_FAILURE'] || 'true'}" log_es_400_reason "#{ENV['FLUENT_ELASTICSEARCH_LOG_ES_400_REASON'] || 'false'}" logstash_prefix "#{ENV['FLUENT_ELASTICSEARCH_LOGSTASH_PREFIX'] || 'dapr'}" logstash_dateformat "#{ENV['FLUENT_ELASTICSEARCH_LOGSTASH_DATEFORMAT'] || '%Y.%m.%d'}" logstash_format "#{ENV['FLUENT_ELASTICSEARCH_LOGSTASH_FORMAT'] || 'true'}" index_name "#{ENV['FLUENT_ELASTICSEARCH_LOGSTASH_INDEX_NAME'] || 'dapr'}" type_name "#{ENV['FLUENT_ELASTICSEARCH_LOGSTASH_TYPE_NAME'] || 'fluentd'}" include_timestamp "#{ENV['FLUENT_ELASTICSEARCH_INCLUDE_TIMESTAMP'] || 'false'}" template_name "#{ENV['FLUENT_ELASTICSEARCH_TEMPLATE_NAME'] || use_nil}" template_file "#{ENV['FLUENT_ELASTICSEARCH_TEMPLATE_FILE'] || use_nil}" template_overwrite "#{ENV['FLUENT_ELASTICSEARCH_TEMPLATE_OVERWRITE'] || use_default}" sniffer_class_name "#{ENV['FLUENT_SNIFFER_CLASS_NAME'] || 'Fluent::Plugin::ElasticsearchSimpleSniffer'}" request_timeout "#{ENV['FLUENT_ELASTICSEARCH_REQUEST_TIMEOUT'] || '5s'}" flush_thread_count "#{ENV['FLUENT_ELASTICSEARCH_BUFFER_FLUSH_THREAD_COUNT'] || '8'}" flush_interval "#{ENV['FLUENT_ELASTICSEARCH_BUFFER_FLUSH_INTERVAL'] || '5s'}" chunk_limit_size "#{ENV['FLUENT_ELASTICSEARCH_BUFFER_CHUNK_LIMIT_SIZE'] || '2M'}" queue_limit_length "#{ENV['FLUENT_ELASTICSEARCH_BUFFER_QUEUE_LIMIT_LENGTH'] || '32'}" retry_max_interval "#{ENV['FLUENT_ELASTICSEARCH_BUFFER_RETRY_MAX_INTERVAL'] || '30'}" retry_forever true ================================================ FILE: setup/config/fluentd.yaml ================================================ --- apiVersion: v1 kind: ServiceAccount metadata: name: fluentd namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRole metadata: name: fluentd namespace: kube-system rules: - apiGroups: - "" resources: - pods - namespaces verbs: - get - list - watch --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: name: fluentd namespace: default roleRef: kind: ClusterRole name: fluentd apiGroup: rbac.authorization.k8s.io subjects: - kind: ServiceAccount name: fluentd namespace: kube-system --- apiVersion: apps/v1 kind: DaemonSet metadata: name: fluentd namespace: kube-system labels: k8s-app: fluentd-logging version: v1 spec: selector: matchLabels: k8s-app: fluentd-logging version: v1 template: metadata: labels: k8s-app: fluentd-logging version: v1 spec: serviceAccount: fluentd serviceAccountName: fluentd tolerations: - key: node-role.kubernetes.io/master effect: NoSchedule containers: - name: fluentd image: fluent/fluentd-kubernetes-daemonset:v1.9.2-debian-elasticsearch7-1.0 env: - name: FLUENT_ELASTICSEARCH_HOST value: "elasticsearch-master.dapr-monitoring" - name: FLUENT_ELASTICSEARCH_PORT value: "9200" - name: FLUENT_ELASTICSEARCH_SCHEME value: "http" - name: FLUENT_UID value: "0" resources: limits: memory: 200Mi requests: cpu: 100m memory: 200Mi volumeMounts: - name: varlog mountPath: /var/log - name: varlibdockercontainers mountPath: /var/lib/docker/containers readOnly: true - name: fluentd-config mountPath: /fluentd/etc terminationGracePeriodSeconds: 30 volumes: - name: varlog hostPath: path: /var/log - name: varlibdockercontainers hostPath: path: /var/lib/docker/containers - name: fluentd-config configMap: name: fluentd-config ================================================ FILE: setup/config/ingress-annotations.yaml ================================================ controller: podAnnotations: dapr.io/enabled: "true" dapr.io/app-id: "nginx-ingress" dapr.io/app-protocol: "http" dapr.io/app-port: "80" dapr.io/api-token-secret: "dapr-api-token" dapr.io/config: "ingress-config" dapr.io/log-as-json: "true" ================================================ FILE: setup/config/ingress-config.yaml ================================================ --- apiVersion: dapr.io/v1alpha1 kind: Configuration metadata: name: ingress-config spec: tracing: samplingRate: "1" secrets: scopes: - storeName: kubernetes defaultAccess: deny allowedSecrets: ["dapr-api-token"] --- apiVersion: dapr.io/v1alpha1 kind: Configuration metadata: name: tracing spec: tracing: samplingRate: "1" ================================================ FILE: setup/config/ingress-template.yaml ================================================ apiVersion: extensions/v1beta1 kind: Ingress metadata: name: ingress-rules annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/rewrite-target: / spec: tls: - hosts: - '*.DOMAINNAME' - DOMAINNAME secretName: tls-secret rules: - host: api.DOMAINNAME http: paths: - path: / backend: serviceName: nginx-ingress-dapr servicePort: 80 ================================================ FILE: setup/config/namespace-template.yml ================================================ apiVersion: v1 kind: Namespace metadata: name: NSNAME --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: secret-reader namespace: NSNAME rules: - apiGroups: [""] resources: ["secrets"] verbs: ["get"] --- kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: dapr-secret-reader namespace: NSNAME subjects: - kind: ServiceAccount name: default roleRef: kind: Role name: secret-reader apiGroup: rbac.authorization.k8s.io --- apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: zipkin namespace: NSNAME spec: type: exporters.zipkin metadata: - name: enabled value: "true" - name: exporterAddress value: "http://zipkin.dapr-monitoring.svc.cluster.local:9411/api/v2/spans" ================================================ FILE: setup/config/sidecar-dashboard.json ================================================ { "dashboard": { "__inputs": [ { "name": "DS_DAPR", "label": "Dapr", "description": "", "type": "datasource", "pluginId": "prometheus", "pluginName": "Prometheus" } ], "__requires": [ { "type": "panel", "id": "bargauge", "name": "Bar Gauge", "version": "" }, { "type": "grafana", "id": "grafana", "name": "Grafana", "version": "6.7.3" }, { "type": "panel", "id": "graph", "name": "Graph", "version": "" }, { "type": "datasource", "id": "prometheus", "name": "Prometheus", "version": "1.0.0" }, { "type": "panel", "id": "stat", "name": "Stat", "version": "" } ], "annotations": { "list": [ { "$$hashKey": "object:356", "builtIn": 1, "datasource": "Dapr", "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "limit": 100, "name": "Annotations & Alerts", "showIn": 0, "type": "dashboard" } ] }, "editable": true, "gnetId": null, "graphTooltip": 0, "id": null, "iteration": 1591551058714, "links": [], "panels": [ { "collapsed": false, "datasource": "Dapr", "gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 }, "id": 16, "panels": [], "title": "Health monitor", "type": "row" }, { "cacheTimeout": null, "datasource": "Dapr", "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.", "gridPos": { "h": 4, "w": 12, "x": 0, "y": 1 }, "id": 70, "links": [], "options": { "colorMode": "value", "fieldOptions": { "calcs": [ "last" ], "defaults": { "decimals": 2, "mappings": [ { "id": 0, "op": "=", "text": "N/A", "type": 1, "value": "null" } ], "nullValueMode": "connected", "thresholds": { "mode": "absolute", "steps": [ { "color": "red", "value": null }, { "color": "green", "value": 600 } ] }, "title": "", "unit": "s" }, "overrides": [], "values": false }, "graphMode": "area", "justifyMode": "auto", "orientation": "vertical" }, "pluginVersion": "6.7.3", "targets": [ { "expr": "time() - max(process_start_time_seconds{kubernetes_name=~\"($dapr_app_id)-dapr\", kubernetes_namespace=\"$namespace\"}) by (kubernetes_name)", "legendFormat": "{{kubernetes_name}}", "refId": "A" } ], "timeFrom": "5m", "timeShift": null, "title": "Sidecar uptime", "type": "stat" }, { "collapsed": false, "datasource": "Dapr", "gridPos": { "h": 1, "w": 24, "x": 0, "y": 5 }, "id": 72, "panels": [], "title": "Resource usage", "type": "row" }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Dapr", "description": "This shows total amount of Kernel and user CPU usage for Daprd.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 6, "x": 0, "y": 6 }, "hiddenSeries": false, "id": 9, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "sum(rate(container_cpu_usage_seconds_total{pod_name=~\"($dapr_app_id).*\", namespace=\"$namespace\"}[5m])) by (pod_name)", "legendFormat": "{{pod_name}}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Total CPU usage (Kernel and User)", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "s", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Dapr", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 6, "x": 6, "y": 6 }, "hiddenSeries": false, "id": 11, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "sum(go_goroutines{kubernetes_name=~\"($dapr_app_id)-dapr.*\", kubernetes_namespace=\"$namespace\"}) by (kubernetes_name)", "legendFormat": "{{kubernetes_name}}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Number of GO routines", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Dapr", "description": "The amount of memory that belongs specifically to that process in bytes. This excludes swapped out memory pages.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 6, "x": 12, "y": 6 }, "hiddenSeries": false, "id": 10, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "sum(process_resident_memory_bytes{kubernetes_name=~\"($dapr_app_id)-dapr\", kubernetes_namespace=\"$namespace\"}) by (kubernetes_name)", "legendFormat": "{{kubernetes_name}}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Memory usage in bytes", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "decbytes", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Dapr", "description": "The amount of address space that a process is managing. This includes all types of memory, both in RAM and swapped out.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 6, "x": 18, "y": 6 }, "hiddenSeries": false, "id": 12, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "sum(process_virtual_memory_bytes{kubernetes_name=~\"($dapr_app_id)-dapr\", kubernetes_namespace=\"$namespace\"}) by (kubernetes_name)", "legendFormat": "{{kubernetes_name}}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Managed virtual memory in bytes", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "decimals": 1, "format": "decbytes", "label": "", "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": false } ], "yaxis": { "align": false, "alignLevel": null } }, { "collapsed": true, "datasource": "Dapr", "gridPos": { "h": 1, "w": 24, "x": 0, "y": 14 }, "id": 21, "panels": [ { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Dapr", "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.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 7 }, "hiddenSeries": false, "id": 19, "legend": { "alignAsTable": false, "avg": false, "current": true, "max": false, "min": false, "rightSide": false, "show": true, "sideWidth": 150, "total": false, "values": true }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pluginVersion": "6.6.2", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "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))", "format": "time_series", "instant": false, "legendFormat": "[95p] {{method}} {{path}} ({{app_id}})", "refId": "A" }, { "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))", "format": "time_series", "instant": false, "legendFormat": "[75p] {{method}} {{path}} ({{app_id}})", "refId": "B" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Outbound request latency (App->Dapr) (95p, 75p)", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "decimals": 2, "format": "ms", "label": "", "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": false } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Dapr", "description": "This charts shows completed request rate (rps) when User app calls Dapr.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 12, "y": 7 }, "hiddenSeries": false, "id": 31, "legend": { "alignAsTable": false, "avg": false, "current": false, "max": false, "min": false, "rightSide": false, "show": true, "sideWidth": 150, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pluginVersion": "6.6.2", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "sum by (app_id, method, path) (rate(dapr_http_server_response_count{app_id=~\"$dapr_app_id\", kubernetes_namespace=\"$namespace\"}[5m]))", "format": "time_series", "instant": false, "legendFormat": "{{method}} {{path}} ({{app_id}})", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Outbound request rate (App -> Dapr) (RPS)", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "decimals": 2, "format": "reqps", "label": "", "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": false } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Dapr", "description": "This charts shows the inbound request latency from Dapr to App. You can analyze App's API endpoint performance.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 15 }, "hiddenSeries": false, "id": 18, "legend": { "avg": false, "current": true, "max": false, "min": false, "rightSide": false, "show": true, "sideWidth": 150, "total": false, "values": true }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pluginVersion": "6.6.2", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "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))", "format": "time_series", "instant": false, "legendFormat": "[95p] {{method}} /{{path}} ({{app_id}})", "refId": "A" }, { "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))", "format": "time_series", "instant": false, "legendFormat": "[75p] {{method}} /{{path}} ({{app_id}})", "refId": "B" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Inbound request latency (Dapr -> App) (95p, 75p)", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "decimals": 2, "format": "ms", "label": "", "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": false } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Dapr", "description": "This charts shows request rate (rps) when Dapr runtime calls User app HTTP endpoints.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 12, "y": 15 }, "hiddenSeries": false, "id": 32, "legend": { "alignAsTable": false, "avg": false, "current": false, "max": false, "min": false, "rightSide": false, "show": true, "sideWidth": 150, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pluginVersion": "6.6.2", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "sum by (app_id, method, path) (rate(dapr_http_client_completed_count{app_id=~\"$dapr_app_id\", kubernetes_namespace=\"$namespace\"}[5m]))", "format": "time_series", "instant": false, "legendFormat": "{{method}} {{path}} ({{app_id}})", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Inbound request rate (Dapr -> App) (RPS)", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "decimals": 2, "format": "reqps", "label": "", "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": false } ], "yaxis": { "align": false, "alignLevel": null } } ], "title": "Throughput/Latency - HTTP", "type": "row" }, { "collapsed": true, "datasource": "Dapr", "gridPos": { "h": 1, "w": 24, "x": 0, "y": 15 }, "id": 34, "panels": [ { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Dapr", "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.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 8 }, "hiddenSeries": false, "id": 36, "legend": { "avg": false, "current": true, "max": false, "min": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "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))", "legendFormat": "[95p] - {{grpc_server_method}} ({{app_id}})", "refId": "A" }, { "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))", "legendFormat": "[75p] {{grpc_server_method}} ({{app_id}})", "refId": "B" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Outbound request latency (App->Dapr) (95p, 75p)", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "ms", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Dapr", "description": "This charts shows completed request rate (rps) when User app calls Dapr.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 12, "y": 8 }, "hiddenSeries": false, "id": 38, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": true, "steppedLine": false, "targets": [ { "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]))", "legendFormat": "{{grpc_server_status}} {{grpc_server_method}} ({{app_id}})", "refId": "B" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Outbound request rate (App -> Dapr) (RPS)", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "reqps", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Dapr", "description": "This charts shows the inbound request latency from Dapr to App. You can analyze App's API endpoint performance.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 16 }, "hiddenSeries": false, "id": 40, "legend": { "avg": false, "current": true, "max": false, "min": false, "show": true, "total": false, "values": true }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "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))", "legendFormat": "[95p] {{grpc_client_method}} ({{app_id}})", "refId": "A" }, { "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))", "legendFormat": "[75p] {{grpc_client_method}} ({{app_id}})", "refId": "B" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Inbound request latency (Dapr -> App) (95p, 75p)", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "ms", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Dapr", "description": "This charts shows request rate (rps) when Dapr runtime calls User app gRPC endpoints.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 12, "y": 16 }, "hiddenSeries": false, "id": 42, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": true, "steppedLine": false, "targets": [ { "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]))", "legendFormat": "{{grpc_client_status}} {{grpc_client_method}} ({{app_id}})", "refId": "B" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Inbound request rate (Dapr -> App) (RPS)", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "reqps", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } } ], "title": "Throughput/Latency - gRPC", "type": "row" }, { "collapsed": true, "datasource": "Dapr", "gridPos": { "h": 1, "w": 24, "x": 0, "y": 16 }, "id": 52, "panels": [ { "datasource": "Dapr", "description": "This shows the number of configured components. For example, it will shows \"2\" if you configure redis statestore and redis pubsub components. ", "gridPos": { "h": 8, "w": 6, "x": 0, "y": 9 }, "id": 56, "options": { "colorMode": "value", "fieldOptions": { "calcs": [ "last" ], "defaults": { "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "red", "value": 80 } ] } }, "overrides": [], "values": false }, "graphMode": "area", "justifyMode": "auto", "orientation": "horizontal" }, "pluginVersion": "6.6.2", "targets": [ { "expr": "sum(dapr_runtime_component_loaded{app_id=~\"$dapr_app_id\", kubernetes_namespace=\"$namespace\"}) by (app_id)", "legendFormat": "{{app_id}}", "refId": "A" } ], "timeFrom": null, "timeShift": null, "title": "Loaded component configurations", "type": "stat" }, { "cacheTimeout": null, "datasource": "Dapr", "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.", "gridPos": { "h": 8, "w": 6, "x": 6, "y": 9 }, "id": 54, "links": [], "options": { "colorMode": "value", "fieldOptions": { "calcs": [ "last" ], "defaults": { "mappings": [ { "id": 0, "op": "=", "text": "N/A", "type": 1, "value": "null" } ], "nullValueMode": "connected", "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "red", "value": 80 } ] }, "unit": "none" }, "overrides": [], "values": false }, "graphMode": "area", "justifyMode": "auto", "orientation": "horizontal" }, "pluginVersion": "6.6.2", "targets": [ { "expr": "sum(dapr_runtime_component_init_total{app_id=~\"$dapr_app_id\", kubernetes_namespace=\"$namespace\"}) by (app_id)", "legendFormat": "{{app_id}}", "refId": "A" } ], "timeFrom": null, "timeShift": null, "title": "Initialized components", "type": "stat" }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Dapr", "description": "This chart shows the number component initialization failures with the reasons.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 12, "y": 9 }, "hiddenSeries": false, "id": 58, "legend": { "avg": false, "current": false, "max": false, "min": false, "rightSide": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "sum(dapr_runtime_component_init_fail_total{app_id=~\"$dapr_app_id\", kubernetes_namespace=\"$namespace\"}) by (app_id, reason)", "legendFormat": "{{app_id}} - {{reason}}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Component failures", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": false } ], "yaxis": { "align": false, "alignLevel": null } } ], "title": "Components", "type": "row" }, { "collapsed": true, "datasource": "Dapr", "gridPos": { "h": 1, "w": 24, "x": 0, "y": 17 }, "id": 44, "panels": [ { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Dapr", "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.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 7, "w": 9, "x": 0, "y": 18 }, "hiddenSeries": false, "id": 46, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": true, "targets": [ { "expr": "rate(dapr_runtime_actor_status_report_total{app_id=~\"$dapr_app_id\", kubernetes_namespace=\"$namespace\"}[5m])", "legendFormat": "OK ({{app_id}})", "refId": "A" }, { "expr": "rate(dapr_runtime_actor_status_report_fail_total{app_id=~\"$dapr_app_id\", kubernetes_namespace=\"$namespace\"}[5m])", "legendFormat": "Error ({{app_id}})", "refId": "B" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Status report status", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "none", "label": null, "logBase": 1, "max": null, "min": null, "show": false }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": false } ], "yaxis": { "align": false, "alignLevel": null } }, { "cacheTimeout": null, "datasource": "Dapr", "description": "This shows the failures when Daprd reports host status to placement.", "gridPos": { "h": 7, "w": 5, "x": 9, "y": 18 }, "id": 59, "links": [], "options": { "displayMode": "basic", "fieldOptions": { "calcs": [ "max" ], "defaults": { "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "red", "value": 10 } ] } }, "overrides": [], "values": false }, "orientation": "horizontal", "showUnfilled": true }, "pluginVersion": "6.7.3", "targets": [ { "expr": "sum(dapr_runtime_actor_status_report_fail_total{app_id=~\"$dapr_app_id\", kubernetes_namespace=\"$namespace\"}) by (app_id, operation)", "legendFormat": "{{operation}} ({{app_id}})", "refId": "A" } ], "timeFrom": null, "timeShift": null, "title": "Report status error reasons", "type": "bargauge" }, { "aliasColors": {}, "bars": true, "dashLength": 10, "dashes": false, "datasource": "Dapr", "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.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 7, "w": 10, "x": 14, "y": 18 }, "hiddenSeries": false, "id": 61, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": false, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 1, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": true, "steppedLine": false, "targets": [ { "expr": "sum(dapr_runtime_actor_table_operation_recv_total{app_id=~\"$dapr_app_id\", kubernetes_namespace=\"$namespace\"}) by (app_id, operation)", "format": "time_series", "intervalFactor": 3, "legendFormat": "{{operation}} ({{app_id}})", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Placement table operation", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": true, "dashLength": 10, "dashes": false, "datasource": "Dapr", "description": "This chart shows how many actors are deactivated.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 7, "w": 9, "x": 0, "y": 25 }, "hiddenSeries": false, "id": 65, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": false, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 1, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": true, "steppedLine": false, "targets": [ { "expr": "dapr_runtime_actor_deactivated_total{app_id=~\"$dapr_app_id\", kubernetes_namespace=\"$namespace\"}", "legendFormat": "OK {{actor_type}}", "refId": "A" }, { "expr": "dapr_runtime_actor_deactivated_failed_total{app_id=~\"$dapr_app_id\", kubernetes_namespace=\"$namespace\"}", "legendFormat": "Error {{actor_type}}", "refId": "C" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Actor Deactivation", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": true, "dashLength": 10, "dashes": false, "datasource": "Dapr", "description": "This shows when actor is rebalanced. This operation can make the activated actors deactivated and move actor to the other hosts.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 7, "w": 7, "x": 9, "y": 25 }, "hiddenSeries": false, "id": 63, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": false, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 1, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": true, "steppedLine": false, "targets": [ { "expr": "dapr_runtime_actor_rebalanced_total{app_id=~\"$dapr_app_id\", kubernetes_namespace=\"$namespace\"}", "intervalFactor": 1, "legendFormat": "{{app_id}}", "refId": "B" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Actor table rebalancing", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } } ], "title": "Actor", "type": "row" }, { "collapsed": true, "datasource": "Dapr", "gridPos": { "h": 1, "w": 24, "x": 0, "y": 18 }, "id": 50, "panels": [ { "aliasColors": {}, "bars": true, "dashLength": 10, "dashes": false, "datasource": "Dapr", "description": "This chart shows when mTLS is initialized, which means root cert is loaded and workload cert is issued from sentry, when runtime starts.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 7, "x": 0, "y": 11 }, "hiddenSeries": false, "id": 48, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": false, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "rate(dapr_runtime_mtls_init_total{app_id=~\"$dapr_app_id\", kubernetes_namespace=\"$namespace\"}[5m])", "intervalFactor": 2, "legendFormat": "OK {{app_id}}", "refId": "A" }, { "expr": "rate(dapr_runtime_mtls_init_fail_total{app_id=~\"$dapr_app_id\", kubernetes_namespace=\"$namespace\"}[5m])", "intervalFactor": 2, "legendFormat": "Error {{app_id}}", "refId": "B" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "mTLS initialization status", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": false }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": false } ], "yaxis": { "align": false, "alignLevel": null } }, { "cacheTimeout": null, "datasource": "Dapr", "gridPos": { "h": 8, "w": 5, "x": 7, "y": 11 }, "id": 67, "links": [], "options": { "displayMode": "basic", "fieldOptions": { "calcs": [ "mean" ], "defaults": { "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "red", "value": 10 } ] } }, "overrides": [], "values": false }, "orientation": "horizontal", "showUnfilled": true }, "pluginVersion": "6.6.2", "targets": [ { "expr": "sum(dapr_runtime_mtls_init_fail_total{app_id=~\"$dapr_app_id\", kubernetes_namespace=\"$namespace\"}) by (app_id, reason)", "legendFormat": "{{reason}} ({{app_id}})", "refId": "A" } ], "timeFrom": null, "timeShift": null, "title": "mTLS Initialization Errors", "type": "bargauge" }, { "aliasColors": {}, "bars": true, "dashLength": 10, "dashes": false, "datasource": "Dapr", "description": "This chart shows when workload certificate is rotated.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 7, "x": 12, "y": 11 }, "hiddenSeries": false, "id": 66, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": false, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pluginVersion": "6.6.2", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "rate(dapr_runtime_mtls_workload_cert_rotated_total{app_id=~\"$dapr_app_id\", kubernetes_namespace=\"$namespace\"}[5m])", "format": "time_series", "instant": false, "intervalFactor": 2, "legendFormat": "OK {{app_id}}", "refId": "A" }, { "expr": "rate(dapr_runtime_mtls_workload_cert_rotated_fail_total{app_id=~\"$dapr_app_id\", kubernetes_namespace=\"$namespace\"}[5m])", "intervalFactor": 2, "legendFormat": "Error {{app_id}}", "refId": "B" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Workload certificate rotation status", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": false }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": false } ], "yaxis": { "align": false, "alignLevel": null } }, { "cacheTimeout": null, "datasource": "Dapr", "gridPos": { "h": 8, "w": 5, "x": 19, "y": 11 }, "id": 68, "links": [], "options": { "displayMode": "basic", "fieldOptions": { "calcs": [ "mean" ], "defaults": { "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "red", "value": 10 } ] } }, "overrides": [], "values": false }, "orientation": "horizontal", "showUnfilled": true }, "pluginVersion": "6.6.2", "targets": [ { "expr": "sum(dapr_runtime_mtls_workload_cert_rotated_fail_total{app_id=~\"$dapr_app_id\", kubernetes_namespace=\"$namespace\"}) by (app_id, reason)", "legendFormat": "{{reason}} ({{app_id}})", "refId": "A" } ], "timeFrom": null, "timeShift": null, "title": "Cert rotation error reasons", "type": "bargauge" } ], "title": "Security", "type": "row" } ], "refresh": "10s", "schemaVersion": 22, "style": "dark", "tags": [ "autosetup" ], "templating": { "list": [ { "allValue": null, "current": {}, "datasource": "Dapr", "definition": "label_values(dapr_runtime_component_loaded,kubernetes_namespace)", "hide": 0, "includeAll": false, "index": -1, "label": "NAMESPACE", "multi": false, "name": "namespace", "options": [], "query": "label_values(dapr_runtime_component_loaded,kubernetes_namespace)", "refresh": 1, "regex": "", "skipUrlSync": false, "sort": 0, "tagValuesQuery": "", "tags": [], "tagsQuery": "", "type": "query", "useTags": false }, { "allValue": null, "current": {}, "datasource": "Dapr", "definition": "label_values(dapr_runtime_component_loaded,app_id)", "hide": 0, "includeAll": false, "index": -1, "label": "APPID", "multi": true, "name": "dapr_app_id", "options": [], "query": "label_values(dapr_runtime_component_loaded,app_id)", "refresh": 1, "regex": "", "skipUrlSync": false, "sort": 1, "tagValuesQuery": "", "tags": [], "tagsQuery": "", "type": "query", "useTags": false } ] }, "time": { "from": "now-3h", "to": "now" }, "timepicker": { "refresh_intervals": [ "5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d" ] }, "timezone": "browser", "title": "Dapr Sidecar Dashboard", "variables": { "list": [] }, "version": 0 }, "overwrite": true } ================================================ FILE: setup/config/system-services-dashboard.json ================================================ { "dashboard": { "annotations": { "list": [ { "builtIn": 1, "datasource": "-- Grafana --", "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations & Alerts", "type": "dashboard" } ] }, "editable": true, "gnetId": null, "graphTooltip": 0, "id": null, "links": [], "panels": [ { "collapsed": false, "datasource": null, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 }, "id": 18, "panels": [], "title": "Health & Resource", "type": "row" }, { "cacheTimeout": null, "datasource": "Dapr", "gridPos": { "h": 8, "w": 4, "x": 0, "y": 1 }, "id": 20, "links": [], "options": { "colorMode": "value", "fieldOptions": { "calcs": [ "last" ], "defaults": { "decimals": 1, "mappings": [ { "id": 0, "op": "=", "text": "N/A", "type": 1, "value": "null" } ], "nullValueMode": "connected", "thresholds": { "mode": "absolute", "steps": [ { "color": "red", "value": null }, { "color": "green", "value": 600 } ] }, "unit": "s" }, "overrides": [], "values": false }, "graphMode": "area", "justifyMode": "auto", "orientation": "horizontal" }, "pluginVersion": "6.6.2", "targets": [ { "expr": "time() - max(process_start_time_seconds{app=~\"dapr-sentry|dapr-placement|dapr-sidecar-injector|dapr-operator\"}) by (app)", "intervalFactor": 2, "legendFormat": "{{app}}", "refId": "A" } ], "timeFrom": null, "timeShift": null, "title": "Uptime", "type": "stat" }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Dapr", "description": "This shows total amount of kernel and user CPU usage time.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 7, "x": 4, "y": 1 }, "hiddenSeries": false, "id": 22, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "sum(rate(container_cpu_usage_seconds_total{pod=~\"(dapr-sentry|dapr-sidecar-injector|dapr-placement|dapr-operator).*\"}[5m])) by (pod)", "legendFormat": "{{pod}}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Total CPU usage (kernel and user)", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "s", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Dapr", "description": "The amount of memory that belongs specifically to that process in bytes. This excludes swapped out memory pages.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 7, "x": 11, "y": 1 }, "hiddenSeries": false, "id": 24, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "max(process_resident_memory_bytes{app=~\"(dapr-sentry|dapr-sidecar-injector|dapr-placement|dapr-operator)\"}) by (app)", "legendFormat": "{{app}}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Memory usage in bytes", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "decbytes", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Dapr", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 6, "x": 18, "y": 1 }, "hiddenSeries": false, "id": 26, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "max(go_goroutines{app=~\"(dapr-sentry|dapr-sidecar-injector|dapr-placement|dapr-operator)\"}) by (app)", "legendFormat": "{{app}}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Number of GO routines", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "collapsed": false, "datasource": null, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 9 }, "id": 14, "panels": [], "title": "Operator", "type": "row" }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "description": "The total number of services created.", "fill": 10, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 10 }, "hiddenSeries": false, "id": 6, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": true, "steppedLine": true, "targets": [ { "expr": "count(dapr_operator_service_created_total) by (app_id)", "legendFormat": "{{app_id}}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "# Services Created", "tooltip": { "shared": true, "sort": 1, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": false } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Dapr", "description": "The total number of services deleted.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 12, "y": 10 }, "hiddenSeries": false, "id": 4, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "dapr_operator_service_deleted_total", "legendFormat": "{{app_id}}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "# Services Deleted", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "collapsed": false, "datasource": null, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 18 }, "id": 12, "panels": [], "title": "Sidecar Injector", "type": "row" }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "description": "The total number of sidecar injection requests.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 9, "w": 12, "x": 0, "y": 19 }, "hiddenSeries": false, "id": 8, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "dapr_injector_sidecar_injection_requests_total", "legendFormat": "sidecars requests", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "# sidecar injection requests", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Dapr", "description": "The total number of successful sidecar injection requests.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 9, "w": 12, "x": 12, "y": 19 }, "hiddenSeries": false, "id": 10, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pluginVersion": "6.6.2", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "dapr_injector_sidecar_injection_succeeded_total", "legendFormat": "{{app_id}}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "# successful sidecar injected", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "collapsed": false, "datasource": null, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 28 }, "id": 42, "panels": [], "title": "CA Sentry", "type": "row" }, { "cacheTimeout": null, "colorBackground": false, "colorValue": true, "colors": [ "#F2495C", "#FADE2A", "#73BF69" ], "datasource": null, "decimals": null, "description": "", "format": "s", "gauge": { "maxValue": 100, "minValue": 0, "show": false, "thresholdLabels": false, "thresholdMarkers": true }, "gridPos": { "h": 7, "w": 3, "x": 0, "y": 29 }, "id": 44, "interval": null, "links": [], "mappingType": 1, "mappingTypes": [ { "name": "value to text", "value": 1 }, { "name": "range to text", "value": 2 } ], "maxDataPoints": 100, "nullPointMode": "connected", "nullText": null, "options": {}, "pluginVersion": "6.6.2", "postfix": " left", "postfixFontSize": "50%", "prefix": "", "prefixFontSize": "50%", "rangeMaps": [ { "from": "null", "text": "N/A", "to": "null" } ], "sparkline": { "fillColor": "rgba(31, 118, 189, 0.18)", "full": false, "lineColor": "rgb(31, 120, 193)", "show": true, "ymax": null, "ymin": null }, "tableColumn": "", "targets": [ { "expr": "min(dapr_sentry_issuercert_expiry_timestamp) - time()", "refId": "A" } ], "thresholds": "2628000, 5256000", "timeFrom": "1m", "timeShift": null, "title": "Root/Issuer cert expiry", "type": "singlestat", "valueFontSize": "80%", "valueMaps": [ { "op": "=", "text": "N/A", "value": "null" } ], "valueName": "current" }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "description": "Certificate Signing Request ( CSR ) from Dapr runtime", "fill": 0, "fillGradient": 0, "gridPos": { "h": 7, "w": 9, "x": 3, "y": 29 }, "hiddenSeries": false, "id": 34, "legend": { "alignAsTable": true, "avg": false, "current": false, "max": false, "min": false, "rightSide": true, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 2, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [ { "alias": "CSR Requests", "color": "rgb(60, 33, 166)", "dashes": true }, { "alias": "CSR Success", "color": "#73BF69" }, { "alias": "CSR Failure", "color": "#F2495C" } ], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "sum(dapr_sentry_cert_sign_request_received_total{app=\"dapr-sentry\"})", "legendFormat": "CSR Requests", "refId": "A" }, { "expr": "sum(dapr_sentry_cert_sign_success_total{app=\"dapr-sentry\"})", "instant": false, "legendFormat": "CSR Success", "refId": "B" }, { "expr": "sum(dapr_sentry_cert_sign_failure_total{app=\"dapr-sentry\"})", "instant": false, "legendFormat": "CSR Failure", "refId": "C" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Certificate Signing Requests (CSR) from Daprd", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": false } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "description": "This chart shows the failure reason of Certificate Sign Request.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 7, "w": 12, "x": 12, "y": 29 }, "hiddenSeries": false, "id": 38, "legend": { "alignAsTable": false, "avg": false, "current": false, "max": false, "min": false, "rightSide": true, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "sum(dapr_sentry_cert_sign_failure_total{app=\"dapr-sentry\"}) by (reason)", "legendFormat": "{{reason}}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "CSR Failures", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": false } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "cacheTimeout": null, "dashLength": 10, "dashes": false, "datasource": "Dapr", "description": "This will be counted when issuer cert and key are changed.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 7, "w": 12, "x": 0, "y": 36 }, "hiddenSeries": false, "id": 36, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": false, "total": false, "values": false }, "lines": true, "linewidth": 1, "links": [], "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pluginVersion": "6.6.2", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "sum(dapr_sentry_issuercert_changed_total{app=\"dapr-sentry\"})", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Issuer cert and key changed total", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "none", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "description": "This chart shows the reason of gRPC server TLS certificate issuance failures.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 7, "w": 12, "x": 12, "y": 36 }, "hiddenSeries": false, "id": 40, "legend": { "alignAsTable": false, "avg": false, "current": false, "max": false, "min": false, "rightSide": true, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "sum(dapr_sentry_servercert_issue_failed_total{app=\"dapr-sentry\"}) by (reason)", "legendFormat": "{{reason}}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Server TLS certificate issuance failures", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": false } ], "yaxis": { "align": false, "alignLevel": null } }, { "collapsed": false, "datasource": null, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 43 }, "id": 16, "panels": [], "title": "Placement", "type": "row" }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "description": "The total number of replicas connected to placement service.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 44 }, "hiddenSeries": false, "id": 28, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "dapr_placement_hosts_total", "legendFormat": "hosts", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "# total replicas", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "description": "The total number of replicas which are not hosting actors.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 12, "y": 44 }, "hiddenSeries": false, "id": 30, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "dapr_placement_nonactorhosts_total", "legendFormat": "{{kubernetes_pod_name}}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "# replicas not hosting actors", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": null, "description": "The total number of actor types registered with Dapr runtime.", "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 52 }, "hiddenSeries": false, "id": 32, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "dataLinks": [] }, "percentage": false, "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "dapr_placement_actortypes_total", "legendFormat": "actor types", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "# actor types", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } } ], "refresh": "15m", "schemaVersion": 22, "style": "dark", "tags": [ "autosetup" ], "templating": { "list": [] }, "time": { "from": "now-1h", "to": "now" }, "timepicker": { "refresh_intervals": [ "5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d" ] }, "timezone": "browser", "title": "Dapr System Services Dashboard", "version": 0 }, "overwrite": true } ================================================ FILE: setup/config/zipkin.yaml ================================================ apiVersion: apps/v1 kind: Deployment metadata: name: zipkin namespace: dapr-monitoring labels: app: zipkin spec: replicas: 1 selector: matchLabels: app: zipkin template: metadata: labels: app: zipkin spec: containers: - name: zipkin image: openzipkin/zipkin ports: - containerPort: 9411 --- kind: Service apiVersion: v1 metadata: name: zipkin namespace: dapr-monitoring labels: app: zipkin spec: selector: app: zipkin ports: - protocol: TCP port: 9411 targetPort: 9411 type: ClusterIP ================================================ FILE: setup/gke/Makefile ================================================ CLUSTER_NAME ?=demo CLUSTER_ZONE ?=us-west1-a CLUSTER_VERSION ?=1.17.13-gke.600 NODE_COUNT ?=2 NODE_TYPE ?=n2-standard-4 .PHONY: all all: @echo === ACTIVE CONFIGURATION === @echo "CLUSTER_NAME: ${CLUSTER_NAME}" @echo "CLUSTER_ZONE: ${CLUSTER_ZONE}" @echo "CLUSTER_VERSION: ${CLUSTER_VERSION}" @echo "NODE_COUNT: ${NODE_COUNT}" @echo "NODE_TYPE: ${NODE_TYPE}" @echo @echo "Export these as environment variables to change their values" @echo .PHONY: setup setup: ## Enables GKE APIs gcloud services enable \ cloudapis.googleapis.com \ container.googleapis.com \ containerregistry.googleapis.com .PHONY: cluster-list cluster-list: ## List GKE clusters gcloud container clusters list .PHONY: version-list version-list: ## List Kubernetes versions supported in GKE gcloud container get-server-config .PHONY: cluster cluster: setup ## Create GKE cluster # Create cluster gcloud container clusters create $(CLUSTER_NAME) \ --addons=HorizontalPodAutoscaling,HttpLoadBalancing \ --zone $(CLUSTER_ZONE) \ --node-locations $(CLUSTER_ZONE) \ --cluster-version $(CLUSTER_VERSION) \ --machine-type $(NODE_TYPE) \ --num-nodes $(NODE_COUNT) \ --enable-ip-alias \ --enable-stackdriver-kubernetes \ --enable-autorepair \ --scopes cloud-platform # Get cluster credentials gcloud container clusters get-credentials $(CLUSTER_NAME) \ --zone $(CLUSTER_ZONE) # Create cluster binding for current user kubectl create clusterrolebinding cluster-admin-binding \ --clusterrole=cluster-admin \ --user=$(gcloud config get-value core/account) .PHONY: cluster-down cluster-down: cluster-list ## Delete previously created GKE cluster (make clusterdown CLUSTER_NAME=demo) gcloud container clusters delete $(CLUSTER_NAME) \ --zone $(CLUSTER_ZONE) .PHONY: help help: ## Display available commands @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \ 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ================================================ FILE: setup/gke/README.md ================================================ # GKE Cluster Setup The following parameters can be used to configure your deployment. Define these as environment variables to set or override the default value: ```shell CLUSTER_NAME # default: demo CLUSTER_ZONE # default: us-west1-a CLUSTER_VERSION # default: 1.17.13-gke.600 NODE_COUNT # default: 2 NODE_COUNT_MIN # default: 1 NODE_COUNT_MAX # default: 5 NODE_TYPE # default: n2-standard-4 ``` ```shell gcloud config set project gcloud config set compute/region gcloud config set compute/zone ``` ## Usage Start by navigating to the [setup/gke](./setup/gke) directory > Run `make` by itself to see the active configuration * `make cluster` to create a cluster on GKE (make cluster CLUSTER_NAME=demo) * `make cluster-list` to list your AKS clusters * `make version-list` to list Kubernetes versions supported on AKS Once cluster is created, you can follow [these instructions](../) to configure Dapr. ## Cleanup To lists previously created clusters run ```shell make cluster-list ``` To delete any of the previously created clusters run > yes, there will be a prompt to confirm before deleting ```shell make cluster-down CLUSTER_NAME=name ``` ## Help To find the list of all the commands with their short descriptions run: ```shell make help ``` ## Disclaimer This 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. ## License This software is released under the [MIT](../../LICENSE) ================================================ FILE: setup/kubectl-install-dapr ================================================ #!/bin/bash set -o errexit set -o pipefail set -o nounset declare -r DAPR_RELEASE=${DAPR_RELEASE:-"1.0.0-rc.1"} declare -r DAPR_HA=${DAPR_HA:-"false"} declare -r DAPR_LOG_AS_JSON=${DAPR_LOG_AS_JSON:-"false"} declare -r DAPR_CONFIG=${DAPR_CONFIG:-"https://raw.githubusercontent.com/mchmarny/dapr-demos/master/setup/config"} echo "Configuration:" echo "DAPR_RELEASE=${DAPR_RELEASE}" echo "DAPR_HA=${DAPR_HA}" echo "DAPR_LOG_AS_JSON=${DAPR_LOG_AS_JSON}" echo "DAPR_CONFIG=${DAPR_CONFIG}" if pgrep kubectl &> /dev/null ; then pkill kubectl -9 ; fi # Installing Dapr... helm repo add dapr https://dapr.github.io/helm-charts/ helm repo update kubectl create ns dapr-system helm install dapr dapr/dapr -n dapr-system \ --version $DAPR_RELEASE \ --set global.logAsJson=$DAPR_LOG_AS_JSON \ --set global.ha.enabled=$DAPR_HA # Wait for everything to finish installing kubectl rollout status deployment/dapr-operator -n dapr-system kubectl rollout status deployment/dapr-dashboard -n dapr-system kubectl rollout status deployment/dapr-sentry -n dapr-system kubectl rollout status deployment/dapr-sidecar-injector -n dapr-system # Installing Keda helm repo add kedacore https://kedacore.github.io/charts helm repo update kubectl create ns keda helm install keda kedacore/keda -n keda --set logLevel=debug # Wait for everything to finish installing kubectl rollout status deployment/keda-operator -n keda kubectl rollout status deployment/keda-operator-metrics-apiserver -n keda # Installing observabiliity... helm repo add grafana https://grafana.github.io/helm-charts helm repo add elastic https://helm.elastic.co helm repo add prometheus https://prometheus-community.github.io/helm-charts helm repo update kubectl create ns dapr-monitoring kubectl apply \ -f "${DAPR_CONFIG}/fluentd-config.yaml" \ -f "${DAPR_CONFIG}/fluentd.yaml" kubectl apply -f "${DAPR_CONFIG}/zipkin.yaml" -n dapr-monitoring helm install elasticsearch elastic/elasticsearch -n dapr-monitoring helm install dapr-prom prometheus/prometheus -n dapr-monitoring helm install grafana grafana/grafana -n dapr-monitoring \ --set persistence.enabled=true \ --set persistence.accessModes={ReadWriteOnce} \ --set persistence.size=8Gi helm install kibana elastic/kibana -n dapr-monitoring # Wait for everything to be ready... kubectl rollout status deployment/dapr-prom-kube-state-metrics -n dapr-monitoring kubectl rollout status deployment/dapr-prom-prometheus-alertmanager -n dapr-monitoring kubectl rollout status deployment/dapr-prom-prometheus-pushgateway -n dapr-monitoring kubectl rollout status deployment/dapr-prom-prometheus-server -n dapr-monitoring kubectl rollout status deployment/grafana -n dapr-monitoring kubectl rollout status deployment/kibana-kibana -n dapr-monitoring # Configure grafana dashbaords declare -r GRAFANA_PASS=$(kubectl get secret \ -n dapr-monitoring grafana \ -o jsonpath="{.data.admin-password}" \ | base64 --decode) kubectl port-forward svc/grafana 8888:80 -n dapr-monitoring & curl -X POST -s -k -u "admin:${GRAFANA_PASS}" \ -H "Content-Type: application/json" \ -d '{ "name":"Dapr", "type":"prometheus", "url":"http://dapr-prom-prometheus-server.dapr-monitoring", "access":"proxy", "basicAuth":false }' \ http://localhost:8888/api/datasources declare -r DASH1="system-services-dashboard.json" if [ ! -f "$DASH1" ]; then curl -O "${DAPR_CONFIG}/${DASH1}" fi curl -X POST -s -k -u "admin:${GRAFANA_PASS}" \ -H "Content-Type: application/json" \ -d @$DASH1 \ http://localhost:8888/api/dashboards/db declare -r DASH2="sidecar-dashboard.json" if [ ! -f "$DASH2" ]; then curl -O "${DAPR_CONFIG}/${DASH2}" fi curl -X POST -s -k -u "admin:${GRAFANA_PASS}" \ -H "Content-Type: application/json" \ -d @sidecar-dashboard.json \ http://localhost:8888/api/dashboards/db declare -r DASH3="actor-dashboard.json" if [ ! -f "$DASH3" ]; then curl -O "${DAPR_CONFIG}/${DASH3}" fi curl -X POST -s -k -u "admin:${GRAFANA_PASS}" \ -H "Content-Type: application/json" \ -d @actor-dashboard.json \ http://localhost:8888/api/dashboards/db # Configure kibana kubectl port-forward svc/kibana-kibana 5601 -n dapr-monitoring & curl -X POST -H "kbn-xsrf: true" \ -H "Content-Type: application/json" \ -d '{"attributes":{"title":"dapr*","timeFieldName":"@timestamp"}}' \ "http://localhost:5601/api/saved_objects/index-pattern/dapr" curl -X POST -H "kbn-xsrf: true" \ -H "Content-Type: application/json" \ -d '{"value":"dapr"}' \ "http://localhost:5601/api/kibana/settings/defaultIndex" ================================================ FILE: state-change-handler/Dockerfile ================================================ FROM golang:1.15.0 as builder WORKDIR /src/ COPY . /src/ ENV GO111MODULE=on RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -a -tags netgo -mod vendor -o ./service . FROM gcr.io/distroless/static:nonroot COPY --from=builder /src/service . ENTRYPOINT ["./service"] ================================================ FILE: state-change-handler/Makefile ================================================ RELEASE_VERSION =v0.11.1 SERVICE_NAME ?="publisher" DOCKER_USERNAME ?=$(DOCKER_USER) .PHONY: mod test run debug build dapr event image show imagerun lint clean, tag, init all: test tidy: ## Updates the go modules and vendors all dependencies go mod tidy go mod vendor test: tidy ## Tests the entire project go test -count=1 -race ./... build: tidy ## Builds local release binary CGO_ENABLED=0 go build -a -tags netgo -mod vendor -o bin/$(SERVICE_NAME) . debug: ## Runs uncompiled code it in Dapr in debug mode dapr run \ --app-id $(SERVICE_NAME) \ --app-protocol grpc \ --app-port 50001 \ --components-path ./config \ --log-level debug \ go run main.go run: build ## Builds binary and runs it in Dapr dapr run \ --app-id $(SERVICE_NAME) \ --app-protocol grpc \ --app-port 50001 \ --components-path ./config \ bin/$(SERVICE_NAME) image: tidy ## Builds and publish docker image docker build -t "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" . docker push "$(DOCKER_USERNAME)/$(SERVICE_NAME):$(RELEASE_VERSION)" lint: ## Lints the entire project golangci-lint run --timeout=3m tag: ## Creates release tag git tag $(RELEASE_VERSION) git push origin $(RELEASE_VERSION) clean: ## Cleans up generated files go clean rm -fr ./bin rm -fr ./vendor help: ## Display available commands @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk \ 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' ================================================ FILE: state-change-handler/README.md ================================================ # Dapr State Change Publisher Binding to detect RethinkDB state changes and stream them into a single topic. ## Components ### Source ```yaml apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: changes spec: type: bindings.rethinkdb.statechange metadata: - name: address value: "127.0.0.1:28015" - name: database value: "dapr" ``` ### Target ```yaml apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: events spec: type: pubsub.redis metadata: - name: redisHost value: localhost:6379 - name: redisPassword value: "" ``` ## Service ```shell dapr run --app-id publisher \ --protocol grpc \ --app-port 50001 \ --components-path ./config \ go run main.go ``` ================================================ FILE: state-change-handler/config/pubsub.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: events spec: type: pubsub.redis metadata: - name: redisHost value: localhost:6379 - name: redisPassword value: "" ================================================ FILE: state-change-handler/config/statechange.yaml ================================================ apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: changes spec: type: bindings.rethinkdb.statechange metadata: - name: address value: "127.0.0.1:28015" - name: database value: "dapr" - name: table value: "daprstate" - name: username value: "admin" # default - name: password value: "rethinkdb" # default ================================================ FILE: state-change-handler/go.mod ================================================ module github.com/mchmarny/dapr-demos/state-change-handler go 1.15 require ( github.com/dapr/go-sdk v0.11.0 github.com/pkg/errors v0.9.1 ) ================================================ FILE: state-change-handler/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dapr/go-sdk v0.11.0 h1:oUAWkFvOevvT+CwvjdIXs4fvK1Gjs33ni0tdHLFcqIo= github.com/dapr/go-sdk v0.11.0/go.mod h1:hre4W06eUYUDzWZBo+ZkOJdjnzuW9GZwZBRYOymvmvs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 h1:4X356008q5SA3YXu8PiRap39KFmy4Lf6sGlceJKZQsU= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0 h1:uslsjIdqvZYANxSBQjTI47vZfwMaTN3mLELkMnMIY/A= google.golang.org/genproto v0.0.0-20200917134801-bb4cff56e0d0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: state-change-handler/main.go ================================================ package main import ( "context" "log" "os" "strings" dapr "github.com/dapr/go-sdk/client" "github.com/dapr/go-sdk/service/common" daprd "github.com/dapr/go-sdk/service/grpc" "github.com/pkg/errors" ) var ( client dapr.Client logger = log.New(os.Stdout, "", 0) address = getEnvVar("ADDRESS", ":50001") method = getEnvVar("METHOD", "changes") pubsub = getEnvVar("PUBSUB", "events") topic = getEnvVar("TOPIC", "changes") ) func main() { // create client c, err := dapr.NewClient() if err != nil { logger.Fatalf("error creating Dapr client: %v", err) } client = c defer client.Close() // create the service s, err := daprd.NewService(address) if err != nil { log.Fatalf("failed to start the server: %v", err) } // add method handler err = s.AddBindingInvocationHandler(method, bindingHandler) if err != nil { log.Fatalf("error adding binding handler: %v", err) } // start the service if err := s.Start(); err != nil { log.Fatalf("server error: %v", err) } } func bindingHandler(ctx context.Context, in *common.BindingEvent) (out []byte, err error) { log.Printf("binding - Data:%s, Meta:%v", in.Data, in.Metadata) if in.Data != nil || len(in.Data) > 0 { if err := client.PublishEvent(ctx, pubsub, topic, in.Data); err != nil { logger.Printf("error publishing data to topic: %s", topic) return nil, errors.Wrapf(err, "error publishing data to topic: %s", topic) } } return nil, nil } func getEnvVar(key, fallbackValue string) string { if val, ok := os.LookupEnv(key); ok { return strings.TrimSpace(val) } return fallbackValue }