From 07b3cfcb92edf65fd2cf76c81d6bbadbcc6f20a5 Mon Sep 17 00:00:00 2001 From: Mukul Date: Sat, 18 Jul 2026 23:38:43 +0530 Subject: [PATCH 1/2] Exit non-zero when dapr stop or dapr uninstall fails dapr stop printed failure messages but always exited 0, so scripts and CI could not detect failed stops. The same applied to dapr uninstall. Also stop k8s run-file handling no longer proceeds after a template parse error, and no longer falls through into the self-hosted process scan after stopping Kubernetes deployments. Fixes #1667 Signed-off-by: Mukul --- cmd/stop.go | 12 ++++++++++-- cmd/uninstall.go | 4 ++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/cmd/stop.go b/cmd/stop.go index dd76baf138..acf1de4851 100644 --- a/cmd/stop.go +++ b/cmd/stop.go @@ -61,19 +61,22 @@ dapr stop --run-file /path/to/directory -k 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") + os.Exit(1) } + print.SuccessStatusEvent(os.Stdout, "Dapr and app processes stopped successfully") return } config, _, cErr := getRunConfigFromRunFile(runFilePath) if cErr != nil { print.FailureStatusEvent(os.Stderr, "Failed to parse run template file %q: %s", runFilePath, cErr.Error()) + os.Exit(1) } err = kubernetes.Stop(runFilePath, config) if err != nil { print.FailureStatusEvent(os.Stderr, "Error stopping deployments from multi-app run template: %v", err) + os.Exit(1) } + return } if stopAppID != "" { args = append(args, stopAppID) @@ -84,14 +87,19 @@ dapr stop --run-file /path/to/directory -k os.Exit(1) } cliPIDToNoOfApps := standalone.GetCLIPIDCountMap(apps) + stopFailed := false for _, appID := range args { err = standalone.Stop(appID, cliPIDToNoOfApps, apps) if err != nil { print.FailureStatusEvent(os.Stderr, "failed to stop app id %s: %s", appID, err) + stopFailed = true } else { print.SuccessStatusEvent(os.Stdout, "app stopped successfully: %s", appID) } } + if stopFailed { + os.Exit(1) + } }, } diff --git a/cmd/uninstall.go b/cmd/uninstall.go index 727e728ad8..7601bd05b3 100644 --- a/cmd/uninstall.go +++ b/cmd/uninstall.go @@ -90,9 +90,9 @@ dapr uninstall --runtime-path if err != nil { print.FailureStatusEvent(os.Stderr, fmt.Sprintf("Error removing Dapr: %s", err)) - } else { - print.SuccessStatusEvent(os.Stdout, "Dapr has been removed successfully") + os.Exit(1) } + print.SuccessStatusEvent(os.Stdout, "Dapr has been removed successfully") }, } From 8deb51986ec7bd7d681612e012858b5be3914af6 Mon Sep 17 00:00:00 2001 From: Mukul Date: Sun, 19 Jul 2026 00:10:17 +0530 Subject: [PATCH 2/2] Fix e2e for non-zero stop exit; tolerate already-deleted resources in k8s multi-app stop The stop_without_install e2e encoded the old exit-0-on-failure behavior; it now asserts a non-zero exit and serves as the regression test for it. dapr stop -f -k races with the graceful shutdown performed by the dapr run -f -k process when it receives the stop signal: whichever process deletes the app resources second got NotFound errors from kubectl. Those were previously invisible because stop always exited 0. Pass --ignore-not-found for the stop path's deletes so only real failures surface. Signed-off-by: Mukul --- pkg/kubernetes/run.go | 15 +++++++++++---- pkg/kubernetes/stop.go | 4 ++-- tests/e2e/standalone/init_negative_test.go | 2 +- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/pkg/kubernetes/run.go b/pkg/kubernetes/run.go index 9985e3406b..9360026093 100644 --- a/pkg/kubernetes/run.go +++ b/pkg/kubernetes/run.go @@ -391,13 +391,20 @@ func deployYamlToK8s(yamlToDeployPath string) error { return nil } -func deleteYamlK8s(yamlToDeletePath string) error { +func deleteYamlK8s(yamlToDeletePath string, ignoreNotFound bool) error { print.InfoStatusEvent(os.Stdout, "Deleting %q from Kubernetes", yamlToDeletePath) _, err := os.Stat(yamlToDeletePath) if os.IsNotExist(err) { return fmt.Errorf("error given file %q does not exist", yamlToDeletePath) } - _, err = utils.RunCmdAndWait("kubectl", "delete", "-f", yamlToDeletePath) + args := []string{"delete", "-f", yamlToDeletePath} + if ignoreNotFound { + // "dapr stop -f -k" races with the graceful shutdown performed by the + // "dapr run -f -k" process on receiving the stop signal; resources + // already deleted by the other process must not fail the stop. + args = append(args, "--ignore-not-found") + } + _, err = utils.RunCmdAndWait("kubectl", args...) if err != nil { return fmt.Errorf("error deleting the yaml %s from Kubernetes: %w", yamlToDeletePath, err) } @@ -412,9 +419,9 @@ func gracefullyShutdownK8sDeployment(runStates []runState, client k8s.Interface, errs := make([]error, 0, len(runStates)*4) for _, r := range runStates { if len(r.serviceFilePath) != 0 { - errs = append(errs, deleteYamlK8s(r.serviceFilePath)) + errs = append(errs, deleteYamlK8s(r.serviceFilePath, false)) } - errs = append(errs, deleteYamlK8s(r.deploymentFilePath)) + errs = append(errs, deleteYamlK8s(r.deploymentFilePath, false)) labelSelector := map[string]string{ daprAppIDKey: r.app.AppID, } diff --git a/pkg/kubernetes/stop.go b/pkg/kubernetes/stop.go index f2932bb7e2..993a021088 100644 --- a/pkg/kubernetes/stop.go +++ b/pkg/kubernetes/stop.go @@ -42,13 +42,13 @@ func Stop(runFilePath string, config runfileconfig.RunFileConfig) error { serviceFilePath := filepath.Join(deployDir, serviceFileName) deploymentFilePath := filepath.Join(deployDir, deploymentFileName) if app.CreateService { - err = deleteYamlK8s(serviceFilePath) + err = deleteYamlK8s(serviceFilePath, true) if err != nil { appError = true } errs = append(errs, err) } - err = deleteYamlK8s(deploymentFilePath) + err = deleteYamlK8s(deploymentFilePath, true) if err != nil { appError = true } diff --git a/tests/e2e/standalone/init_negative_test.go b/tests/e2e/standalone/init_negative_test.go index 9fe056852f..48f8dd1be3 100644 --- a/tests/e2e/standalone/init_negative_test.go +++ b/tests/e2e/standalone/init_negative_test.go @@ -54,7 +54,7 @@ func TestStandaloneInitNegatives(t *testing.T) { t.Run("stop without install", func(t *testing.T) { output, err := cmdStopWithAppID("test") - require.NoError(t, err, "expected no error on stop without install") + require.Error(t, err, "expected non-zero exit code on stop without install") require.Contains(t, output, "failed to stop app id test: couldn't find app id test", "expected output to match") })