mirror of https://github.com/dapr/cli.git
1106 lines
43 KiB
Go
1106 lines
43 KiB
Go
/*
|
|
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 cmd
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"golang.org/x/mod/semver"
|
|
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/pflag"
|
|
"github.com/spf13/viper"
|
|
|
|
daprRuntime "github.com/dapr/dapr/pkg/runtime"
|
|
|
|
"github.com/dapr/cli/pkg/kubernetes"
|
|
"github.com/dapr/cli/pkg/metadata"
|
|
"github.com/dapr/cli/pkg/print"
|
|
runExec "github.com/dapr/cli/pkg/runexec"
|
|
"github.com/dapr/cli/pkg/runfileconfig"
|
|
"github.com/dapr/cli/pkg/standalone"
|
|
daprsyscall "github.com/dapr/cli/pkg/syscall"
|
|
"github.com/dapr/cli/utils"
|
|
)
|
|
|
|
var (
|
|
appPort int
|
|
profilePort int
|
|
appID string
|
|
configFile string
|
|
port int
|
|
grpcPort int
|
|
internalGRPCPort int
|
|
maxConcurrency int
|
|
enableProfiling bool
|
|
logLevel string
|
|
protocol string
|
|
componentsPath string
|
|
resourcesPaths []string
|
|
appSSL bool
|
|
metricsPort int
|
|
maxRequestBodySize string
|
|
readBufferSize string
|
|
unixDomainSocket string
|
|
enableAppHealth bool
|
|
appHealthPath string
|
|
appHealthInterval int
|
|
appHealthTimeout int
|
|
appHealthThreshold int
|
|
enableAPILogging bool
|
|
apiListenAddresses string
|
|
schedulerHostAddress string
|
|
runFilePath string
|
|
appChannelAddress string
|
|
enableRunK8s bool
|
|
)
|
|
|
|
const (
|
|
defaultRunTemplateFileName = "dapr.yaml"
|
|
runtimeWaitTimeoutInSeconds = 60
|
|
)
|
|
|
|
// Flags that are compatible with --run-file
|
|
var runFileCompatibleFlags = []string{
|
|
"kubernetes",
|
|
"help",
|
|
"version",
|
|
"runtime-path",
|
|
"log-as-json",
|
|
}
|
|
|
|
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 gRPC application written in Go (listening on port 3000)
|
|
dapr run --app-id myapp --app-port 3000 --app-protocol grpc -- go run main.go
|
|
|
|
# Run a gRPC application written in Go (listening on port 3000) with a different app channel address
|
|
dapr run --app-id myapp --app-port 3000 --app-channel-address localhost --app-protocol grpc -- go run main.go
|
|
|
|
|
|
# Run sidecar only specifying dapr runtime installation directory
|
|
dapr run --app-id myapp --runtime-path /usr/local/dapr
|
|
|
|
# Run multiple apps by providing path of a run config file
|
|
dapr run --run-file dapr.yaml
|
|
|
|
# Run multiple apps by providing a directory path containing the run config file(dapr.yaml)
|
|
dapr run --run-file /path/to/directory
|
|
|
|
# Run multiple apps by providing config via stdin
|
|
cat dapr.template.yaml | envsubst | dapr run --run-file -
|
|
|
|
# Run multiple apps in Kubernetes by proficing path of a run config file
|
|
dapr run --run-file dapr.yaml -k
|
|
|
|
# Run multiple apps in Kubernetes by providing a directory path containing the run config file(dapr.yaml)
|
|
dapr run --run-file /path/to/directory -k
|
|
`,
|
|
Args: cobra.MinimumNArgs(0),
|
|
PreRun: func(cmd *cobra.Command, args []string) {
|
|
viper.BindPFlag("placement-host-address", cmd.Flags().Lookup("placement-host-address"))
|
|
},
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
if len(runFilePath) > 0 {
|
|
// Check for incompatible flags
|
|
incompatibleFlags := detectIncompatibleFlags(cmd)
|
|
if len(incompatibleFlags) > 0 {
|
|
// Print warning message about incompatible flags
|
|
warningMsg := "The following flags are ignored when using --run-file and should be configured in the run file instead: " + strings.Join(incompatibleFlags, ", ")
|
|
print.WarningStatusEvent(os.Stdout, warningMsg)
|
|
}
|
|
|
|
runConfigFilePath, err := getRunFilePath(runFilePath)
|
|
if err != nil {
|
|
print.FailureStatusEvent(os.Stderr, "Failed to get run file path: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
executeRunWithAppsConfigFile(runConfigFilePath, enableRunK8s)
|
|
return
|
|
}
|
|
if len(args) == 0 {
|
|
fmt.Println(print.WhiteBold("WARNING: no application command found."))
|
|
}
|
|
|
|
daprDirPath, err := standalone.GetDaprRuntimePath(daprRuntimePath)
|
|
if err != nil {
|
|
print.FailureStatusEvent(os.Stderr, "Failed to get Dapr install directory: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Fallback to default config file if not specified.
|
|
if configFile == "" {
|
|
configFile = standalone.GetDaprConfigPath(daprDirPath)
|
|
}
|
|
|
|
// Fallback to default components directory if not specified.
|
|
if componentsPath == "" {
|
|
componentsPath = standalone.GetDaprComponentsPath(daprDirPath)
|
|
}
|
|
|
|
if unixDomainSocket != "" {
|
|
// TODO(@daixiang0): add Windows support.
|
|
if runtime.GOOS == string(windowsOsType) {
|
|
print.FailureStatusEvent(os.Stderr, "The unix-domain-socket option is not supported on Windows")
|
|
os.Exit(1)
|
|
} else {
|
|
// use unix domain socket means no port any more.
|
|
print.WarningStatusEvent(os.Stdout, "Unix domain sockets are currently a preview feature")
|
|
port = 0
|
|
grpcPort = 0
|
|
}
|
|
}
|
|
|
|
sharedRunConfig := &standalone.SharedRunConfig{
|
|
ConfigFile: configFile,
|
|
EnableProfiling: enableProfiling,
|
|
LogLevel: logLevel,
|
|
MaxConcurrency: maxConcurrency,
|
|
AppProtocol: protocol,
|
|
PlacementHostAddr: viper.GetString("placement-host-address"),
|
|
ComponentsPath: componentsPath,
|
|
ResourcesPaths: resourcesPaths,
|
|
AppSSL: appSSL,
|
|
MaxRequestBodySize: maxRequestBodySize,
|
|
HTTPReadBufferSize: readBufferSize,
|
|
EnableAppHealth: enableAppHealth,
|
|
AppHealthPath: appHealthPath,
|
|
AppHealthInterval: appHealthInterval,
|
|
AppHealthTimeout: appHealthTimeout,
|
|
AppHealthThreshold: appHealthThreshold,
|
|
EnableAPILogging: enableAPILogging,
|
|
APIListenAddresses: apiListenAddresses,
|
|
SchedulerHostAddress: schedulerHostAddress,
|
|
DaprdInstallPath: daprRuntimePath,
|
|
}
|
|
output, err := runExec.NewOutput(&standalone.RunConfig{
|
|
AppID: appID,
|
|
AppChannelAddress: appChannelAddress,
|
|
AppPort: appPort,
|
|
HTTPPort: port,
|
|
GRPCPort: grpcPort,
|
|
ProfilePort: profilePort,
|
|
Command: args,
|
|
MetricsPort: metricsPort,
|
|
UnixDomainSocket: unixDomainSocket,
|
|
InternalGRPCPort: internalGRPCPort,
|
|
SharedRunConfig: *sharedRunConfig,
|
|
})
|
|
if err != nil {
|
|
print.FailureStatusEvent(os.Stderr, err.Error())
|
|
os.Exit(1)
|
|
}
|
|
// TODO: In future release replace following logic with the refactored functions seen below.
|
|
|
|
sigCh := make(chan os.Signal, 1)
|
|
daprsyscall.SetupShutdownNotify(sigCh)
|
|
|
|
daprRunning := make(chan bool, 1)
|
|
appRunning := make(chan bool, 1)
|
|
|
|
go func() {
|
|
var startInfo string
|
|
if unixDomainSocket != "" {
|
|
startInfo = fmt.Sprintf(
|
|
"Starting Dapr with id %s. HTTP Socket: %v. gRPC Socket: %v.",
|
|
output.AppID,
|
|
utils.GetSocket(unixDomainSocket, output.AppID, "http"),
|
|
utils.GetSocket(unixDomainSocket, output.AppID, "grpc"))
|
|
} else {
|
|
startInfo = fmt.Sprintf(
|
|
"Starting Dapr with id %s. HTTP Port: %v. gRPC Port: %v",
|
|
output.AppID,
|
|
output.DaprHTTPPort,
|
|
output.DaprGRPCPort)
|
|
}
|
|
|
|
if (daprVer.RuntimeVersion != "edge") && (semver.Compare(fmt.Sprintf("v%v", daprVer.RuntimeVersion), "v1.14.0-rc.1") == -1) {
|
|
print.InfoStatusEvent(os.Stdout, "The scheduler is only compatible with dapr runtime 1.14 onwards.")
|
|
for i, arg := range output.DaprCMD.Args {
|
|
if strings.HasPrefix(arg, "--scheduler-host-address") {
|
|
output.DaprCMD.Args[i] = ""
|
|
}
|
|
}
|
|
}
|
|
print.InfoStatusEvent(os.Stdout, startInfo)
|
|
|
|
output.DaprCMD.Stdout = os.Stdout
|
|
output.DaprCMD.Stderr = os.Stderr
|
|
|
|
err = output.DaprCMD.Start()
|
|
if err != nil {
|
|
print.FailureStatusEvent(os.Stderr, err.Error())
|
|
os.Exit(1)
|
|
}
|
|
|
|
go func() {
|
|
daprdErr := output.DaprCMD.Wait()
|
|
|
|
if daprdErr != nil {
|
|
output.DaprErr = daprdErr
|
|
print.FailureStatusEvent(os.Stderr, "The daprd process exited with error code: %s", daprdErr.Error())
|
|
} else {
|
|
print.SuccessStatusEvent(os.Stdout, "Exited Dapr successfully")
|
|
}
|
|
sigCh <- os.Interrupt
|
|
}()
|
|
|
|
if appPort <= 0 {
|
|
// If app does not listen to port, we can check for Dapr's sidecar health before starting the app.
|
|
// Otherwise, it creates a deadlock.
|
|
sidecarUp := true
|
|
|
|
if unixDomainSocket != "" {
|
|
httpSocket := utils.GetSocket(unixDomainSocket, output.AppID, "http")
|
|
print.InfoStatusEvent(os.Stdout, "Checking if Dapr sidecar is listening on HTTP socket %v", httpSocket)
|
|
err = utils.IsDaprListeningOnSocket(httpSocket, time.Duration(runtimeWaitTimeoutInSeconds)*time.Second)
|
|
if err != nil {
|
|
sidecarUp = false
|
|
print.WarningStatusEvent(os.Stdout, "Dapr sidecar is not listening on HTTP socket: %s", err.Error())
|
|
}
|
|
|
|
grpcSocket := utils.GetSocket(unixDomainSocket, output.AppID, "grpc")
|
|
print.InfoStatusEvent(os.Stdout, "Checking if Dapr sidecar is listening on GRPC socket %v", grpcSocket)
|
|
err = utils.IsDaprListeningOnSocket(grpcSocket, time.Duration(runtimeWaitTimeoutInSeconds)*time.Second)
|
|
if err != nil {
|
|
sidecarUp = false
|
|
print.WarningStatusEvent(os.Stdout, "Dapr sidecar is not listening on GRPC socket: %s", err.Error())
|
|
}
|
|
|
|
} else {
|
|
print.InfoStatusEvent(os.Stdout, "Checking if Dapr sidecar is listening on HTTP port %v", output.DaprHTTPPort)
|
|
err = utils.IsDaprListeningOnPort(output.DaprHTTPPort, time.Duration(runtimeWaitTimeoutInSeconds)*time.Second)
|
|
if err != nil {
|
|
sidecarUp = false
|
|
print.WarningStatusEvent(os.Stdout, "Dapr sidecar is not listening on HTTP port: %s", err.Error())
|
|
}
|
|
|
|
print.InfoStatusEvent(os.Stdout, "Checking if Dapr sidecar is listening on GRPC port %v", output.DaprGRPCPort)
|
|
err = utils.IsDaprListeningOnPort(output.DaprGRPCPort, time.Duration(runtimeWaitTimeoutInSeconds)*time.Second)
|
|
if err != nil {
|
|
sidecarUp = false
|
|
print.WarningStatusEvent(os.Stdout, "Dapr sidecar is not listening on GRPC port: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
if sidecarUp {
|
|
print.InfoStatusEvent(os.Stdout, "Dapr sidecar is up and running.")
|
|
} else {
|
|
print.WarningStatusEvent(os.Stdout, "Dapr sidecar might not be responding.")
|
|
}
|
|
}
|
|
|
|
daprRunning <- true
|
|
}()
|
|
|
|
<-daprRunning
|
|
|
|
go func() {
|
|
if output.AppCMD == nil {
|
|
appRunning <- true
|
|
return
|
|
}
|
|
|
|
stdErrPipe, pipeErr := output.AppCMD.StderrPipe()
|
|
if pipeErr != nil {
|
|
print.FailureStatusEvent(os.Stderr, "Error creating stderr for App: "+err.Error())
|
|
appRunning <- false
|
|
return
|
|
}
|
|
|
|
stdOutPipe, pipeErr := output.AppCMD.StdoutPipe()
|
|
if pipeErr != nil {
|
|
print.FailureStatusEvent(os.Stderr, "Error creating stdout for App: "+err.Error())
|
|
appRunning <- false
|
|
return
|
|
}
|
|
|
|
errScanner := bufio.NewScanner(stdErrPipe)
|
|
outScanner := bufio.NewScanner(stdOutPipe)
|
|
go func() {
|
|
for errScanner.Scan() {
|
|
fmt.Println(print.Blue("== APP == " + errScanner.Text()))
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
for outScanner.Scan() {
|
|
fmt.Println(print.Blue("== APP == " + outScanner.Text()))
|
|
}
|
|
}()
|
|
|
|
err = output.AppCMD.Start()
|
|
if err != nil {
|
|
print.FailureStatusEvent(os.Stderr, err.Error())
|
|
appRunning <- false
|
|
return
|
|
}
|
|
|
|
go func() {
|
|
appErr := output.AppCMD.Wait()
|
|
|
|
if appErr != nil {
|
|
output.AppErr = appErr
|
|
print.FailureStatusEvent(os.Stderr, "The App process exited with error code: %s", appErr.Error())
|
|
} else {
|
|
print.SuccessStatusEvent(os.Stdout, "Exited App successfully")
|
|
}
|
|
sigCh <- os.Interrupt
|
|
}()
|
|
|
|
appRunning <- true
|
|
}()
|
|
|
|
appRunStatus := <-appRunning
|
|
if !appRunStatus {
|
|
// Start App failed, try to stop Dapr and exit.
|
|
err = output.DaprCMD.Process.Kill()
|
|
if err != nil {
|
|
print.FailureStatusEvent(os.Stderr, fmt.Sprintf("Start App failed, try to stop Dapr Error: %s", err))
|
|
} else {
|
|
print.SuccessStatusEvent(os.Stdout, "Start App failed, try to stop Dapr successfully")
|
|
}
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Metadata API is only available if app has started listening to port, so wait for app to start before calling metadata API.
|
|
err = metadata.Put(output.DaprHTTPPort, "cliPID", strconv.Itoa(os.Getpid()), output.AppID, unixDomainSocket)
|
|
if err != nil {
|
|
print.WarningStatusEvent(os.Stdout, "Could not update sidecar metadata for cliPID: %s", err.Error())
|
|
}
|
|
|
|
if output.AppCMD != nil {
|
|
if output.AppCMD.Process != nil {
|
|
print.InfoStatusEvent(os.Stdout, fmt.Sprintf("Updating metadata for appPID: %d", output.AppCMD.Process.Pid))
|
|
err = metadata.Put(output.DaprHTTPPort, "appPID", strconv.Itoa(output.AppCMD.Process.Pid), output.AppID, unixDomainSocket)
|
|
if err != nil {
|
|
print.WarningStatusEvent(os.Stdout, "Could not update sidecar metadata for appPID: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
appCommand := strings.Join(args, " ")
|
|
print.InfoStatusEvent(os.Stdout, "Updating metadata for app command: "+appCommand)
|
|
err = metadata.Put(output.DaprHTTPPort, "appCommand", appCommand, output.AppID, unixDomainSocket)
|
|
if err != nil {
|
|
print.WarningStatusEvent(os.Stdout, "Could not update sidecar metadata for appCommand: %s", err.Error())
|
|
} else {
|
|
print.SuccessStatusEvent(os.Stdout, "You're up and running! Both Dapr and your app logs will appear here.\n")
|
|
}
|
|
} else {
|
|
print.SuccessStatusEvent(os.Stdout, "You're up and running! Dapr logs will appear here.\n")
|
|
}
|
|
|
|
<-sigCh
|
|
print.InfoStatusEvent(os.Stdout, "\nterminated signal received: shutting down")
|
|
|
|
exitWithError := false
|
|
|
|
if output.DaprErr != nil {
|
|
exitWithError = true
|
|
print.FailureStatusEvent(os.Stderr, fmt.Sprintf("Error exiting Dapr: %s", output.DaprErr))
|
|
} else if output.DaprCMD.ProcessState == nil || !output.DaprCMD.ProcessState.Exited() {
|
|
err = output.DaprCMD.Process.Kill()
|
|
if err != nil {
|
|
exitWithError = true
|
|
print.FailureStatusEvent(os.Stderr, fmt.Sprintf("Error exiting Dapr: %s", err))
|
|
} else {
|
|
print.SuccessStatusEvent(os.Stdout, "Exited Dapr successfully")
|
|
}
|
|
}
|
|
|
|
if output.AppErr != nil {
|
|
exitWithError = true
|
|
print.FailureStatusEvent(os.Stderr, fmt.Sprintf("Error exiting App: %s", output.AppErr))
|
|
} else if output.AppCMD != nil && (output.AppCMD.ProcessState == nil || !output.AppCMD.ProcessState.Exited()) {
|
|
err = output.AppCMD.Process.Kill()
|
|
if err != nil {
|
|
exitWithError = true
|
|
print.FailureStatusEvent(os.Stderr, fmt.Sprintf("Error exiting App: %s", err))
|
|
} else {
|
|
print.SuccessStatusEvent(os.Stdout, "Exited App successfully")
|
|
}
|
|
}
|
|
|
|
if unixDomainSocket != "" {
|
|
for _, s := range []string{"http", "grpc"} {
|
|
os.Remove(utils.GetSocket(unixDomainSocket, output.AppID, s))
|
|
}
|
|
}
|
|
|
|
if exitWithError {
|
|
os.Exit(1)
|
|
}
|
|
},
|
|
}
|
|
|
|
func init() {
|
|
RunCmd.Flags().IntVarP(&appPort, "app-port", "p", -1, "The port your application is listening on")
|
|
RunCmd.Flags().StringVarP(&appID, "app-id", "a", "", "The id for your application, used for service discovery")
|
|
RunCmd.Flags().StringVarP(&configFile, "config", "c", "", "Dapr configuration file")
|
|
RunCmd.Flags().IntVarP(&port, "dapr-http-port", "H", -1, "The HTTP port for Dapr to listen on")
|
|
RunCmd.Flags().IntVarP(&grpcPort, "dapr-grpc-port", "G", -1, "The gRPC port for Dapr to listen on")
|
|
RunCmd.Flags().IntVarP(&internalGRPCPort, "dapr-internal-grpc-port", "I", -1, "The gRPC port for the Dapr internal API to listen on")
|
|
RunCmd.Flags().BoolVar(&enableProfiling, "enable-profiling", false, "Enable pprof profiling via an HTTP endpoint")
|
|
RunCmd.Flags().IntVarP(&profilePort, "profile-port", "", -1, "The port for the profile server to listen on")
|
|
RunCmd.Flags().StringVarP(&logLevel, "log-level", "", "info", "The log verbosity. Valid values are: debug, info, warn, error, fatal, or panic")
|
|
RunCmd.Flags().IntVarP(&maxConcurrency, "app-max-concurrency", "", -1, "The concurrency level of the application, otherwise is unlimited")
|
|
RunCmd.Flags().StringVarP(&protocol, "app-protocol", "P", "http", "The protocol (grpc, grpcs, http, https, h2c) Dapr uses to talk to the application")
|
|
RunCmd.Flags().StringVarP(&componentsPath, "components-path", "d", "", "The path for components directory. Default is $HOME/.dapr/components or %USERPROFILE%\\.dapr\\components")
|
|
RunCmd.Flags().StringSliceVarP(&resourcesPaths, "resources-path", "", []string{}, "The path for resources directory")
|
|
// TODO: Remove below line once the flag is removed in the future releases.
|
|
// By marking this as deprecated, the flag will be hidden from the help menu, but will continue to work. It will show a warning message when used.
|
|
RunCmd.Flags().MarkDeprecated("components-path", "This flag is deprecated and will be removed in the future releases. Use \"resources-path\" flag instead")
|
|
RunCmd.Flags().String("placement-host-address", "localhost", "The address of the placement service. Format is either <hostname> for default port or <hostname>:<port> for custom port")
|
|
RunCmd.Flags().StringVarP(&schedulerHostAddress, "scheduler-host-address", "", "localhost", "The address of the scheduler service. Format is either <hostname> for default port or <hostname>:<port> for custom port")
|
|
// TODO: Remove below flag once the flag is removed in runtime in future release.
|
|
RunCmd.Flags().BoolVar(&appSSL, "app-ssl", false, "Enable https when Dapr invokes the application")
|
|
RunCmd.Flags().MarkDeprecated("app-ssl", "This flag is deprecated and will be removed in the future releases. Use \"app-protocol\" flag with https or grpcs values instead")
|
|
RunCmd.Flags().IntVarP(&metricsPort, "metrics-port", "M", -1, "The port of metrics on dapr")
|
|
RunCmd.Flags().BoolP("help", "h", false, "Print this help message")
|
|
RunCmd.Flags().StringVarP(&maxRequestBodySize, "max-body-size", "", strconv.Itoa(daprRuntime.DefaultMaxRequestBodySize>>20)+"Mi", "Max size of request body in MB")
|
|
RunCmd.Flags().StringVarP(&readBufferSize, "read-buffer-size", "", strconv.Itoa(daprRuntime.DefaultReadBufferSize>>10)+"Ki", "HTTP header read buffer in KB")
|
|
RunCmd.Flags().StringVarP(&unixDomainSocket, "unix-domain-socket", "u", "", "Path to a unix domain socket dir. If specified, Dapr API servers will use Unix Domain Sockets")
|
|
RunCmd.Flags().BoolVar(&enableAppHealth, "enable-app-health-check", false, "Enable health checks for the application using the protocol defined with app-protocol")
|
|
RunCmd.Flags().StringVar(&appHealthPath, "app-health-check-path", "", "Path used for health checks; HTTP only")
|
|
RunCmd.Flags().IntVar(&appHealthInterval, "app-health-probe-interval", 0, "Interval to probe for the health of the app in seconds")
|
|
RunCmd.Flags().IntVar(&appHealthTimeout, "app-health-probe-timeout", 0, "Timeout for app health probes in milliseconds")
|
|
RunCmd.Flags().IntVar(&appHealthThreshold, "app-health-threshold", 0, "Number of consecutive failures for the app to be considered unhealthy")
|
|
RunCmd.Flags().BoolVar(&enableAPILogging, "enable-api-logging", false, "Log API calls at INFO verbosity. Valid values are: true or false")
|
|
RunCmd.Flags().BoolVarP(&enableRunK8s, "kubernetes", "k", false, "Run the multi-app run template against Kubernetes environment.")
|
|
RunCmd.Flags().StringVar(&apiListenAddresses, "dapr-listen-addresses", "", "Comma separated list of IP addresses that sidecar will listen to")
|
|
RunCmd.Flags().StringVarP(&runFilePath, "run-file", "f", "", "Path to the run template file for the list of apps to run")
|
|
RunCmd.Flags().StringVarP(&appChannelAddress, "app-channel-address", "", utils.DefaultAppChannelAddress, "The network address the application listens on")
|
|
RootCmd.AddCommand(RunCmd)
|
|
}
|
|
|
|
func executeRun(runTemplateName, runFilePath string, apps []runfileconfig.App) (bool, error) {
|
|
var exitWithError bool
|
|
|
|
// setup shutdown notify channel.
|
|
sigCh := make(chan os.Signal, 1)
|
|
daprsyscall.SetupShutdownNotify(sigCh)
|
|
|
|
runStates := make([]*runExec.RunExec, 0, len(apps))
|
|
|
|
// Creates a separate process group ID for current process i.e. "dapr run -f".
|
|
// All the subprocess and their grandchildren inherit this PGID.
|
|
// This is done to provide a better grouping, which can be used to control all the proceses started by "dapr run -f".
|
|
daprsyscall.CreateProcessGroupID()
|
|
|
|
for _, app := range apps {
|
|
print.StatusEvent(os.Stdout, print.LogInfo, "Validating config and starting app %q", app.RunConfig.AppID)
|
|
// Set defaults if zero value provided in config yaml.
|
|
app.RunConfig.SetDefaultFromSchema()
|
|
|
|
app.RunConfig.SchedulerHostAddress = validateSchedulerHostAddress(daprVer.RuntimeVersion, app.RunConfig.SchedulerHostAddress)
|
|
|
|
// Validate validates the configs and modifies the ports to free ports, appId etc.
|
|
err := app.RunConfig.Validate()
|
|
if err != nil {
|
|
print.FailureStatusEvent(os.Stderr, "Error validating run config for app %q present in %s: %s", app.RunConfig.AppID, runFilePath, err.Error())
|
|
exitWithError = true
|
|
break
|
|
}
|
|
|
|
// Get Run Config for different apps.
|
|
runConfig := app.RunConfig
|
|
err = app.CreateDaprdLogFile()
|
|
if err != nil {
|
|
print.StatusEvent(os.Stderr, print.LogFailure, "Error getting daprd log file for app %q present in %s: %s", runConfig.AppID, runFilePath, err.Error())
|
|
exitWithError = true
|
|
break
|
|
}
|
|
|
|
// Combined multiwriter for logs.
|
|
var appDaprdWriter io.Writer
|
|
// appLogWriter is used when app command is present.
|
|
var appLogWriter io.Writer
|
|
// A custom writer used for trimming ASCII color codes from logs when writing to files.
|
|
var customAppLogWriter io.Writer
|
|
|
|
daprdLogWriterCloser := runfileconfig.GetLogWriter(app.DaprdLogWriteCloser, app.DaprdLogDestination)
|
|
|
|
if len(runConfig.Command) == 0 {
|
|
print.StatusEvent(os.Stdout, print.LogWarning, "No application command found for app %q present in %s", runConfig.AppID, runFilePath)
|
|
appDaprdWriter = runExec.GetAppDaprdWriter(app, true)
|
|
appLogWriter = app.DaprdLogWriteCloser
|
|
} else {
|
|
err = app.CreateAppLogFile()
|
|
if err != nil {
|
|
print.StatusEvent(os.Stderr, print.LogFailure, "Error getting app log file for app %q present in %s: %s", runConfig.AppID, runFilePath, err.Error())
|
|
exitWithError = true
|
|
break
|
|
}
|
|
appDaprdWriter = runExec.GetAppDaprdWriter(app, false)
|
|
appLogWriter = runfileconfig.GetLogWriter(app.AppLogWriteCloser, app.AppLogDestination)
|
|
}
|
|
customAppLogWriter = print.CustomLogWriter{W: appLogWriter}
|
|
runState, err := startDaprdAndAppProcesses(&runConfig, app.AppDirPath, sigCh,
|
|
daprdLogWriterCloser, daprdLogWriterCloser, customAppLogWriter, customAppLogWriter)
|
|
if err != nil {
|
|
print.StatusEvent(appDaprdWriter, print.LogFailure, "Error starting Dapr and app (%q): %s", app.AppID, err.Error())
|
|
exitWithError = true
|
|
break
|
|
}
|
|
// Store runState in an array.
|
|
runStates = append(runStates, runState)
|
|
|
|
// Metadata API is only available if app has started listening to port, so wait for app to start before calling metadata API.
|
|
putCLIProcessIDInMeta(runState, os.Getpid())
|
|
|
|
// Update extended metadata with run file path.
|
|
putRunFilePathInMeta(runState, runFilePath)
|
|
|
|
// Update extended metadata with run file path.
|
|
putRunTemplateNameInMeta(runState, runTemplateName)
|
|
|
|
// Update extended metadata with app log file path.
|
|
if app.AppLogDestination != standalone.Console {
|
|
putAppLogFilePathInMeta(runState, app.AppLogFileName)
|
|
}
|
|
|
|
// Update extended metadata with daprd log file path.
|
|
if app.DaprdLogDestination != standalone.Console {
|
|
putDaprLogFilePathInMeta(runState, app.DaprdLogFileName)
|
|
}
|
|
|
|
if runState.AppCMD.Command != nil {
|
|
putAppCommandInMeta(runConfig, runState)
|
|
|
|
if runState.AppCMD.Command.Process != nil {
|
|
putAppProcessIDInMeta(runState)
|
|
// Attach a windows job object to the app process.
|
|
utils.AttachJobObjectToProcess(strconv.Itoa(os.Getpid()), runState.AppCMD.Command.Process)
|
|
}
|
|
}
|
|
|
|
print.StatusEvent(runState.DaprCMD.OutputWriter, print.LogSuccess, "You're up and running! Dapr logs will appear here.\n")
|
|
logInformationalStatusToStdout(app)
|
|
}
|
|
// If all apps have been started and there are no errors in starting the apps wait for signal from sigCh.
|
|
if !exitWithError {
|
|
// After all apps started wait for sigCh.
|
|
<-sigCh
|
|
// To add a new line in Stdout.
|
|
fmt.Println()
|
|
print.InfoStatusEvent(os.Stdout, "Received signal to stop Dapr and app processes. Shutting down Dapr and app processes.")
|
|
}
|
|
|
|
// Stop daprd and app processes for each runState.
|
|
closeError := gracefullyShutdownAppsAndCloseResources(runStates, apps)
|
|
|
|
for _, app := range apps {
|
|
runConfig := app.RunConfig
|
|
if runConfig.UnixDomainSocket != "" {
|
|
for _, s := range []string{"http", "grpc"} {
|
|
os.Remove(utils.GetSocket(runConfig.UnixDomainSocket, runConfig.AppID, s))
|
|
}
|
|
}
|
|
}
|
|
|
|
return exitWithError, closeError
|
|
}
|
|
|
|
func logInformationalStatusToStdout(app runfileconfig.App) {
|
|
print.InfoStatusEvent(os.Stdout, "Started Dapr with app id %q. HTTP Port: %d. gRPC Port: %d",
|
|
app.AppID, app.RunConfig.HTTPPort, app.RunConfig.GRPCPort)
|
|
print.InfoStatusEvent(os.Stdout, "Writing log files to directory : %s", app.GetLogsDir())
|
|
}
|
|
|
|
func gracefullyShutdownAppsAndCloseResources(runState []*runExec.RunExec, apps []runfileconfig.App) error {
|
|
for _, s := range runState {
|
|
stopDaprdAndAppProcesses(s)
|
|
}
|
|
var err error
|
|
// close log file resources.
|
|
for _, app := range apps {
|
|
hasErr := app.CloseAppLogFile()
|
|
if err == nil && hasErr != nil {
|
|
err = hasErr
|
|
}
|
|
hasErr = app.CloseDaprdLogFile()
|
|
if err == nil && hasErr != nil {
|
|
err = hasErr
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
func executeRunWithAppsConfigFile(runFilePath string, k8sEnabled bool) {
|
|
config, apps, err := getRunConfigFromRunFile(runFilePath)
|
|
if err != nil {
|
|
print.StatusEvent(os.Stdout, print.LogFailure, "Error getting apps from config file: %s", err)
|
|
os.Exit(1)
|
|
}
|
|
if len(apps) == 0 {
|
|
print.StatusEvent(os.Stdout, print.LogFailure, "No apps to run")
|
|
os.Exit(1)
|
|
}
|
|
var exitWithError bool
|
|
var closeErr error
|
|
if !k8sEnabled {
|
|
exitWithError, closeErr = executeRun(config.Name, runFilePath, apps)
|
|
} else {
|
|
exitWithError, closeErr = kubernetes.Run(runFilePath, config)
|
|
}
|
|
if exitWithError {
|
|
if closeErr != nil {
|
|
print.StatusEvent(os.Stdout, print.LogFailure, "Error closing resources: %s", closeErr)
|
|
}
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
// populate the scheduler host address based on the dapr version.
|
|
func validateSchedulerHostAddress(version, address string) string {
|
|
// If no SchedulerHostAddress is supplied, set it to default value.
|
|
if semver.Compare(fmt.Sprintf("v%v", version), "v1.15.0-rc.0") == 1 {
|
|
if address == "" {
|
|
return "localhost"
|
|
}
|
|
}
|
|
return address
|
|
}
|
|
|
|
func getRunConfigFromRunFile(runFilePath string) (runfileconfig.RunFileConfig, []runfileconfig.App, error) {
|
|
config := runfileconfig.RunFileConfig{}
|
|
apps, err := config.GetApps(runFilePath)
|
|
return config, apps, err
|
|
}
|
|
|
|
// startDaprdAndAppProcesses is a function to start the App process and the associated Daprd process.
|
|
// This should be called as a blocking function call.
|
|
func startDaprdAndAppProcesses(runConfig *standalone.RunConfig, commandDir string, sigCh chan os.Signal,
|
|
daprdOutputWriter io.Writer, daprdErrorWriter io.Writer,
|
|
appOutputWriter io.Writer, appErrorWriter io.Writer,
|
|
) (*runExec.RunExec, error) {
|
|
daprRunning := make(chan bool, 1)
|
|
appRunning := make(chan bool, 1)
|
|
|
|
daprCMD, err := runExec.GetDaprCmdProcess(runConfig)
|
|
if err != nil {
|
|
print.StatusEvent(daprdErrorWriter, print.LogFailure, "Error getting daprd command with args : %s", err.Error())
|
|
return nil, err
|
|
}
|
|
if strings.TrimSpace(commandDir) != "" {
|
|
daprCMD.Command.Dir = commandDir
|
|
}
|
|
daprCMD.WithOutputWriter(daprdOutputWriter)
|
|
daprCMD.WithErrorWriter(daprdErrorWriter)
|
|
daprCMD.SetStdout()
|
|
daprCMD.SetStderr()
|
|
|
|
appCmd, err := runExec.GetAppCmdProcess(runConfig)
|
|
if err != nil {
|
|
print.StatusEvent(appErrorWriter, print.LogFailure, "Error getting app command with args : %s", err.Error())
|
|
return nil, err
|
|
}
|
|
if appCmd.Command != nil {
|
|
// If an app is being run, set the command directory for that app.
|
|
// appCmd does not need to call SetStdout and SetStderr since output is being read processed and then written
|
|
// to the output and error writers for an app.
|
|
appCmd.WithOutputWriter(appOutputWriter)
|
|
appCmd.WithErrorWriter(appErrorWriter)
|
|
if strings.TrimSpace(commandDir) != "" {
|
|
appCmd.Command.Dir = commandDir
|
|
}
|
|
}
|
|
|
|
runState := runExec.New(runConfig, daprCMD, appCmd)
|
|
|
|
startErrChan := make(chan error, 1)
|
|
|
|
// Start daprd process.
|
|
go startDaprdProcess(runConfig, runState, daprRunning, sigCh, startErrChan)
|
|
|
|
// Wait for daprRunning channel output.
|
|
if daprStarted := <-daprRunning; !daprStarted {
|
|
startErr := <-startErrChan
|
|
print.StatusEvent(daprdErrorWriter, print.LogFailure, "Error starting daprd process: %s", startErr.Error())
|
|
return nil, startErr
|
|
}
|
|
|
|
// No application command is present.
|
|
if appCmd.Command == nil {
|
|
print.StatusEvent(appOutputWriter, print.LogWarning, "No application command present")
|
|
return runState, nil
|
|
}
|
|
|
|
// Start App process.
|
|
go startAppProcess(runConfig, runState, appRunning, sigCh, startErrChan)
|
|
|
|
// Wait for appRunnning channel output.
|
|
if appStarted := <-appRunning; !appStarted {
|
|
startErr := <-startErrChan
|
|
print.StatusEvent(appErrorWriter, print.LogFailure, "Error starting app process: %s", startErr.Error())
|
|
// Start App failed so try to stop daprd process.
|
|
err = killDaprdProcess(runState)
|
|
if err != nil {
|
|
print.StatusEvent(daprdErrorWriter, print.LogFailure, "Error stopping daprd process: %s", err.Error())
|
|
print.StatusEvent(appErrorWriter, print.LogFailure, "Error stopping daprd process: %s", err.Error())
|
|
}
|
|
// Return the error from starting the app process.
|
|
return nil, startErr
|
|
}
|
|
return runState, nil
|
|
}
|
|
|
|
// stopDaprdAndAppProcesses is a function to stop the App process and the associated Daprd process
|
|
// This should be called as a blocking function call.
|
|
func stopDaprdAndAppProcesses(runState *runExec.RunExec) bool {
|
|
var err error
|
|
print.StatusEvent(runState.DaprCMD.OutputWriter, print.LogInfo, "\ntermination signal received: shutting down")
|
|
// Only if app command is present and
|
|
// if two different output writers are present run the following print statement.
|
|
if runState.AppCMD.Command != nil && runState.AppCMD.OutputWriter != runState.DaprCMD.OutputWriter {
|
|
print.StatusEvent(runState.AppCMD.OutputWriter, print.LogInfo, "\ntermination signal received: shutting down")
|
|
}
|
|
|
|
exitWithError := false
|
|
|
|
daprErr := runState.DaprCMD.CommandErr
|
|
|
|
if daprErr != nil {
|
|
exitWithError = true
|
|
print.StatusEvent(runState.DaprCMD.ErrorWriter, print.LogFailure, "Error exiting Dapr: %s", daprErr)
|
|
} else if runState.DaprCMD.Command.ProcessState == nil || !runState.DaprCMD.Command.ProcessState.Exited() {
|
|
err = killDaprdProcess(runState)
|
|
if err != nil {
|
|
exitWithError = true
|
|
}
|
|
}
|
|
appErr := runState.AppCMD.CommandErr
|
|
|
|
if appErr != nil {
|
|
exitWithError = true
|
|
print.StatusEvent(runState.AppCMD.ErrorWriter, print.LogFailure, "Error exiting App: %s", appErr)
|
|
} else if runState.AppCMD.Command != nil && (runState.AppCMD.Command.ProcessState == nil || !runState.AppCMD.Command.ProcessState.Exited()) {
|
|
err = killAppProcess(runState)
|
|
if err != nil {
|
|
exitWithError = true
|
|
}
|
|
}
|
|
return exitWithError
|
|
}
|
|
|
|
// startAppsProcess, starts the App process and calls wait in a goroutine
|
|
// This function should be called as a goroutine.
|
|
func startAppProcess(runConfig *standalone.RunConfig, runE *runExec.RunExec,
|
|
appRunning chan bool, sigCh chan os.Signal, errorChan chan error,
|
|
) {
|
|
if runE.AppCMD.Command == nil {
|
|
appRunning <- true
|
|
return
|
|
}
|
|
|
|
stdErrPipe, pipeErr := runE.AppCMD.Command.StderrPipe()
|
|
if pipeErr != nil {
|
|
print.StatusEvent(runE.AppCMD.ErrorWriter, print.LogFailure, "Error creating stderr for App %q : %s", runE.AppID, pipeErr.Error())
|
|
errorChan <- pipeErr
|
|
appRunning <- false
|
|
return
|
|
}
|
|
|
|
stdOutPipe, pipeErr := runE.AppCMD.Command.StdoutPipe()
|
|
if pipeErr != nil {
|
|
print.StatusEvent(runE.AppCMD.ErrorWriter, print.LogFailure, "Error creating stdout for App %q : %s", runE.AppID, pipeErr.Error())
|
|
errorChan <- pipeErr
|
|
appRunning <- false
|
|
return
|
|
}
|
|
|
|
errScanner := bufio.NewScanner(stdErrPipe)
|
|
outScanner := bufio.NewScanner(stdOutPipe)
|
|
go func() {
|
|
for errScanner.Scan() {
|
|
fmt.Fprintln(runE.AppCMD.ErrorWriter, print.Blue(fmt.Sprintf("== APP - %s == %s", runE.AppID,
|
|
errScanner.Text())))
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
for outScanner.Scan() {
|
|
fmt.Fprintln(runE.AppCMD.OutputWriter, print.Blue(fmt.Sprintf("== APP - %s == %s", runE.AppID, outScanner.Text())))
|
|
}
|
|
}()
|
|
|
|
err := runE.AppCMD.Command.Start()
|
|
if err != nil {
|
|
print.StatusEvent(runE.AppCMD.ErrorWriter, print.LogFailure, err.Error())
|
|
errorChan <- err
|
|
appRunning <- false
|
|
return
|
|
}
|
|
|
|
go func() {
|
|
appErr := runE.AppCMD.Command.Wait()
|
|
|
|
if appErr != nil {
|
|
runE.AppCMD.CommandErr = appErr
|
|
print.StatusEvent(runE.AppCMD.ErrorWriter, print.LogFailure, "The App process exited with error code: %s", appErr.Error())
|
|
} else {
|
|
print.StatusEvent(runE.AppCMD.OutputWriter, print.LogSuccess, "Exited App successfully")
|
|
}
|
|
}()
|
|
|
|
appRunning <- true
|
|
}
|
|
|
|
// startDaprdProcess, starts the Daprd process and calls wait in a goroutine
|
|
// This function should be called as a goroutine.
|
|
func startDaprdProcess(runConfig *standalone.RunConfig, runE *runExec.RunExec,
|
|
daprRunning chan bool, sigCh chan os.Signal, errorChan chan error,
|
|
) {
|
|
var startInfo string
|
|
if runConfig.UnixDomainSocket != "" {
|
|
startInfo = fmt.Sprintf(
|
|
"Starting Dapr with id %s. HTTP Socket: %v. gRPC Socket: %v.",
|
|
runE.AppID,
|
|
utils.GetSocket(unixDomainSocket, runE.AppID, "http"),
|
|
utils.GetSocket(unixDomainSocket, runE.AppID, "grpc"))
|
|
} else {
|
|
startInfo = fmt.Sprintf(
|
|
"Starting Dapr with id %s. HTTP Port: %v. gRPC Port: %v",
|
|
runE.AppID,
|
|
runE.DaprHTTPPort,
|
|
runE.DaprGRPCPort)
|
|
}
|
|
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogInfo, startInfo)
|
|
|
|
err := runE.DaprCMD.Command.Start()
|
|
if err != nil {
|
|
errorChan <- err
|
|
daprRunning <- false
|
|
return
|
|
}
|
|
go func() {
|
|
daprdErr := runE.DaprCMD.Command.Wait()
|
|
if daprdErr != nil {
|
|
runE.DaprCMD.CommandErr = daprdErr
|
|
print.StatusEvent(runE.DaprCMD.ErrorWriter, print.LogFailure, "The daprd process exited with error code: %s", daprdErr.Error())
|
|
} else {
|
|
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogSuccess, "Exited Dapr successfully")
|
|
}
|
|
}()
|
|
|
|
if runConfig.AppPort <= 0 {
|
|
// If app does not listen to port, we can check for Dapr's sidecar health before starting the app.
|
|
// Otherwise, it creates a deadlock.
|
|
sidecarUp := true
|
|
|
|
if runConfig.UnixDomainSocket != "" {
|
|
httpSocket := utils.GetSocket(runConfig.UnixDomainSocket, runE.AppID, "http")
|
|
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogInfo, "Checking if Dapr sidecar is listening on HTTP socket %v", httpSocket)
|
|
err = utils.IsDaprListeningOnSocket(httpSocket, time.Duration(runtimeWaitTimeoutInSeconds)*time.Second)
|
|
if err != nil {
|
|
sidecarUp = false
|
|
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogWarning, "Dapr sidecar is not listening on HTTP socket: %s", err.Error())
|
|
}
|
|
|
|
grpcSocket := utils.GetSocket(runConfig.UnixDomainSocket, runE.AppID, "grpc")
|
|
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogInfo, "Checking if Dapr sidecar is listening on GRPC socket %v", grpcSocket)
|
|
err = utils.IsDaprListeningOnSocket(grpcSocket, time.Duration(runtimeWaitTimeoutInSeconds)*time.Second)
|
|
if err != nil {
|
|
sidecarUp = false
|
|
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogWarning, "Dapr sidecar is not listening on GRPC socket: %s", err.Error())
|
|
}
|
|
} else {
|
|
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogInfo, "Checking if Dapr sidecar is listening on HTTP port %v", runE.DaprHTTPPort)
|
|
err = utils.IsDaprListeningOnPort(runE.DaprHTTPPort, time.Duration(runtimeWaitTimeoutInSeconds)*time.Second)
|
|
if err != nil {
|
|
sidecarUp = false
|
|
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogWarning, "Dapr sidecar is not listening on HTTP port: %s", err.Error())
|
|
}
|
|
|
|
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogInfo, "Checking if Dapr sidecar is listening on GRPC port %v", runE.DaprGRPCPort)
|
|
err = utils.IsDaprListeningOnPort(runE.DaprGRPCPort, time.Duration(runtimeWaitTimeoutInSeconds)*time.Second)
|
|
if err != nil {
|
|
sidecarUp = false
|
|
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogWarning, "Dapr sidecar is not listening on GRPC port: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
if sidecarUp {
|
|
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogInfo, "Dapr sidecar is up and running.")
|
|
} else {
|
|
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogWarning, "Dapr sidecar might not be responding.")
|
|
}
|
|
}
|
|
daprRunning <- true
|
|
}
|
|
|
|
// killDaprdProcess is used to kill the Daprd process and return error on failure.
|
|
func killDaprdProcess(runE *runExec.RunExec) error {
|
|
err := runE.DaprCMD.Command.Process.Kill()
|
|
if err != nil {
|
|
print.StatusEvent(runE.DaprCMD.ErrorWriter, print.LogFailure, "Error exiting Dapr: %s", err)
|
|
return err
|
|
}
|
|
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogSuccess, "Exited Dapr successfully")
|
|
return nil
|
|
}
|
|
|
|
// killAppProcess is used to kill the App process and return error on failure.
|
|
func killAppProcess(runE *runExec.RunExec) error {
|
|
if runE.AppCMD.Command == nil {
|
|
return nil
|
|
}
|
|
err := runE.AppCMD.Command.Process.Kill()
|
|
if err != nil {
|
|
print.StatusEvent(runE.DaprCMD.ErrorWriter, print.LogFailure, "Error exiting App: %s", err)
|
|
return err
|
|
}
|
|
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogSuccess, "Exited App successfully")
|
|
return nil
|
|
}
|
|
|
|
// putCLIProcessIDInMeta puts the CLI process ID in metadata so that it can be used by the CLI to stop the CLI process.
|
|
func putCLIProcessIDInMeta(runE *runExec.RunExec, pid int) {
|
|
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogInfo, "Updating metadata for cliPID: %d", pid)
|
|
err := metadata.Put(runE.DaprHTTPPort, "cliPID", strconv.Itoa(pid), runE.AppID, unixDomainSocket)
|
|
if err != nil {
|
|
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogWarning, "Could not update sidecar metadata for cliPID: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
func putAppProcessIDInMeta(runE *runExec.RunExec) {
|
|
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogInfo, "Updating metadata for appPID: %d", runE.AppCMD.Command.Process.Pid)
|
|
err := metadata.Put(runE.DaprHTTPPort, "appPID", strconv.Itoa(runE.AppCMD.Command.Process.Pid), runE.AppID, unixDomainSocket)
|
|
if err != nil {
|
|
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogWarning, "Could not update sidecar metadata for appPID: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
// putAppCommandInMeta puts the app command in metadata so that it can be used by the CLI to stop the app.
|
|
func putAppCommandInMeta(runConfig standalone.RunConfig, runE *runExec.RunExec) {
|
|
appCommand := strings.Join(runConfig.Command, " ")
|
|
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogInfo, "Updating metadata for app command: %s", appCommand)
|
|
err := metadata.Put(runE.DaprHTTPPort, "appCommand", appCommand, runE.AppID, runConfig.UnixDomainSocket)
|
|
if err != nil {
|
|
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogWarning, "Could not update sidecar metadata for appCommand: %s", err.Error())
|
|
return
|
|
}
|
|
}
|
|
|
|
// putRunFilePathInMeta puts the absolute path of run file in metadata so that it can be used by the CLI to stop all apps started by this run file.
|
|
func putRunFilePathInMeta(runE *runExec.RunExec, runFilePath string) {
|
|
runFilePath, err := filepath.Abs(runFilePath)
|
|
if err != nil {
|
|
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogWarning, "Could not get absolute path for run file: %s", err.Error())
|
|
return
|
|
}
|
|
err = metadata.Put(runE.DaprHTTPPort, "runTemplatePath", runFilePath, runE.AppID, unixDomainSocket)
|
|
if err != nil {
|
|
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogWarning, "Could not update sidecar metadata for run file path: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
// putRunTemplateNameInMeta puts the name of the run file in metadata so that it can be used by the CLI to stop all apps started by this run file.
|
|
func putRunTemplateNameInMeta(runE *runExec.RunExec, runTemplateName string) {
|
|
err := metadata.Put(runE.DaprHTTPPort, "runTemplateName", runTemplateName, runE.AppID, unixDomainSocket)
|
|
if err != nil {
|
|
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogWarning, "Could not update sidecar metadata for run template name: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
// putAppLogFilePathInMeta puts the absolute path of app log file in metadata so that it can be used by the CLI to stop the app.
|
|
func putAppLogFilePathInMeta(runE *runExec.RunExec, appLogFilePath string) {
|
|
err := metadata.Put(runE.DaprHTTPPort, "appLogPath", appLogFilePath, runE.AppID, unixDomainSocket)
|
|
if err != nil {
|
|
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogWarning, "Could not update sidecar metadata for app log file path: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
// putDaprLogFilePathInMeta puts the absolute path of Dapr log file in metadata so that it can be used by the CLI to stop the app.
|
|
func putDaprLogFilePathInMeta(runE *runExec.RunExec, daprLogFilePath string) {
|
|
err := metadata.Put(runE.DaprHTTPPort, "daprdLogPath", daprLogFilePath, runE.AppID, unixDomainSocket)
|
|
if err != nil {
|
|
print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogWarning, "Could not update sidecar metadata for dapr log file path: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
// getRunFilePath returns the path to the run file.
|
|
// If the provided path is a path to a YAML file then return the same.
|
|
// Else it returns the path of "dapr.yaml" in the provided directory.
|
|
func getRunFilePath(path string) (string, error) {
|
|
if path == "-" {
|
|
return path, nil // will be read from stdin later.
|
|
}
|
|
fileInfo, err := os.Stat(path)
|
|
if err != nil {
|
|
return "", fmt.Errorf("error getting file info for %s: %w", path, err)
|
|
}
|
|
if fileInfo.IsDir() {
|
|
filePath, err := utils.FindFileInDir(path, defaultRunTemplateFileName)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return filePath, nil
|
|
}
|
|
hasYAMLExtension := strings.HasSuffix(path, ".yaml") || strings.HasSuffix(path, ".yml")
|
|
if !hasYAMLExtension {
|
|
return "", fmt.Errorf("file %q is not a YAML file", path)
|
|
}
|
|
return path, nil
|
|
}
|
|
|
|
// getConflictingFlags checks if any flags are set other than the ones passed in the excludedFlags slice.
|
|
// Used for logic or notifications when any of the flags are conflicting and should not be used together.
|
|
func getConflictingFlags(cmd *cobra.Command, excludedFlags ...string) []string {
|
|
var conflictingFlags []string
|
|
cmd.Flags().Visit(func(f *pflag.Flag) {
|
|
if !slices.Contains(excludedFlags, f.Name) {
|
|
conflictingFlags = append(conflictingFlags, f.Name)
|
|
}
|
|
})
|
|
return conflictingFlags
|
|
}
|
|
|
|
// detectIncompatibleFlags checks if any incompatible flags are used with --run-file
|
|
// and returns a slice of the flag names that were used
|
|
func detectIncompatibleFlags(cmd *cobra.Command) []string {
|
|
if runFilePath == "" {
|
|
return nil // No run file specified, so no incompatibilities
|
|
}
|
|
|
|
// Get all flags that are not in the compatible list
|
|
return getConflictingFlags(cmd, append(runFileCompatibleFlags, "run-file")...)
|
|
}
|