diff --git a/README.md b/README.md index a28371179..4488e4558 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ Each release of Dapr CLI includes various OSes and architectures. These binary v In self-hosted mode, dapr can be initialized using the CLI with the placement, redis and zipkin containers enabled by default(recommended) or without them which also does not require docker to be available in the environment. #### Initialize Dapr + ([Prerequisite](#Prerequisites): Docker is available in the environment - recommended) Use the init command to initialize Dapr. On init, multiple default configuration files and containers are installed along with the dapr runtime binary. Dapr runtime binary is installed under $HOME/.dapr/bin for Mac, Linux and %USERPROFILE%\.dapr\bin for Windows. @@ -87,6 +88,7 @@ Output should look like so: > Note: To see that Dapr has been installed successfully, from a command prompt run the `docker ps` command and check that the `daprio/dapr:latest`, `dapr_redis` and `dapr_zipkin` container images are all running. This step creates the following defaults: + 1. components folder which is later used during `dapr run` unless the `--components-path` option is provided. For Linux/MacOS, the default components folder path is `$HOME/.dapr/components` and for Windows it is `%USERPROFILE%\.dapr\components`. 2. component files in the components folder called `pubsub.yaml` and `statestore.yaml`. 3. default config file `$HOME/.dapr/config.yaml` for Linux/MacOS or for Windows at `%USERPROFILE%\.dapr\config.yaml` to enable tracing on `dapr init` call. Can be overridden with the `--config` flag on `dapr run`. @@ -146,14 +148,15 @@ Uninstalling will remove daprd binary and the placement container (if installed ```bash -$ dapr uninstall +dapr uninstall ``` + > For Linux users, if you run your docker cmds with sudo, you need to use "**sudo dapr uninstall**" to remove the containers. The command above won't remove the redis or zipkin containers by default in case you were using it for other purposes. It will also not remove the default dapr folder that was created on `dapr init`. To remove all the containers (placement, redis, zipkin) and also the default dapr folder created on init run: ```bash -$ dapr uninstall --all +dapr uninstall --all ``` The above command can also be run when Dapr has been installed in a non-docker environment, it will only remove the installed binaries and the default dapr folder in that case. @@ -167,7 +170,7 @@ The above command can also be run when Dapr has been installed in a non-docker e If previously installed to a specific Docker network, Dapr can be uninstalled with the `--network` argument: ```bash -$ dapr uninstall --network dapr-network +dapr uninstall --network dapr-network ``` ### Install Dapr on Kubernetes @@ -176,7 +179,7 @@ The init command will install Dapr to a Kubernetes cluster. For more advanced us *Note: The default namespace is dapr-system. The installation will appear under the name `dapr` for Helm* -``` +```bash $ dapr init -k ⌛ Making the jump to hyperspace... @@ -190,52 +193,52 @@ $ dapr init -k All available [Helm Chart values](https://github.com/dapr/dapr/tree/master/charts/dapr#configuration) can be set by using the `--set` flag: -``` -$ dapr init -k --set global.tag=1.0.0 --set dapr_operator.logLevel=error +```bash +dapr init -k --set global.tag=1.0.0 --set dapr_operator.logLevel=error ``` #### Installing to a custom namespace -``` -$ dapr init -k -n my-namespace +```bash +dapr init -k -n my-namespace ``` #### Installing with a highly available control plane config -``` -$ dapr init -k --enable-ha=true +```bash +dapr init -k --enable-ha=true ``` #### Installing with mTLS disabled -``` -$ dapr init -k --enable-mtls=false +```bash +dapr init -k --enable-mtls=false ``` #### Waiting for the Helm install to complete (default timeout is 300s/5m) -``` -$ dapr init -k --wait --timeout 600 +```bash +dapr init -k --wait --timeout 600 ``` #### Uninstall Dapr on Kubernetes To remove Dapr from your Kubernetes cluster, use the `uninstall` command with `--kubernetes` flag or the `-k` shorthand. -``` -$ dapr uninstall -k +```bash +dapr uninstall -k ``` The default timeout is 300s/5m and can be overridden using the `--timeout` flag. -``` -$ dapr uninstall -k --timeout 600 +```bash +dapr uninstall -k --timeout 600 ``` To remove all Dapr Custom Resource Definitions: -``` -$ dapr uninstall -k --all +```bash +dapr uninstall -k --all ``` *Warning: this will remove any components, subscriptions or configurations that are applied in the cluster at the time of deletion.* @@ -244,8 +247,8 @@ $ dapr uninstall -k --all To perform a zero downtime upgrade of the Dapr control plane: -``` -$ dapr upgrade -k --runtime-version=1.0.0 +```bash +dapr upgrade -k --runtime-version=1.0.0 ``` The example above shows how to upgrade from your current version to version `1.0.0`. @@ -254,8 +257,8 @@ The example above shows how to upgrade from your current version to version `1.0 All available [Helm Chart values](https://github.com/dapr/dapr/tree/master/charts/dapr#configuration) can be set by using the `--set` flag: -``` -$ dapr upgrade -k --runtime-version=1.0.0 --set global.tag=my-tag --set dapr_operator.logLevel=error +```bash +dapr upgrade -k --runtime-version=1.0.0 --set global.tag=my-tag --set dapr_operator.logLevel=error ``` *Note: do not use the `dapr upgrade` command if you're upgrading from 0.x versions of Dapr* @@ -267,33 +270,33 @@ Logs from both the Dapr Runtime and your app will be displayed in real time! Example of launching Dapr with a node app: -``` -$ dapr run --app-id nodeapp node app.js +```bash +dapr run --app-id nodeapp node app.js ``` Example of launching Dapr with a node app listening on port 3000: -``` -$ dapr run --app-id nodeapp --app-port 3000 node app.js +```bash +dapr run --app-id nodeapp --app-port 3000 node app.js ``` Example of launching Dapr on HTTP port 6000: -``` -$ dapr run --app-id nodeapp --app-port 3000 --dapr-http-port 6000 node app.js +```bash +dapr run --app-id nodeapp --app-port 3000 --dapr-http-port 6000 node app.js ``` Example of launching Dapr on gRPC port 50002: -``` -$ dapr run --app-id nodeapp --app-port 3000 --dapr-grpc-port 50002 node app.js +```bash +dapr run --app-id nodeapp --app-port 3000 --dapr-grpc-port 50002 node app.js ``` Example of launching Dapr within a specific Docker network: ```bash -$ dapr init --network dapr-network -$ dapr run --app-id nodeapp --placement-host-address dapr_placement node app.js +dapr init --network dapr-network +dapr run --app-id nodeapp --placement-host-address dapr_placement node app.js ``` > Note: When in a specific Docker network, the Redis, Zipkin and placement service containers are given specific network aliases, `dapr_redis`, `dapr_zipkin` and `dapr_placement`, respectively. The default configuration files reflect the network alias rather than `localhost` when a docker network is specified. @@ -302,7 +305,7 @@ $ dapr run --app-id nodeapp --placement-host-address dapr_placement node app.js If your app uses gRPC instead of HTTP to receive Dapr events, run the CLI with the following command: -``` +```bash dapr run --app-id nodeapp --app-protocol grpc --app-port 6000 node app.js ``` @@ -315,8 +318,8 @@ This sample assumes your app is listening on port 3000. Launch Dapr and your app: -``` -$ dapr run --app-id nodeapp --app-port 3000 node app.js +```bash +dapr run --app-id nodeapp --app-port 3000 node app.js ``` Publish a message: @@ -327,13 +330,15 @@ The `--pubsub` parameter takes in the name of the pub/sub. The default name of Publish a message: * Linux/Mac + ```bash -$ dapr publish --publish-app-id nodeapp --pubsub pubsub --topic myevent --data '{ "name": "yoda" }' +dapr publish --publish-app-id nodeapp --pubsub pubsub --topic myevent --data '{ "name": "yoda" }' ``` * Windows -```bash -C:> dapr publish --publish-app-id nodeapp --pubsub pubsub --topic myevent --data "{ \"name\": \"yoda\" }" + +```powershell +dapr publish --publish-app-id nodeapp --pubsub pubsub --topic myevent --data "{ \"name\": \"yoda\" }" ``` ### Invoking @@ -343,83 +348,83 @@ For this sample, we'll assume a node app listening on port 3000 with a ```/mymet Launch Dapr and your app: -``` -$ dapr run --app-id nodeapp --app-port 3000 node app.js +```bash +dapr run --app-id nodeapp --app-port 3000 node app.js ``` Note: To choose a non-default components folder, use the --components-path option. Invoke your app: -``` -$ dapr invoke --app-id nodeapp --method mymethod +```bash +dapr invoke --app-id nodeapp --method mymethod ``` Specify a verb: By default, Dapr will use the `POST` verb. If your app uses Dapr for gRPC, you should use `POST`. -``` -$ dapr invoke --app-id nodeapp --method mymethod --verb GET +```bash +dapr invoke --app-id nodeapp --method mymethod --verb GET ``` ### List To list all Dapr instances running on your machine: -``` -$ dapr list +```bash +dapr list ``` To list all Dapr instances running in a Kubernetes cluster: -``` -$ dapr list --kubernetes +```bash +dapr list --kubernetes ``` To list all Dapr instances but return output as JSON or YAML (e.g. for consumption by other tools): -``` -$ dapr list --output json -$ dapr list --output yaml +```bash +dapr list --output json +dapr list --output yaml ``` ### Check system services (control plane) status Check Dapr's system services (control plane) health status in a Kubernetes cluster: -``` -$ dapr status --kubernetes +```bash +dapr status --kubernetes ``` ### Check mTLS status To check if Mutual TLS is enabled in your Kubernetes cluster: -``` -$ dapr mtls --kubernetes +```bash +dapr mtls --kubernetes ``` ### Export TLS certificates To export the root cert, issuer cert and issuer key created by Dapr from a Kubernetes cluster to a local path: -``` -$ dapr mtls export +```bash +dapr mtls export ``` This will save the certs to the working directory. To specify a custom directory: -``` -$ dapr mtls export -o certs +```bash +dapr mtls export -o certs ``` ### Check root certificate expiry -``` -$ dapr mtls expiry +```bash +dapr mtls expiry ``` This can be used when upgrading to a newer version of Dapr, as it's recommended to carry over the existing certs for a zero downtime upgrade. @@ -428,16 +433,16 @@ This can be used when upgrading to a newer version of Dapr, as it's recommended To list all Dapr components on Kubernetes: -``` -$ dapr components --kubernetes +```bash +dapr components --kubernetes ``` ### Use non-default Components Path To use a custom path for component definitions -``` -$ dapr run --components-path [custom path] +```bash +dapr run --components-path [custom path] ``` @@ -445,8 +450,8 @@ $ dapr run --components-path [custom path] To list all Dapr configurations on Kubernetes: -``` -$ dapr configurations --kubernetes +```bash +dapr configurations --kubernetes ``` ### Stop @@ -454,42 +459,45 @@ $ dapr configurations --kubernetes Use ```dapr list``` to get a list of all running instances. To stop a Dapr app on your machine: +```bash +dapr stop myAppID ``` -$ dapr stop myAppID -``` + You can also stop multiple Dapr apps + +```bash +dapr stop myAppID1 myAppID2 ``` -$ dapr stop myAppID1 myAppID2 -``` + ### Enable profiling In order to enable profiling, use the `enable-profiling` flag: -``` -$ dapr run --app-id nodeapp --app-port 3000 node app.js --enable-profiling +```bash +dapr run --app-id nodeapp --app-port 3000 node app.js --enable-profiling ``` Dapr will automatically assign a profile port for you. If you want to manually assign a profiling port, use the `profile-port` flag: -``` -$ dapr run --app-id nodeapp --app-port 3000 node app.js --enable-profiling --profile-port 7777 +```bash +dapr run --app-id nodeapp --app-port 3000 node app.js --enable-profiling --profile-port 7777 ``` ### Set metrics server port To change the metrics server port used by Dapr, set the `metrics-port` flag: -``` -$ dapr run --app-id nodeapp --app-port 3000 node app.js --metrics-port 5040 +```bash +dapr run --app-id nodeapp --app-port 3000 node app.js --metrics-port 5040 ``` ### Set log level In order to set the Dapr runtime log verbosity level, use the `log-level` flag: -``` -$ dapr run --app-id nodeapp --app-port 3000 node app.js --log-level debug +```bash +dapr run --app-id nodeapp --app-port 3000 node app.js --log-level debug ``` This sets the Dapr log level to `debug`. @@ -499,8 +507,8 @@ The default is `info`. If your app is listening on `https` or has a gRPC TLS configuration enabled, use the following `app-ssl` flag: -``` -$ dapr run --app-id nodeapp --app-port 3000 node app.js --app-ssl +```bash +dapr run --app-id nodeapp --app-port 3000 node app.js --app-ssl ``` This will have Dapr invoke the app over an insecure SSL channel. @@ -511,16 +519,32 @@ The default is false. You can run Dapr's sidecar only (`daprd`) by omitting the application's command in the end: -``` -$ dapr run --app-id myapp --dapr-http-port 3005 --dapr-grpc-port 50001 +```bash +dapr run --app-id myapp --dapr-http-port 3005 --dapr-grpc-port 50001 ``` ### Generate shell completion scripts To generate shell completion scripts: +```bash +dapr completion +``` + +### Enable Unix domain socket + +In order to enable Unix domain socket to connect Dapr API server, use the `--unix-domain-socket` flag: + ``` -$ dapr completion +$ dapr run --app-id nodeapp --unix-domain-socket node app.js +``` + +Dapr will automatically create a Unix domain socket to connect Dapr API server. + +If you want to invoke your app, also use this flag: + +``` +$ dapr invoke --app-id nodeapp --unix-domain-socket --method mymethod ``` ### Enable Unix domain socket diff --git a/cmd/run.go b/cmd/run.go index e25d7a361..cad569e23 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -57,16 +57,23 @@ var RunCmd = &cobra.Command{ Use: "run", Short: "Run Dapr and (optionally) your application side by side. Supported platforms: Self-hosted", Example: ` -# Run a .NET application: - dapr run --app-id myapp --app-port 5000 -- dotnet run -# Run a Java application: - dapr run --app-id myapp -- java -jar myapp.jar -# Run a NodeJs application that listens to port 3000: - dapr run --app-id myapp --app-port 3000 -- node myapp.js -# Run a Python application: - dapr run --app-id myapp -- python myapp.py -# Run sidecar only: - dapr run --app-id myapp +# Run a .NET application +dapr run --app-id myapp --app-port 5000 -- dotnet run + +# Run a Java application +dapr run --app-id myapp -- java -jar myapp.jar + +# Run a NodeJs application that listens to port 3000 +dapr run --app-id myapp --app-port 3000 -- node myapp.js + +# Run a Python application +dapr run --app-id myapp -- python myapp.py + +# Run sidecar only +dapr run --app-id myapp + +# Run a gRPC application written in Go (listening on port 3000) +dapr run --app-id myapp --app-port 3000 --app-protocol grpc -- go run main.go `, Args: cobra.MinimumNArgs(0), PreRun: func(cmd *cobra.Command, args []string) { diff --git a/pkg/standalone/run.go b/pkg/standalone/run.go index 6ac9354eb..e4d259f7f 100644 --- a/pkg/standalone/run.go +++ b/pkg/standalone/run.go @@ -16,10 +16,11 @@ package standalone import ( "fmt" "io/ioutil" + "net" "os" "os/exec" + "reflect" "runtime" - "strconv" "strings" "github.com/Pallinder/sillyname-go" @@ -34,133 +35,251 @@ const sentryDefaultAddress = "localhost:50001" // RunConfig represents the application configuration parameters. type RunConfig struct { - AppID string - AppPort int - HTTPPort int - GRPCPort int - ConfigFile string - Protocol string + AppID string `env:"APP_ID" arg:"app-id"` + AppPort int `env:"APP_PORT" arg:"app-port"` + HTTPPort int `env:"DAPR_HTTP_PORT" arg:"dapr-http-port"` + GRPCPort int `env:"DAPR_GRPC_PORT" arg:"dapr-grpc-port"` + ConfigFile string `arg:"config"` + Protocol string `arg:"app-protocol"` Arguments []string - EnableProfiling bool - ProfilePort int - LogLevel string - MaxConcurrency int - PlacementHostAddr string - ComponentsPath string - AppSSL bool - MetricsPort int - MaxRequestBodySize int - UnixDomainSocket string + EnableProfiling bool `arg:"enable-profiling"` + ProfilePort int `arg:"profile-port"` + LogLevel string `arg:"log-level"` + MaxConcurrency int `arg:"app-max-concurrency"` + PlacementHostAddr string `arg:"placement-host-address"` + ComponentsPath string `arg:"components-path"` + AppSSL bool `arg:"app-ssl"` + MetricsPort int `env:"DAPR_METRICS_PORT" arg:"metrics-port"` + MaxRequestBodySize int `arg:"dapr-http-max-request-size"` + UnixDomainSocket string `arg:"unix-domain-socket"` } -// RunOutput represents the run output. -type RunOutput struct { - DaprCMD *exec.Cmd - DaprHTTPPort int - DaprGRPCPort int - AppID string - AppCMD *exec.Cmd +func (meta *DaprMeta) newAppID() string { + for { + appID := strings.ReplaceAll(sillyname.GenerateStupidName(), " ", "-") + if !meta.idExists(appID) { + return appID + } + } } -func getDaprCommand(appID string, daprHTTPPort int, daprGRPCPort int, appPort int, configFile, protocol string, enableProfiling bool, - profilePort int, logLevel string, maxConcurrency int, placementHostAddr string, componentsPath string, appSSL bool, metricsPort int, requestBodySize int, unixDomainSocket string) (*exec.Cmd, int, int, int, error) { - if daprHTTPPort < 0 { +func (config *RunConfig) validateComponentPath() error { + _, err := os.Stat(config.ComponentsPath) + if err != nil { + return err + } + componentsLoader := components.NewStandaloneComponents(modes.StandaloneConfig{ComponentsPath: config.ComponentsPath}) + _, err = componentsLoader.LoadComponents() + if err != nil { + return err + } + return nil +} + +func (config *RunConfig) validatePlacementHostAddr() error { + placementHostAddr := config.PlacementHostAddr + if len(placementHostAddr) == 0 { + placementHostAddr = "localhost" + } + if indx := strings.Index(placementHostAddr, ":"); indx == -1 { + if runtime.GOOS == daprWindowsOS { + placementHostAddr = fmt.Sprintf("%s:6050", placementHostAddr) + } else { + placementHostAddr = fmt.Sprintf("%s:50005", placementHostAddr) + } + } + config.PlacementHostAddr = placementHostAddr + return nil +} + +func (config *RunConfig) validatePort(portName string, portPtr *int, meta *DaprMeta) error { + if *portPtr <= 0 { port, err := freeport.GetFreePort() if err != nil { - return nil, -1, -1, -1, err + return err } + *portPtr = port + return nil + } - daprHTTPPort = port + if meta.portExists(*portPtr) { + return fmt.Errorf("invalid configuration for %s. Port %v is not available", portName, *portPtr) } + return nil +} - if daprGRPCPort < 0 { - grpcPort, err := freeport.GetFreePort() - if err != nil { - return nil, -1, -1, -1, err - } +func (config *RunConfig) validate() error { + meta, err := newDaprMeta() + if err != nil { + return err + } + + if config.AppID == "" { + config.AppID = meta.newAppID() + } + + err = config.validateComponentPath() + if err != nil { + return err + } + + if config.AppPort < 0 { + config.AppPort = 0 + } - daprGRPCPort = grpcPort + err = config.validatePort("HTTPPort", &config.HTTPPort, meta) + if err != nil { + return err + } + + err = config.validatePort("GRPCPort", &config.GRPCPort, meta) + if err != nil { + return err } - if metricsPort < 0 { - var err error - metricsPort, err = freeport.GetFreePort() + err = config.validatePort("MetricsPort", &config.MetricsPort, meta) + if err != nil { + return err + } + + if config.EnableProfiling { + err = config.validatePort("ProfilePort", &config.ProfilePort, meta) if err != nil { - return nil, -1, -1, -1, err + return err } } - if maxConcurrency < 1 { - maxConcurrency = -1 + if config.MaxConcurrency < 1 { + config.MaxConcurrency = -1 + } + if config.MaxRequestBodySize < 0 { + config.MaxRequestBodySize = -1 } - if requestBodySize < 0 { - requestBodySize = -1 + err = config.validatePlacementHostAddr() + if err != nil { + return err } + return nil +} - daprCMD := binaryFilePath(defaultDaprBinPath(), "daprd") +type DaprMeta struct { + ExistingIDs map[string]bool + ExistingPorts map[int]bool +} - args := []string{ - "--app-id", appID, - "--dapr-http-port", strconv.Itoa(daprHTTPPort), - "--dapr-grpc-port", strconv.Itoa(daprGRPCPort), - "--log-level", logLevel, - "--app-max-concurrency", strconv.Itoa(maxConcurrency), - "--app-protocol", protocol, - "--components-path", componentsPath, - "--metrics-port", strconv.Itoa(metricsPort), - "--dapr-http-max-request-size", strconv.Itoa(requestBodySize), +func (meta *DaprMeta) idExists(id string) bool { + _, ok := meta.ExistingIDs[id] + return ok +} + +func (meta *DaprMeta) portExists(port int) bool { + if port <= 0 { + return false } - if appPort > -1 { - args = append(args, "--app-port", strconv.Itoa(appPort)) + _, ok := meta.ExistingPorts[port] + if ok { + return true } - args = append(args, "--placement-host-address") - // if placementHostAddr does not contain port, add default port value - if indx := strings.Index(placementHostAddr, ":"); indx == -1 { - if runtime.GOOS == daprWindowsOS { - args = append(args, fmt.Sprintf("%s:6050", placementHostAddr)) - } else { - args = append(args, fmt.Sprintf("%s:50005", placementHostAddr)) - } - } else { - args = append(args, placementHostAddr) + // try to listen on the port + listener, err := net.Listen("tcp", fmt.Sprintf(":%v", port)) + if err != nil { + return true + } + listener.Close() + + meta.ExistingPorts[port] = true + return false +} + +func newDaprMeta() (*DaprMeta, error) { + meta := DaprMeta{} + meta.ExistingIDs = make(map[string]bool) + meta.ExistingPorts = make(map[int]bool) + dapr, err := List() + if err != nil { + return nil, err + } + for _, instance := range dapr { + meta.ExistingIDs[instance.AppID] = true + meta.ExistingPorts[instance.AppPort] = true + meta.ExistingPorts[instance.HTTPPort] = true + meta.ExistingPorts[instance.GRPCPort] = true } + return &meta, nil +} + +func (config *RunConfig) getArgs() []string { + args := []string{} + schema := reflect.ValueOf(*config) + for i := 0; i < schema.NumField(); i++ { + valueField := schema.Field(i).Interface() + typeField := schema.Type().Field(i) + key := typeField.Tag.Get("arg") + if len(key) == 0 { + continue + } + key = "--" + key - if configFile != "" { - args = append(args, "--config", configFile) - sentryAddress := mtlsEndpoint(configFile) + switch valueField.(type) { + case bool: + if valueField == true { + args = append(args, key) + } + default: + value := fmt.Sprintf("%v", reflect.ValueOf(valueField)) + if len(value) != 0 { + args = append(args, key, value) + } + } + } + if config.ConfigFile != "" { + sentryAddress := mtlsEndpoint(config.ConfigFile) if sentryAddress != "" { // mTLS is enabled locally, set it up args = append(args, "--enable-mtls", "--sentry-address", sentryAddress) } } - if enableProfiling { - if profilePort == -1 { - pp, err := freeport.GetFreePort() - if err != nil { - return nil, -1, -1, -1, err - } - profilePort = pp - } + return args +} - args = append( - args, - "--enable-profiling", - "--profile-port", strconv.Itoa(profilePort)) - } +func (config *RunConfig) getEnv() []string { + env := []string{} + schema := reflect.ValueOf(*config) + for i := 0; i < schema.NumField(); i++ { + valueField := schema.Field(i).Interface() + typeField := schema.Type().Field(i) + key := typeField.Tag.Get("env") + if len(key) == 0 { + continue + } + if value, ok := valueField.(int); ok && value <= 0 { + // ignore unset numeric variables + continue + } - if appSSL { - args = append(args, "--app-ssl") + value := fmt.Sprintf("%v", reflect.ValueOf(valueField)) + env = append(env, fmt.Sprintf("%s=%v", key, value)) } + return env +} - if unixDomainSocket != "" { - args = append(args, "--unix-domain-socket", unixDomainSocket) - } +// RunOutput represents the run output. +type RunOutput struct { + DaprCMD *exec.Cmd + DaprHTTPPort int + DaprGRPCPort int + AppID string + AppCMD *exec.Cmd +} +func getDaprCommand(config *RunConfig) (*exec.Cmd, error) { + daprCMD := binaryFilePath(defaultDaprBinPath(), "daprd") + args := config.getArgs() cmd := exec.Command(daprCMD, args...) - return cmd, daprHTTPPort, daprGRPCPort, metricsPort, nil + return cmd, nil } func mtlsEndpoint(configFile string) string { @@ -185,80 +304,43 @@ func mtlsEndpoint(configFile string) string { return "" } -func getAppCommand(httpPort, grpcPort, metricsPort int, command string, args []string) (*exec.Cmd, error) { - cmd := exec.Command(command, args...) - cmd.Env = os.Environ() - cmd.Env = append( - cmd.Env, - fmt.Sprintf("DAPR_HTTP_PORT=%v", httpPort), - fmt.Sprintf("DAPR_GRPC_PORT=%v", grpcPort), - fmt.Sprintf("DAPR_METRICS_PORT=%v", metricsPort)) - - return cmd, nil -} +func getAppCommand(config *RunConfig) *exec.Cmd { + argCount := len(config.Arguments) -func Run(config *RunConfig) (*RunOutput, error) { - appID := config.AppID - if appID == "" { - appID = strings.ReplaceAll(sillyname.GenerateStupidName(), " ", "-") + if argCount == 0 { + return nil } + command := config.Arguments[0] - _, err := os.Stat(config.ComponentsPath) - if err != nil { - return nil, err + args := []string{} + if argCount > 1 { + args = config.Arguments[1:] } - dapr, err := List() - if err != nil { - return nil, err - } + cmd := exec.Command(command, args...) + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, config.getEnv()...) - for _, a := range dapr { - if appID == a.AppID { - return nil, fmt.Errorf("dapr with ID %s is already running", appID) - } - } + return cmd +} - componentsLoader := components.NewStandaloneComponents(modes.StandaloneConfig{ComponentsPath: config.ComponentsPath}) - _, err = componentsLoader.LoadComponents() +func Run(config *RunConfig) (*RunOutput, error) { + err := config.validate() if err != nil { return nil, err } - daprCMD, daprHTTPPort, daprGRPCPort, metricsPort, err := getDaprCommand(appID, config.HTTPPort, config.GRPCPort, config.AppPort, config.ConfigFile, config.Protocol, config.EnableProfiling, config.ProfilePort, config.LogLevel, config.MaxConcurrency, config.PlacementHostAddr, config.ComponentsPath, config.AppSSL, config.MetricsPort, config.MaxRequestBodySize, config.UnixDomainSocket) + daprCMD, err := getDaprCommand(config) if err != nil { return nil, err } - for _, a := range dapr { - if daprHTTPPort == a.HTTPPort { - return nil, fmt.Errorf("there's already a Dapr instance running with http port %v", daprHTTPPort) - } else if daprGRPCPort == a.GRPCPort { - return nil, fmt.Errorf("there's already a Dapr instance running with gRPC port %v", daprGRPCPort) - } - } - - argCount := len(config.Arguments) - runArgs := []string{} - var appCMD *exec.Cmd - - if argCount > 0 { - cmd := config.Arguments[0] - if len(config.Arguments) > 1 { - runArgs = config.Arguments[1:] - } - - appCMD, err = getAppCommand(daprHTTPPort, daprGRPCPort, metricsPort, cmd, runArgs) - if err != nil { - return nil, err - } - } - + var appCMD *exec.Cmd = getAppCommand(config) return &RunOutput{ DaprCMD: daprCMD, AppCMD: appCMD, - AppID: appID, - DaprHTTPPort: daprHTTPPort, - DaprGRPCPort: daprGRPCPort, + AppID: config.AppID, + DaprHTTPPort: config.HTTPPort, + DaprGRPCPort: config.GRPCPort, }, nil } diff --git a/pkg/standalone/run_test.go b/pkg/standalone/run_test.go index 7bf84c3fe..a0877f322 100644 --- a/pkg/standalone/run_test.go +++ b/pkg/standalone/run_test.go @@ -14,8 +14,8 @@ limitations under the License. package standalone import ( + "fmt" "os" - "runtime" "strings" "testing" @@ -57,7 +57,7 @@ func assertArgumentNotEqual(t *testing.T, key string, expectedValue string, args func setupRun(t *testing.T) { componentsDir := DefaultComponentsDirPath() configFile := DefaultConfigFilePath() - err := os.MkdirAll(componentsDir, 0700) + err := os.MkdirAll(componentsDir, 0o700) assert.Equal(t, nil, err, "Unable to setup components dir before running test") file, err := os.Create(configFile) file.Close() @@ -71,6 +71,62 @@ func tearDownRun(t *testing.T) { assert.Equal(t, nil, err, "Unable to delete default config file after running test") } +func assertCommonArgs(t *testing.T, basicConfig *RunConfig, output *RunOutput) { + assert.NotNil(t, output) + + assert.Equal(t, "MyID", output.AppID) + assert.Equal(t, 8000, output.DaprHTTPPort) + assert.Equal(t, 50001, output.DaprGRPCPort) + + assert.Contains(t, output.DaprCMD.Args[0], "daprd") + assertArgumentEqual(t, "app-id", "MyID", output.DaprCMD.Args) + assertArgumentEqual(t, "dapr-http-port", "8000", output.DaprCMD.Args) + assertArgumentEqual(t, "dapr-grpc-port", "50001", output.DaprCMD.Args) + assertArgumentEqual(t, "log-level", basicConfig.LogLevel, output.DaprCMD.Args) + assertArgumentEqual(t, "app-max-concurrency", "-1", output.DaprCMD.Args) + assertArgumentEqual(t, "app-protocol", "http", output.DaprCMD.Args) + assertArgumentEqual(t, "app-port", "3000", output.DaprCMD.Args) + assertArgumentEqual(t, "components-path", DefaultComponentsDirPath(), output.DaprCMD.Args) + assertArgumentEqual(t, "app-ssl", "", output.DaprCMD.Args) + assertArgumentEqual(t, "metrics-port", "9001", output.DaprCMD.Args) + assertArgumentEqual(t, "dapr-http-max-request-size", "-1", output.DaprCMD.Args) +} + +func assertAppEnv(t *testing.T, config *RunConfig, output *RunOutput) { + envSet := make(map[string]bool) + for _, env := range output.AppCMD.Env { + envSet[env] = true + } + + expectedEnvSet := getEnvSet(config) + for _, env := range expectedEnvSet { + _, found := envSet[env] + if !found { + assert.Fail(t, "Missing environment variable. Expected to have "+env) + } + } +} + +func getEnvSet(config *RunConfig) []string { + set := []string{ + getEnv("DAPR_GRPC_PORT", config.GRPCPort), + getEnv("DAPR_HTTP_PORT", config.HTTPPort), + getEnv("DAPR_METRICS_PORT", config.MetricsPort), + getEnv("APP_ID", config.AppID), + } + if config.AppPort > 0 { + set = append(set, getEnv("APP_PORT", config.AppPort)) + } + if config.EnableProfiling { + set = append(set, getEnv("DAPR_PROFILE_PORT", config.ProfilePort)) + } + return set +} + +func getEnv(key string, value interface{}) string { + return fmt.Sprintf("%s=%v", key, value) +} + func TestRun(t *testing.T) { // Setup the components directory which is done at init time setupRun(t) @@ -88,7 +144,6 @@ func TestRun(t *testing.T) { EnableProfiling: false, ProfilePort: 9090, Protocol: "http", - PlacementHostAddr: "localhost", ComponentsPath: DefaultComponentsDirPath(), AppSSL: true, MetricsPort: 9001, @@ -97,34 +152,12 @@ func TestRun(t *testing.T) { t.Run("run happy http", func(t *testing.T) { output, err := Run(basicConfig) - assert.Nil(t, err) - assert.NotNil(t, output) - - assert.Equal(t, "MyID", output.AppID) - assert.Equal(t, 8000, output.DaprHTTPPort) - assert.Equal(t, 50001, output.DaprGRPCPort) - - assert.Contains(t, output.DaprCMD.Args[0], "daprd") - assertArgumentEqual(t, "app-id", "MyID", output.DaprCMD.Args) - assertArgumentEqual(t, "dapr-http-port", "8000", output.DaprCMD.Args) - assertArgumentEqual(t, "dapr-grpc-port", "50001", output.DaprCMD.Args) - assertArgumentEqual(t, "log-level", "WARN", output.DaprCMD.Args) - assertArgumentEqual(t, "app-max-concurrency", "-1", output.DaprCMD.Args) - assertArgumentEqual(t, "app-protocol", "http", output.DaprCMD.Args) - assertArgumentEqual(t, "app-port", "3000", output.DaprCMD.Args) - assertArgumentEqual(t, "components-path", DefaultComponentsDirPath(), output.DaprCMD.Args) - assertArgumentEqual(t, "app-ssl", "", output.DaprCMD.Args) - assertArgumentEqual(t, "metrics-port", "9001", output.DaprCMD.Args) - if runtime.GOOS == "windows" { - assertArgumentEqual(t, "placement-host-address", "localhost:6050", output.DaprCMD.Args) - } else { - assertArgumentEqual(t, "placement-host-address", "localhost:50005", output.DaprCMD.Args) - } - assertArgumentEqual(t, "dapr-http-max-request-size", "-1", output.DaprCMD.Args) + assertCommonArgs(t, basicConfig, output) assert.Equal(t, "MyCommand", output.AppCMD.Args[0]) assert.Equal(t, "--my-arg", output.AppCMD.Args[1]) + assertAppEnv(t, basicConfig, output) }) t.Run("run without app command", func(t *testing.T) { @@ -132,31 +165,10 @@ func TestRun(t *testing.T) { basicConfig.LogLevel = "INFO" basicConfig.ConfigFile = DefaultConfigFilePath() output, err := Run(basicConfig) - assert.Nil(t, err) - assert.NotNil(t, output) - assert.Equal(t, "MyID", output.AppID) - assert.Equal(t, 8000, output.DaprHTTPPort) - assert.Equal(t, 50001, output.DaprGRPCPort) - - assert.Contains(t, output.DaprCMD.Args[0], "daprd") - assertArgumentEqual(t, "app-id", "MyID", output.DaprCMD.Args) - assertArgumentEqual(t, "dapr-http-port", "8000", output.DaprCMD.Args) - assertArgumentEqual(t, "dapr-grpc-port", "50001", output.DaprCMD.Args) - assertArgumentEqual(t, "log-level", "INFO", output.DaprCMD.Args) - assertArgumentEqual(t, "app-max-concurrency", "-1", output.DaprCMD.Args) - assertArgumentEqual(t, "app-protocol", "http", output.DaprCMD.Args) - assertArgumentEqual(t, "app-port", "3000", output.DaprCMD.Args) + assertCommonArgs(t, basicConfig, output) assertArgumentEqual(t, "config", DefaultConfigFilePath(), output.DaprCMD.Args) - assertArgumentEqual(t, "components-path", DefaultComponentsDirPath(), output.DaprCMD.Args) - if runtime.GOOS == "windows" { - assertArgumentEqual(t, "placement-host-address", "localhost:6050", output.DaprCMD.Args) - } else { - assertArgumentEqual(t, "placement-host-address", "localhost:50005", output.DaprCMD.Args) - } - assertArgumentEqual(t, "dapr-http-max-request-size", "-1", output.DaprCMD.Args) - assert.Nil(t, output.AppCMD) }) @@ -173,14 +185,4 @@ func TestRun(t *testing.T) { assertArgumentNotEqual(t, "grpc-port", "-1", output.DaprCMD.Args) assertArgumentNotEqual(t, "metrics-port", "-1", output.DaprCMD.Args) }) - - t.Run("run with specified placement-host port", func(t *testing.T) { - basicConfig.PlacementHostAddr = "localhost:12345" - output, err := Run(basicConfig) - - assert.Nil(t, err) - assert.NotNil(t, output) - - assertArgumentEqual(t, "placement-host-address", "localhost:12345", output.DaprCMD.Args) - }) }