diff --git a/cmd/dapr.go b/cmd/dapr.go index 6a04a102..1ea7208b 100644 --- a/cmd/dapr.go +++ b/cmd/dapr.go @@ -46,6 +46,12 @@ type daprVersion struct { RuntimeVersion string `json:"Runtime version"` } +type osType string + +const ( + windowsOsType osType = "windows" +) + var ( daprVer daprVersion logAsJSON bool diff --git a/cmd/invoke.go b/cmd/invoke.go index cbf77b5c..c8b009f5 100644 --- a/cmd/invoke.go +++ b/cmd/invoke.go @@ -70,7 +70,7 @@ dapr invoke --unix-domain-socket --app-id target --method sample --verb GET // TODO(@daixiang0): add Windows support. if invokeSocket != "" { - if runtime.GOOS == "windows" { + if runtime.GOOS == string(windowsOsType) { print.FailureStatusEvent(os.Stderr, "The unix-domain-socket option is not supported on Windows") os.Exit(1) } else { diff --git a/cmd/publish.go b/cmd/publish.go index b3195ba6..cd07959d 100644 --- a/cmd/publish.go +++ b/cmd/publish.go @@ -69,7 +69,7 @@ dapr publish --publish-app-id myapp --pubsub target --topic sample --data '{"key client := standalone.NewClient() // TODO(@daixiang0): add Windows support. if publishSocket != "" { - if runtime.GOOS == "windows" { + if runtime.GOOS == string(windowsOsType) { print.FailureStatusEvent(os.Stderr, "The unix-domain-socket option is not supported on Windows") os.Exit(1) } else { diff --git a/cmd/run.go b/cmd/run.go index 37dad5a3..6d5fd10b 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -18,6 +18,7 @@ import ( "fmt" "io" "os" + "path/filepath" "runtime" "strconv" "strings" @@ -92,6 +93,12 @@ dapr run --app-id myapp --app-port 3000 --app-protocol grpc -- go run main.go # Run sidecar only specifying dapr runtime installation directory dapr run --app-id myapp --dapr-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 `, Args: cobra.MinimumNArgs(0), PreRun: func(cmd *cobra.Command, args []string) { @@ -99,7 +106,7 @@ dapr run --app-id myapp --dapr-path /usr/local/dapr }, Run: func(cmd *cobra.Command, args []string) { if len(runFilePath) > 0 { - if runtime.GOOS == "windows" { + if runtime.GOOS == string(windowsOsType) { print.FailureStatusEvent(os.Stderr, "The run command with run file is not supported on Windows") os.Exit(1) } @@ -133,7 +140,7 @@ dapr run --app-id myapp --dapr-path /usr/local/dapr if unixDomainSocket != "" { // TODO(@daixiang0): add Windows support. - if runtime.GOOS == "windows" { + if runtime.GOOS == string(windowsOsType) { print.FailureStatusEvent(os.Stderr, "The unix-domain-socket option is not supported on Windows") os.Exit(1) } else { @@ -438,7 +445,7 @@ func init() { 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().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 configuration file for the apps to run") + RunCmd.Flags().StringVarP(&runFilePath, "run-file", "f", "", "Path to the run template file for the list of apps to run") RootCmd.AddCommand(RunCmd) } @@ -503,11 +510,13 @@ func executeRun(runFilePath string, apps []runfileconfig.App) (bool, error) { 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. - // The PID is put as 0, so as to not kill CLI process when any one of the apps is stopped. - _ = putCLIProcessIDInMeta(runState, 0) + putCLIProcessIDInMeta(runState, os.Getpid()) + + // Update extended metadata with run file path. + putRunFilePathInMeta(runState, runFilePath) if runState.AppCMD.Command != nil { - _ = putAppCommandInMeta(runConfig, runState) + putAppCommandInMeta(runConfig, runState) } print.StatusEvent(runState.DaprCMD.OutputWriter, print.LogSuccess, "You're up and running! Dapr logs will appear here.\n") logInformationalStatusToStdout(app) @@ -885,28 +894,37 @@ func killAppProcess(runE *runExec.RunExec) error { } // 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) error { - // For now putting this as 0, since we do not want the dapr stop command for a single to kill the CLI process, - // thereby killing all the apps that are running via dapr run -f. +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()) - return err } - return nil } // 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, runState *runExec.RunExec) error { +func putAppCommandInMeta(runConfig standalone.RunConfig, runE *runExec.RunExec) { appCommand := strings.Join(runConfig.Command, " ") - print.StatusEvent(runState.DaprCMD.OutputWriter, print.LogInfo, "Updating metadata for app command: %s", appCommand) - err := metadata.Put(runState.DaprHTTPPort, "appCommand", appCommand, runState.AppID, runConfig.UnixDomainSocket) + 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(runState.DaprCMD.OutputWriter, print.LogWarning, "Could not update sidecar metadata for appCommand: %s", err.Error()) - return err + print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogWarning, "Could not update sidecar metadata for appCommand: %s", err.Error()) + return + } + print.StatusEvent(runE.DaprCMD.OutputWriter, print.LogSuccess, "You're up and running! Dapr logs will appear here.\n") +} + +// 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 runFile: %s", err.Error()) } - print.StatusEvent(runState.DaprCMD.OutputWriter, print.LogSuccess, "You're up and running! Dapr logs will appear here.\n") - return nil } // getRunFilePath returns the path to the run file. diff --git a/cmd/stop.go b/cmd/stop.go index db254cd9..bdfe8be4 100644 --- a/cmd/stop.go +++ b/cmd/stop.go @@ -14,7 +14,10 @@ limitations under the License. package cmd import ( + "fmt" "os" + "path/filepath" + "runtime" "github.com/spf13/cobra" @@ -30,13 +33,44 @@ var StopCmd = &cobra.Command{ Example: ` # Stop Dapr application dapr stop --app-id + +# Stop multiple apps by providing a run config file +dapr stop --run-file dapr.yaml + +# Stop multiple apps by providing a directory path containing the run config file(dapr.yaml) +dapr stop --run-file /path/to/directory `, Run: func(cmd *cobra.Command, args []string) { + var err error + if len(runFilePath) > 0 { + if runtime.GOOS == string(windowsOsType) { + print.FailureStatusEvent(os.Stderr, "Stop command with run file is not supported on Windows") + os.Exit(1) + } + runFilePath, err = getRunFilePath(runFilePath) + if err != nil { + print.FailureStatusEvent(os.Stderr, "Failed to get run file path: %v", err) + os.Exit(1) + } + err = executeStopWithRunFile(runFilePath) + if err != nil { + print.FailureStatusEvent(os.Stderr, "Failed to stop Dapr and app processes: %s", err) + } else { + print.SuccessStatusEvent(os.Stdout, "Dapr and app processes stopped successfully") + } + return + } if stopAppID != "" { args = append(args, stopAppID) } + apps, err := standalone.List() + if err != nil { + print.FailureStatusEvent(os.Stderr, "failed to get list of apps started by dapr : %s", err) + os.Exit(1) + } + cliPIDToNoOfApps := standalone.GetCLIPIDCountMap(apps) for _, appID := range args { - err := standalone.Stop(appID) + err = standalone.Stop(appID, cliPIDToNoOfApps, apps) if err != nil { print.FailureStatusEvent(os.Stderr, "failed to stop app id %s: %s", appID, err) } else { @@ -48,6 +82,15 @@ dapr stop --app-id func init() { StopCmd.Flags().StringVarP(&stopAppID, "app-id", "a", "", "The application id to be stopped") + StopCmd.Flags().StringVarP(&runFilePath, "run-file", "f", "", "Path to the run template file for the list of apps to stop") StopCmd.Flags().BoolP("help", "h", false, "Print this help message") RootCmd.AddCommand(StopCmd) } + +func executeStopWithRunFile(runFilePath string) error { + absFilePath, err := filepath.Abs(runFilePath) + if err != nil { + return fmt.Errorf("failed to get absolute file path for %s: %w", runFilePath, err) + } + return standalone.StopAppsWithRunFile(absFilePath) +} diff --git a/pkg/standalone/list.go b/pkg/standalone/list.go index 51c41430..7fa53abb 100644 --- a/pkg/standalone/list.go +++ b/pkg/standalone/list.go @@ -41,6 +41,7 @@ type ListOutput struct { CliPID int `csv:"CLI PID" json:"cliPid" yaml:"cliPid"` MaxRequestBodySize int `csv:"-" json:"maxRequestBodySize" yaml:"maxRequestBodySize"` // Additional field, not displayed in table. HTTPReadBufferSize int `csv:"-" json:"httpReadBufferSize" yaml:"httpReadBufferSize"` // Additional field, not displayed in table. + RunTemplatePath string `csv:"RUN_TEMPLATE_PATH" json:"runTemplatePath" yaml:"runTemplatePath"` } func (d *daprProcess) List() ([]ListOutput, error) { @@ -99,11 +100,13 @@ func List() ([]ListOutput, error) { appID := argumentsMap["--app-id"] appCmd := "" cliPIDString := "" + runTemplatePath := "" socket := argumentsMap["--unix-domain-socket"] appMetadata, err := metadata.Get(httpPort, appID, socket) if err == nil { appCmd = appMetadata.Extended["appCommand"] cliPIDString = appMetadata.Extended["cliPID"] + runTemplatePath = appMetadata.Extended["runTemplatePath"] } // Parse functions return an error on bad input. @@ -134,6 +137,7 @@ func List() ([]ListOutput, error) { Command: utils.TruncateString(appCmd, 20), MaxRequestBodySize: maxRequestBodySize, HTTPReadBufferSize: httpReadBufferSize, + RunTemplatePath: runTemplatePath, } // filter only dashboard instance. @@ -156,3 +160,12 @@ func getIntArg(argMap map[string]string, argKey string, argDef int) int { } return argDef } + +// GetCLIPIDCountMap returns a map of CLI PIDs to number of apps started with it. +func GetCLIPIDCountMap(apps []ListOutput) map[int]int { + cliPIDCountMap := make(map[int]int, len(apps)) + for _, app := range apps { + cliPIDCountMap[app.CliPID]++ + } + return cliPIDCountMap +} diff --git a/pkg/standalone/stop.go b/pkg/standalone/stop.go index 137166e1..040cdd6e 100644 --- a/pkg/standalone/stop.go +++ b/pkg/standalone/stop.go @@ -23,19 +23,15 @@ import ( ) // Stop terminates the application process. -func Stop(appID string) error { - apps, err := List() - if err != nil { - return err - } - +func Stop(appID string, cliPIDToNoOfApps map[int]int, apps []ListOutput) error { for _, a := range apps { if a.AppID == appID { var pid string // Kill the Daprd process if Daprd was started without CLI, otherwise // kill the CLI process which also kills the associated Daprd process. - if a.CliPID == 0 { + if a.CliPID == 0 || cliPIDToNoOfApps[a.CliPID] > 1 { pid = fmt.Sprintf("%v", a.DaprdPID) + cliPIDToNoOfApps[a.CliPID]-- } else { pid = fmt.Sprintf("%v", a.CliPID) } @@ -45,6 +41,20 @@ func Stop(appID string) error { return err } } - return fmt.Errorf("couldn't find app id %s", appID) } + +// StopAppsWithRunFile terminates the daprd and application processes with the given run file. +func StopAppsWithRunFile(runTemplatePath string) error { + apps, err := List() + if err != nil { + return err + } + for _, a := range apps { + if a.RunTemplatePath == runTemplatePath { + _, err := utils.RunCmdAndWait("kill", fmt.Sprintf("%v", a.CliPID)) + return err + } + } + return fmt.Errorf("couldn't find apps with run file %q", runTemplatePath) +} diff --git a/pkg/standalone/stop_windows.go b/pkg/standalone/stop_windows.go index 8d04f7ba..b12bc5c5 100644 --- a/pkg/standalone/stop_windows.go +++ b/pkg/standalone/stop_windows.go @@ -14,6 +14,7 @@ limitations under the License. package standalone import ( + "errors" "fmt" "syscall" @@ -21,12 +22,7 @@ import ( ) // Stop terminates the application process. -func Stop(appID string) error { - apps, err := List() - if err != nil { - return err - } - +func Stop(appID string, cliPIDToNoOfApps map[int]int, apps []ListOutput) error { for _, a := range apps { if a.AppID == appID { eventName, _ := syscall.UTF16FromString(fmt.Sprintf("dapr_cli_%v", a.CliPID)) @@ -39,6 +35,10 @@ func Stop(appID string) error { return err } } - return fmt.Errorf("couldn't find app id %s", appID) } + +// StopAppsWithRunFile terminates the daprd and application processes with the given run file. +func StopAppsWithRunFile(runFilePath string) error { + return errors.New("stopping apps with run template file is not supported on windows") +} diff --git a/tests/e2e/standalone/stop_test.go b/tests/e2e/standalone/stop_test.go index f6d1b921..20edabfa 100644 --- a/tests/e2e/standalone/stop_test.go +++ b/tests/e2e/standalone/stop_test.go @@ -39,6 +39,7 @@ func TestStandaloneStop(t *testing.T) { output, err := cmdStop("dapr_e2e_stop", "-p", "test") require.Error(t, err, "expected error on stop with unknown flag") require.Contains(t, output, "Error: unknown shorthand flag: 'p' in -p\nUsage:", "expected usage to be printed") - require.Contains(t, output, "-a, --app-id string The application id to be stopped", "expected usage to be printed") + require.Contains(t, output, "-a, --app-id string", "expected usage to be printed") + require.Contains(t, output, "-f, --run-file string", "expected usage to be printed") }) }