From 943b63864a011951d0ae18d5882a8df025e4f1a6 Mon Sep 17 00:00:00 2001 From: Tim Callaghan <69230637+timcallaghan@users.noreply.github.com> Date: Thu, 20 Apr 2023 04:42:55 +1000 Subject: [PATCH] Add docker compose support (#217) * Add new platform type for docker-compose Signed-off-by: Tim Callaghan * Update readme to include setup process for docker compose Update dev docs with information on defining a new platform Signed-off-by: Tim Callaghan * Typo Signed-off-by: Tim Callaghan * Update platform.md Signed-off-by: Artur Souza * Fix conflict resolution in instances.go Signed-off-by: Artur Souza * Fix compilation after merge conflicts. Signed-off-by: Artur Souza * Fixing lint errors. Signed-off-by: Artur Souza --------- Signed-off-by: Tim Callaghan Signed-off-by: Artur Souza Co-authored-by: Artur Souza --- README.md | 105 ++++++++- cmd/dashboard.go | 28 ++- cmd/webserver.go | 21 +- docs/development/platform.md | 17 +- go.mod | 19 +- go.sum | 31 ++- pkg/components/components.go | 63 +++++- pkg/configurations/configurations.go | 61 +++++- pkg/instances/instances.go | 199 ++++++++++++++++-- pkg/platforms/platforms.go | 22 ++ .../configuration/configuration.component.ts | 2 + .../pages/dashboard/dashboard.component.ts | 3 + 12 files changed, 517 insertions(+), 54 deletions(-) create mode 100644 pkg/platforms/platforms.go diff --git a/README.md b/README.md index c2660a9..0670bf4 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,12 @@ [![codecov](https://codecov.io/gh/dapr/dashboard/branch/master/graph/badge.svg)](https://codecov.io/gh/dapr/dashboard) [![FOSSA Status](https://app.fossa.com/api/projects/custom%2B162%2Fgithub.com%2Fdapr%2Fdashboard.svg?type=shield)](https://app.fossa.com/projects/custom%2B162%2Fgithub.com%2Fdapr%2Fdashboard?ref=badge_shield) -Dapr Dashboard is a web-based UI for Dapr, allowing users to see information, view logs and more for the Dapr applications, components, and configurations running either locally or in a Kubernetes cluster. +Dapr Dashboard is a web-based UI for Dapr, allowing users to see information, view logs and more for the Dapr applications, components, and configurations running on a supported Dapr Dashboard platform. + +Supported Dapr Dashboard plaforms are: +- Standalone - running locally via Dapr CLI +- Kubernetes - running inside a Kubernetes cluster +- Docker Compose - running inside a docker compose network

@@ -11,19 +16,22 @@ Dapr Dashboard is a web-based UI for Dapr, allowing users to see information, vi ## Features -Dapr Dashboard provides information about Dapr applications, components, configurations, and control plane services. Users can view metadata, manifests and deployment files, actors, logs, and more on both Kubernetes and self-hosted platforms. For more information, check out the [changelog](docs/development/changelog.md). +Dapr Dashboard provides information about Dapr applications, components, configurations, and control plane services (Kubernetes only). Users can view metadata, manifests and deployment files, actors, logs, and more. For more information, check out the [changelog](docs/development/changelog.md). ## Getting started ### Prerequisites -[Dapr Runtime](https://github.com/dapr/dapr) -[Dapr CLI](https://github.com/dapr/cli) +If you intend to run in the Standalone or Kubernetes platform mode you will need to have the following: -### Installation +- [Dapr Runtime](https://github.com/dapr/dapr) +- [Dapr CLI](https://github.com/dapr/cli) Dapr Dashboard comes pre-packaged with the Dapr CLI. To learn more about the dashboard command, use the CLI command `dapr dashboard -h`. +If you intend to run in the Docker Compose platform mode, you don't need to install anything. Instead you specify Dapr docker images to use. + +### Installation If you want to install via Helm, run: ```sh helm repo add dapr https://dapr.github.io/helm-charts/ @@ -37,6 +45,93 @@ Run `dapr dashboard -k`, or if you installed Dapr in a non-default namespace, `d #### Standalone Run `dapr dashboard`, and navigate to http://localhost:8080. +#### Docker Compose +Construct a docker compose file that references the specific Dapr pieces that you want to use. The following example defines an application and its corresponding daprd sidecar, the Dapr Placement service, and the Dapr Dashboard. + +When running inside docker compose, the dashboard needs access to the component and configuration files that are passed to the daprd services. It also needs to know about all daprd services running inside the docker compose network - it retrieves this by parsing the docker-compose.yml file. To achieve this, you define docker bind mounts to these files/directories and pass them as command args to the dashboard process. In addition, you must specify the command arg `--docker-compose=true` to tell the dashboard to use the docker compose platform type. + +```yml +version: '3.8' +services: + + my-application-webhost: + build: + context: . + dockerfile: src/My.Application.WebHost/Dockerfile + ports: + - "5002:80" + networks: + - my-network + + my-application-webhost-dapr: + image: "daprio/daprd:1.8.0" + command: [ "./daprd", + "-app-id", "MyApplication.DaprSidecar", + "-app-port", "80", + "-placement-host-address", "dapr-placement:50000", + "-components-path", "/components", + "-config", "/configuration/config.yaml" ] + volumes: + - "./dockercompose/dapr/components/:/components" + - "./dockercompose/dapr/config/:/configuration" + depends_on: + - my-application-webhost + - dapr-placement + network_mode: "service:my-application-webhost" + + dapr-placement: + image: "daprio/dapr:1.8.0" + command: [ "./placement", "-port", "50000" ] + ports: + - "50000:50000" + networks: + - my-network + + dapr-dashboard: + image: "daprio/dashboard:latest" + command: [ "--docker-compose=true", + "--components-path=/home/nonroot/components", + "--config-path=/home/nonroot/configuration", + "--docker-compose-path=/home/nonroot/docker-compose.yml" ] + ports: + - "8080:8080" + volumes: + - "./dockercompose/dapr/components/:/home/nonroot/components" + - "./dockercompose/dapr/config/:/home/nonroot/configuration" + - ./docker-compose.yml:/home/nonroot/docker-compose.yml + networks: + - my-network + +networks: + my-network: +``` + +The above example assumes the following file system layout + +``` +dockercompose + dapr + components + (component yaml files e.g. pubsub.yaml, statestore.yaml etc.) + config + config.yaml +src + My.Application.WebHost + Dockerfile +docker-compose.yml +``` + +If you have configured your Dapr sidecars to require [API token authentication](https://docs.dapr.io/operations/security/api-token/), you can set the environment variable `DAPR_API_TOKEN: {your token}` on the Dapr Dashboard service declaration as follows +```yml + dapr-dashboard: + image: "daprio/dashboard:latest" + environment: + DAPR_API_TOKEN: {your token} + ... +``` + +For more information about running Dapr with docker compose see [Run using Docker-Compose](https://docs.dapr.io/operations/hosting/self-hosted/self-hosted-with-docker/#run-using-docker-compose) + ### Contributing Anyone is free to open an issue, a feature request, or a pull request. diff --git a/cmd/dashboard.go b/cmd/dashboard.go index ed172a7..5aa38d0 100644 --- a/cmd/dashboard.go +++ b/cmd/dashboard.go @@ -25,13 +25,39 @@ import ( func RunDashboard() { dashboardVersion := flag.Bool("version", false, "Prints the dashboard version") port := flag.Int("port", 8080, "Port to listen to") + dockerCompose := flag.Bool("docker-compose", false, "Is running inside docker compose") + componentsPath := flag.String("components-path", "", "Path to volume mounted dapr components (docker-compose only)") + configPath := flag.String("config-path", "", "Path to volume mounted dapr configuration (docker-compose only)") + dockerComposePath := flag.String("docker-compose-path", "", "Path to volume mounted docker compose file (docker-compose only)") flag.Parse() + fmt.Println(*dockerCompose) + fmt.Println(*componentsPath) + fmt.Println(*configPath) + fmt.Println(*dockerComposePath) + + if *dockerCompose { + if len(*componentsPath) == 0 { + fmt.Println("--components-path required when --docker-compose=true") + os.Exit(1) + } + + if len(*configPath) == 0 { + fmt.Println("--config-path required when --docker-compose=true") + os.Exit(1) + } + + if len(*dockerComposePath) == 0 { + fmt.Println("--docker-compose-path required when --docker-compose=true") + os.Exit(1) + } + } + if *dashboardVersion { fmt.Println(version.GetVersion()) os.Exit(0) } else { - RunWebServer(*port) + RunWebServer(*port, *dockerCompose, *componentsPath, *configPath, *dockerComposePath) } } diff --git a/cmd/webserver.go b/cmd/webserver.go index 5e75c9e..f6fc359 100644 --- a/cmd/webserver.go +++ b/cmd/webserver.go @@ -32,6 +32,7 @@ import ( instances "github.com/dapr/dashboard/pkg/instances" kube "github.com/dapr/dashboard/pkg/kube" dashboard_log "github.com/dapr/dashboard/pkg/log" + "github.com/dapr/dashboard/pkg/platforms" "github.com/gorilla/mux" "github.com/gorilla/websocket" ) @@ -77,18 +78,18 @@ var ( ) // RunWebServer starts the web server that serves the Dapr UI dashboard and the API -func RunWebServer(port int) { - platform := "" +func RunWebServer(port int, isDockerCompose bool, componentsPath string, configPath string, dockerComposePath string) { + platform := platforms.Standalone kubeClient, daprClient, _ := kube.Clients() if kubeClient != nil { - platform = "kubernetes" - } else { - platform = "standalone" + platform = platforms.Kubernetes + } else if isDockerCompose { + platform = platforms.DockerCompose } - inst = instances.NewInstances(platform, kubeClient) - comps = components.NewComponents(platform, daprClient) - configs = configurations.NewConfigurations(platform, daprClient) + inst = instances.NewInstances(platform, kubeClient, dockerComposePath) + comps = components.NewComponents(platform, daprClient, componentsPath) + configs = configurations.NewConfigurations(platform, daprClient, configPath) r := mux.NewRouter() api := r.PathPrefix("/api/").Subrouter() @@ -213,7 +214,7 @@ func getFeaturesHandler(w http.ResponseWriter, r *http.Request) { if configs.Supported() { features = append(features, "configurations") } - if inst.CheckPlatform() == "kubernetes" { + if inst.CheckPlatform() == platforms.Kubernetes { features = append(features, "status") } respondWithJSON(w, 200, features) @@ -221,7 +222,7 @@ func getFeaturesHandler(w http.ResponseWriter, r *http.Request) { func getPlatformHandler(w http.ResponseWriter, r *http.Request) { resp := inst.CheckPlatform() - respondWithPlainString(w, 200, resp) + respondWithPlainString(w, 200, string(resp)) } func getContainersHandler(w http.ResponseWriter, r *http.Request) { diff --git a/docs/development/platform.md b/docs/development/platform.md index 25373fa..897035b 100644 --- a/docs/development/platform.md +++ b/docs/development/platform.md @@ -1,11 +1,15 @@ # Adding a new platform -Dashboard currently supports 2 platforms: Kubernetes and self-hosted (August 2020). +Dashboard currently supports 3 platforms: Kubernetes, self-hosted and docker-compose (April 2023). ## Backend -The application platform is defined in `cmd/webserver.go:RunWebServer()`. To add a new platform, logic needs to be defined here to determine how Dashboard will recognize it. Any API clients or other necessary structures should be passed as arguments to the constructor of `instances.NewInstances(...)` and the other backend struct constructors along with the platform. +Platform definitions are contained in [platforms.go](../../pkg/platforms/platforms.go). When adding a new platform, define a new constant for the platform in `platforms.go`. + +If the new platform requires configuration arguments you can define optional go flags in [dashboard.go](../../cmd/dashboard.go) and pass them to `RunWebServer(...)` in [webserver.go](../../cmd/webserver.go). + +The runtime application platform is defined in `cmd/webserver.go:RunWebServer()`. To add a new platform, logic needs to be defined here to determine how Dashboard will recognize it. Any API clients or other necessary structures should be passed as arguments to the constructor of `instances.NewInstances(...)` and the other backend struct constructors along with the platform. In `pkg/instances.go`, `pkg/components.go`, and `pkg/configurations.go`, new methods should be defined for each new platform, following the current pattern. In these files, functions such as `GetInstance(scope string, id string)` and `GetScopes()` are defined. These abstracted functions will call the correct platform-specific function: @@ -18,6 +22,15 @@ func (i *instances) GetInstances(scope string) []Instance { Where `i.getInstanceFn` is defined in the constructor as `getPlatformInstances`, e.g. `getKubernetesInstances` or `getStandaloneInstances` according to the platform supplied. +If the new platform supports a feature (e.g. components) make sure you update the `func (c *type) Supported() bool` method in the corresponding package to include the platform definition. For example, in [components.go](../../pkg/components/components.go) + +```go +// Supported checks whether or not the supplied platform is able to access Dapr components +func (c *components) Supported() bool { + return c.platform == platforms.Kubernetes || c.platform == platforms.Standalone || c.platform == platforms.DockerCompose +} +``` + ## Frontend For platform-specific features of Dashboard, there is a service defined in `web/src/app/globals/globals.service.ts` that retrieves platform information from the backend. Any component that needs to know the current platform can make a call to this service and handle the returned data appropriately: diff --git a/go.mod b/go.mod index 3e6ba55..8603632 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,15 @@ module github.com/dapr/dashboard go 1.19 require ( + github.com/compose-spec/compose-go v1.13.3 github.com/dapr/cli v1.10.0-rc.3 + github.com/dapr/components-contrib v1.10.0-rc.4 github.com/dapr/dapr v1.10.0-rc.5 + github.com/dapr/kit v0.0.4 github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 - github.com/stretchr/testify v1.8.1 + github.com/shirou/gopsutil v3.21.11+incompatible + github.com/stretchr/testify v1.8.2 k8s.io/api v0.26.1 k8s.io/apimachinery v0.26.1 k8s.io/client-go v1.5.2 @@ -26,12 +30,12 @@ require ( github.com/benbjohnson/clock v1.3.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/briandowns/spinner v1.21.0 // indirect + github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/cenkalti/backoff/v4 v4.2.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/containerd/containerd v1.6.16 // indirect - github.com/dapr/components-contrib v1.10.0-rc.4 // indirect - github.com/dapr/kit v0.0.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/distribution/distribution/v3 v3.0.0-20230214150026-36d8c594d7aa // indirect github.com/docker/distribution v2.8.1+incompatible // indirect github.com/docker/docker v23.0.0+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect @@ -61,6 +65,7 @@ require ( github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.3.0 // indirect + github.com/grandcat/zeroconf v1.0.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect @@ -69,7 +74,7 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.2 // indirect github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.1 // indirect - github.com/imdario/mergo v0.3.13 // indirect + github.com/imdario/mergo v0.3.15 // indirect github.com/jhump/protoreflect v1.14.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -80,8 +85,10 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/mattn/go-shellwords v1.0.12 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/microsoft/durabletask-go v0.1.3 // indirect + github.com/miekg/dns v1.1.43 // indirect github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4 // indirect @@ -102,7 +109,6 @@ require ( github.com/prometheus/statsd_exporter v0.23.0 // indirect github.com/rivo/uniseg v0.4.3 // indirect github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect - github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/sirupsen/logrus v1.9.0 // indirect github.com/sony/gobreaker v0.5.0 // indirect github.com/spf13/cast v1.5.0 // indirect @@ -114,6 +120,9 @@ require ( github.com/tklauser/numcpus v0.6.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.44.0 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/otel v1.13.0 // indirect diff --git a/go.sum b/go.sum index 7eecbc7..dba008e 100644 --- a/go.sum +++ b/go.sum @@ -77,6 +77,8 @@ github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx2 github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4= github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -96,6 +98,8 @@ github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/compose-spec/compose-go v1.13.3 h1:6ap8P6WcCrRKKCufGwV43SIqr47Y2wnZncqIh7DwyU0= +github.com/compose-spec/compose-go v1.13.3/go.mod h1:rsiZ8uaOHJYJemDBzTe9UBpaq5ZFVEOO4TxM2G3SJxk= github.com/containerd/containerd v1.6.16 h1:0H5xH6ABsN7XTrxIAKxFpBkFCBtrZ/OSORhCpUnHjrc= github.com/containerd/containerd v1.6.16/go.mod h1:1RdCUu95+gc2v9t3IL+zIlpClSmew7/0YS8O5eQZrOw= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -112,6 +116,8 @@ 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/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/distribution/distribution/v3 v3.0.0-20230214150026-36d8c594d7aa h1:L9Ay/slwQ4ERSPaurC+TVkZrM0K98GNrEEo1En3e8as= +github.com/distribution/distribution/v3 v3.0.0-20230214150026-36d8c594d7aa/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/distribution v0.0.0-20191216044856-a8371794149d h1:jC8tT/S0OGx2cswpeUTn4gOIea8P08lD3VFQT0cOZ50= github.com/docker/distribution v0.0.0-20191216044856-a8371794149d/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= @@ -265,6 +271,8 @@ github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE= +github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= @@ -289,8 +297,8 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4= github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= -github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= +github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= @@ -346,11 +354,16 @@ github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= +github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/microsoft/durabletask-go v0.1.3 h1:7SJ4U7MUn64xdKzRr/8CDtK4WtZmc7WC5lEQYWFEo6c= github.com/microsoft/durabletask-go v0.1.3/go.mod h1:UOgcE09cx6SzBS31p4darJ7ve9P5pSVIStPShxDnvPo= +github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= +github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= @@ -480,8 +493,9 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stvp/go-udp-testing v0.0.0-20201019212854-469649b16807/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc= github.com/tidwall/transform v0.0.0-20201103190739-32f242e2dbde h1:AMNpJRc7P+GTwVbl8DkK2I9I8BBUzNiHuH/tlxrpan0= github.com/tidwall/transform v0.0.0-20201103190739-32f242e2dbde/go.mod h1:MvrEmduDUz4ST5pGZ7CABCnOU5f3ZiOAZzT6b1A6nX8= @@ -495,7 +509,11 @@ github.com/valyala/fasthttp v1.44.0 h1:R+gLUhldIsfg1HokMuQjdQ5bh9nuXHPIfvkYUu9eR github.com/valyala/fasthttp v1.44.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -598,6 +616,7 @@ golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -614,6 +633,7 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -662,6 +682,7 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -684,6 +705,7 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -742,6 +764,7 @@ golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -892,11 +915,11 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/pkg/components/components.go b/pkg/components/components.go index 02bdd35..2012f1b 100644 --- a/pkg/components/components.go +++ b/pkg/components/components.go @@ -22,6 +22,7 @@ import ( v1alpha1 "github.com/dapr/dapr/pkg/apis/components/v1alpha1" scheme "github.com/dapr/dapr/pkg/client/clientset/versioned" "github.com/dapr/dashboard/pkg/age" + "github.com/dapr/dashboard/pkg/platforms" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/yaml" ) @@ -34,21 +35,25 @@ type Components interface { } type components struct { - platform string + platform platforms.Platform daprClient scheme.Interface getComponentsFn func(scope string) []Component + componentsPath string } // NewComponents returns a new Components instance -func NewComponents(platform string, daprClient scheme.Interface) Components { +func NewComponents(platform platforms.Platform, daprClient scheme.Interface, componentsPath string) Components { c := components{} c.platform = platform - if platform == "kubernetes" { + if platform == platforms.Kubernetes { c.getComponentsFn = c.getKubernetesComponents c.daprClient = daprClient - } else if platform == "standalone" { + } else if platform == platforms.Standalone { c.getComponentsFn = c.getStandaloneComponents + } else if platform == platforms.DockerCompose { + c.getComponentsFn = c.getDockerComposeComponents + c.componentsPath = componentsPath } return &c } @@ -66,7 +71,7 @@ type Component struct { // Supported checks whether or not the supplied platform is able to access Dapr components func (c *components) Supported() bool { - return c.platform == "kubernetes" || c.platform == "standalone" + return c.platform == platforms.Kubernetes || c.platform == platforms.Standalone || c.platform == platforms.DockerCompose } // GetComponent returns a specific component based on a supplied component name @@ -163,3 +168,51 @@ func (c *components) getStandaloneComponents(scope string) []Component { } return standaloneComponents } + +// getDockerComposeComponents returns the list of all docker-compose Dapr components +func (c *components) getDockerComposeComponents(scope string) []Component { + componentsDirectory := c.componentsPath + dockerComposeComponents := []Component{} + err := filepath.Walk(componentsDirectory, func(path string, info os.FileInfo, err error) error { + if err != nil { + log.Printf("Failure accessing path %s: %v\n", path, err) + return err + } + if info.IsDir() && info.Name() != filepath.Base(componentsDirectory) { + return filepath.SkipDir + } else if !info.IsDir() && filepath.Ext(path) == ".yaml" { + content, err := os.ReadFile(path) + if err != nil { + log.Printf("Failure reading file %s: %v\n", path, err) + return err + } + + comp := v1alpha1.Component{} + err = yaml.Unmarshal(content, &comp) + if err != nil { + log.Printf("Failure unmarshalling %s into Component: %s\n", path, err.Error()) + } + + newComponent := Component{ + Name: comp.Name, + Kind: comp.Kind(), + Type: comp.Spec.Type, + Created: info.ModTime().Format("2006-01-02 15:04.05"), + Age: age.GetAge(info.ModTime()), + Scopes: comp.Scopes, + Manifest: string(content), + } + + if newComponent.Kind == "Component" { + dockerComposeComponents = append(dockerComposeComponents, newComponent) + } + return nil + } + return nil + }) + if err != nil { + log.Printf("error walking the path %q: %v\n", componentsDirectory, err) + return []Component{} + } + return dockerComposeComponents +} diff --git a/pkg/configurations/configurations.go b/pkg/configurations/configurations.go index ee662f4..11d4e99 100644 --- a/pkg/configurations/configurations.go +++ b/pkg/configurations/configurations.go @@ -23,6 +23,7 @@ import ( scheme "github.com/dapr/dapr/pkg/client/clientset/versioned" "github.com/dapr/dapr/pkg/config" "github.com/dapr/dashboard/pkg/age" + "github.com/dapr/dashboard/pkg/platforms" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -34,21 +35,25 @@ type Configurations interface { } type configurations struct { - platform string + platform platforms.Platform daprClient scheme.Interface getConfigurationsFn func(scope string) []Configuration + configPath string } // NewConfigurations returns a new Configurations instance -func NewConfigurations(platform string, daprClient scheme.Interface) Configurations { +func NewConfigurations(platform platforms.Platform, daprClient scheme.Interface, configPath string) Configurations { c := configurations{} c.platform = platform - if platform == "kubernetes" { + if platform == platforms.Kubernetes { c.getConfigurationsFn = c.getKubernetesConfigurations c.daprClient = daprClient - } else if platform == "standalone" { + } else if platform == platforms.Standalone { c.getConfigurationsFn = c.getStandaloneConfigurations + } else if platform == platforms.DockerCompose { + c.getConfigurationsFn = c.getDockerComposeConfigurations + c.configPath = configPath } return &c } @@ -70,7 +75,7 @@ type Configuration struct { // Supported checks whether or not the supplied platform is able to access Dapr configurations func (c *configurations) Supported() bool { - return c.platform == "kubernetes" || c.platform == "standalone" + return c.platform == platforms.Kubernetes || c.platform == platforms.Standalone || c.platform == platforms.DockerCompose } // GetConfiguration returns the Dapr configuration specified by name @@ -171,6 +176,52 @@ func (c *configurations) getStandaloneConfigurations(scope string) []Configurati return standaloneConfigurations } +// getDockerComposeConfigurations returns the list of docker-compose Dapr Configurations Statuses +func (c *configurations) getDockerComposeConfigurations(scope string) []Configuration { + configurationsDirectory := c.configPath + dockerComposeConfigurations := []Configuration{} + err := filepath.Walk(configurationsDirectory, func(path string, info os.FileInfo, err error) error { + if err != nil { + log.Printf("Failure accessing path %s: %v\n", path, err) + return err + } + if info.IsDir() && info.Name() != filepath.Base(configurationsDirectory) { + return filepath.SkipDir + } else if !info.IsDir() && filepath.Ext(path) == ".yaml" { + comp, content, err := config.LoadStandaloneConfiguration(path) + if err != nil { + log.Printf("Failure reading configuration file %s: %v\n", path, err) + return err + } + + newConfiguration := Configuration{ + Name: comp.Name, + Kind: comp.Kind, + Created: info.ModTime().Format("2006-01-02 15:04.05"), + Age: age.GetAge(info.ModTime()), + TracingEnabled: tracingEnabled(comp.Spec.TracingSpec.SamplingRate), + SamplingRate: comp.Spec.TracingSpec.SamplingRate, + MetricsEnabled: comp.Spec.MetricSpec.Enabled, + MTLSEnabled: comp.Spec.MTLSSpec.Enabled, + WorkloadCertTTL: comp.Spec.MTLSSpec.WorkloadCertTTL, + ClockSkew: comp.Spec.MTLSSpec.AllowedClockSkew, + Manifest: content, + } + + if newConfiguration.Kind == "Configuration" { + dockerComposeConfigurations = append(dockerComposeConfigurations, newConfiguration) + } + return nil + } + return nil + }) + if err != nil { + log.Printf("error walking the path %q: %v\n", configurationsDirectory, err) + return []Configuration{} + } + return dockerComposeConfigurations +} + // tracingEnabled checks if tracing is enabled for a configuration func tracingEnabled(samplingRate string) bool { sr, err := strconv.ParseFloat(samplingRate, 32) diff --git a/pkg/instances/instances.go b/pkg/instances/instances.go index 7a4be0e..bb137b3 100644 --- a/pkg/instances/instances.go +++ b/pkg/instances/instances.go @@ -21,12 +21,21 @@ import ( "io" "log" "net/http" + "os" "strconv" "strings" "sync" + "time" + "github.com/compose-spec/compose-go/loader" + "github.com/compose-spec/compose-go/types" "github.com/dapr/cli/pkg/standalone" + "github.com/dapr/components-contrib/nameresolution" + "github.com/dapr/components-contrib/nameresolution/mdns" "github.com/dapr/dashboard/pkg/age" + "github.com/dapr/dashboard/pkg/platforms" + "github.com/dapr/kit/logger" + process "github.com/shirou/gopsutil/process" v1 "k8s.io/api/core/v1" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -62,35 +71,46 @@ type Instances interface { GetControlPlaneStatus() []StatusOutput GetMetadata(scope string, id string) MetadataOutput GetScopes() []string - CheckPlatform() string + CheckPlatform() platforms.Platform } type instances struct { - platform string - kubeClient kubernetes.Interface - getInstancesFn func(string) []Instance - getScopesFn func() []string + platform platforms.Platform + kubeClient kubernetes.Interface + getInstancesFn func(string) []Instance + getScopesFn func() []string + dockerComposePath string + resolver nameresolution.Resolver + metadataClient http.Client + daprApiToken string } // NewInstances returns an Instances instance -func NewInstances(platform string, kubeClient *kubernetes.Clientset) Instances { +func NewInstances(platform platforms.Platform, kubeClient *kubernetes.Clientset, dockerComposePath string) Instances { i := instances{} i.platform = platform + i.metadataClient = http.Client{} + i.daprApiToken = os.Getenv("DAPR_API_TOKEN") - if i.platform == "kubernetes" { + if i.platform == platforms.Kubernetes { i.getInstancesFn = i.getKubernetesInstances i.getScopesFn = i.getKubernetesScopes i.kubeClient = kubeClient - } else if i.platform == "standalone" { + } else if i.platform == platforms.Standalone { i.getInstancesFn = i.getStandaloneInstances i.getScopesFn = i.getStandaloneScopes + } else if i.platform == platforms.DockerCompose { + i.getInstancesFn = i.getDockerComposeInstances + i.getScopesFn = i.getDockerComposeScopes + i.dockerComposePath = dockerComposePath + i.resolver = mdns.NewResolver(logger.NewLogger("mdns")) } return &i } // Supported checks if the current platform supports Dapr instances func (i *instances) Supported() bool { - return i.platform == "kubernetes" || i.platform == "standalone" + return i.platform == platforms.Kubernetes || i.platform == platforms.Standalone || i.platform == platforms.DockerCompose } // GetScopes returns the result of the appropriate environment's GetScopes function @@ -99,7 +119,7 @@ func (i *instances) GetScopes() []string { } // CheckPlatform returns the current environment dashboard is running in -func (i *instances) CheckPlatform() string { +func (i *instances) CheckPlatform() platforms.Platform { return i.platform } @@ -267,7 +287,7 @@ func (i *instances) DeleteInstance(scope string, id string) error { return standalone.Stop(id, cliPIDToNoOfApps, apps) } -// GetInstance uses the appropriate getInstance function (kubernetes, standalone, etc.) and returns the given instance from its id +// GetInstance uses the appropriate getInstance function (kubernetes, standalone, docker-compose etc.) and returns the given instance from its id func (i *instances) GetInstance(scope string, id string) Instance { instanceList := i.getInstancesFn(scope) for _, instance := range instanceList { @@ -378,17 +398,28 @@ func (i *instances) GetMetadata(scope string, id string) MetadataOutput { } } + } else if i.platform == platforms.DockerCompose { + appId := i.GetInstance(scope, id).AppID + port := i.GetInstance(scope, id).HTTPPort + address, err := i.resolver.ResolveID(nameresolution.ResolveRequest{ID: appId}) + if err != nil { + log.Println(err) + return MetadataOutput{} + } + url = append(url, fmt.Sprintf("http://%s:%v/v1.0/metadata", strings.Split(address, ":")[0], port)) + } else { port := i.GetInstance(scope, id).HTTPPort url = append(url, fmt.Sprintf("http://localhost:%v/v1.0/metadata", port)) } if len(url) != 0 { - data := getMetadataOutputFromURLs(url[0], "") + data := i.getMetadataOutputFromURLs(url[0], "") + if len(url) > 1 { // merge the actor metadata from the other replicas - for i := range url[1:] { - replicaData := getMetadataOutputFromURLs(url[i+1], secondaryUrl[i+1]) + for index := range url[1:] { + replicaData := i.getMetadataOutputFromURLs(url[index+1], secondaryUrl[index+1]) for _, actor := range replicaData.Actors { // check if this actor type is already in the list @@ -414,16 +445,42 @@ func (i *instances) GetMetadata(scope string, id string) MetadataOutput { return MetadataOutput{} } -func getMetadataOutputFromURLs(primaryURL string, secondaryURL string) MetadataOutput { - resp, err := http.Get(primaryURL) +func (i *instances) getMetadataOutputFromURLs(primaryURL string, secondaryURL string) MetadataOutput { + req, err := http.NewRequest("GET", primaryURL, nil) + if err != nil { + log.Println(err) + return MetadataOutput{} + } + + if len(i.daprApiToken) > 0 { + req.Header.Add("dapr-api-token", i.daprApiToken) + } + + resp, err := i.metadataClient.Do(req) if err != nil && len(secondaryURL) != 0 { log.Println(err) - resp, err = http.Get(secondaryURL) + secondaryReq, err := http.NewRequest("GET", secondaryURL, nil) if err != nil { log.Println(err) return MetadataOutput{} } + + if len(i.daprApiToken) > 0 { + secondaryReq.Header.Add("dapr-api-token", i.daprApiToken) + } + resp, err = i.metadataClient.Do(secondaryReq) + + if err != nil { + log.Println(err) + return MetadataOutput{} + } + } + + if err != nil { + log.Println(err) + return MetadataOutput{} } + defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { @@ -529,6 +586,110 @@ func (i *instances) getStandaloneInstances(scope string) []Instance { return list } +// getDockerComposeInstances returns the Dapr instances running in the docker compose environment +func (i *instances) getDockerComposeInstances(scope string) []Instance { + list := []Instance{} + + composeFile, err := os.ReadFile(i.dockerComposePath) + if err != nil { + log.Println(err) + return list + } + + workingDir, err := os.Getwd() + if err != nil { + log.Println(err) + return list + } + + configFiles := []types.ConfigFile{} + configFiles = append(configFiles, types.ConfigFile{ + Filename: "docker-compose.yml", + Content: composeFile, + }) + + configDetails := types.ConfigDetails{ + WorkingDir: workingDir, + ConfigFiles: configFiles, + Environment: nil, + } + + // Can't get creation time and age of daprd services so approximate from dashboard process + dashboardPID := os.Getpid() + procDetails, err := process.NewProcess(int32(dashboardPID)) + if err != nil { + log.Println(err) + return list + } + + createUnixTimeMilliseconds, err := procDetails.CreateTime() + if err != nil { + log.Println(err) + return list + } + + createTime := time.Unix(createUnixTimeMilliseconds/1000, 0) + + project, err := loader.Load(configDetails) + + if err != nil { + log.Println(err) + } else { + for _, service := range project.Services { + if !strings.Contains(service.Image, "daprd") { + continue + } + + appId := "" + appPort := 80 + foundAppId := false + foundAppPort := false + for _, cmdArg := range service.Command { + if strings.Contains(cmdArg, "app-id") { + foundAppId = true + continue + } + + if strings.Contains(cmdArg, "app-port") { + foundAppPort = true + continue + } + + if foundAppId { + appId = cmdArg + foundAppId = false + continue + } + + if foundAppPort { + parsedPort, err := strconv.ParseInt(cmdArg, 10, 0) + if err == nil { + appPort = int(parsedPort) + } + foundAppId = false + continue + } + } + + list = append(list, Instance{ + AppID: appId, + HTTPPort: 3500, + GRPCPort: 50001, + AppPort: appPort, + Command: "", + Age: age.GetAge(createTime), + Created: createTime.Format("2006-01-02 15:04.05"), + PID: -1, + Replicas: 1, + SupportsDeletion: false, + SupportsLogs: false, + Address: fmt.Sprintf("%s:3500", appId), + }) + } + } + return list +} + func (i *instances) getKubernetesScopes() []string { ctx := context.Background() scopes := []string{"All"} @@ -547,6 +708,10 @@ func (i *instances) getStandaloneScopes() []string { return []string{"All"} } +func (i *instances) getDockerComposeScopes() []string { + return []string{"All"} +} + func getAppLabelValue(value string) string { if value != "" { return "app:" + value diff --git a/pkg/platforms/platforms.go b/pkg/platforms/platforms.go new file mode 100644 index 0000000..3fe128a --- /dev/null +++ b/pkg/platforms/platforms.go @@ -0,0 +1,22 @@ +/* +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package platforms + +type Platform string + +const ( + Kubernetes Platform = "kubernetes" + Standalone Platform = "standalone" + DockerCompose Platform = "docker-compose" +) diff --git a/web/src/app/pages/configuration/configuration.component.ts b/web/src/app/pages/configuration/configuration.component.ts index 1fc6c2a..63be8a3 100644 --- a/web/src/app/pages/configuration/configuration.component.ts +++ b/web/src/app/pages/configuration/configuration.component.ts @@ -28,6 +28,8 @@ export class ConfigurationComponent implements OnInit, OnDestroy { this.displayedColumns = ['name', 'tracing-enabled', 'mtls-enabled', 'mtls-workload-ttl', 'mtls-clock-skew', 'age', 'created']; } else if (platform === 'standalone') { this.displayedColumns = ['name', 'tracing-enabled', 'mtls-enabled', 'age', 'created']; + } else if (platform === 'docker-compose') { + this.displayedColumns = ['name', 'tracing-enabled', 'mtls-enabled', 'age', 'created']; } }); diff --git a/web/src/app/pages/dashboard/dashboard.component.ts b/web/src/app/pages/dashboard/dashboard.component.ts index 6f2fb64..090d606 100644 --- a/web/src/app/pages/dashboard/dashboard.component.ts +++ b/web/src/app/pages/dashboard/dashboard.component.ts @@ -55,6 +55,9 @@ export class DashboardComponent implements OnInit, OnDestroy { else if (platform === 'standalone') { this.displayedColumns = ['name', 'age', 'actions']; } + else if (platform === 'docker-compose') { + this.displayedColumns = ['name', 'age']; + } }); }