diff --git a/changes/16856-fix-duplicate-activities-lock-unlock-scripts b/changes/16856-fix-duplicate-activities-lock-unlock-scripts new file mode 100644 index 0000000000..0600cc82e2 --- /dev/null +++ b/changes/16856-fix-duplicate-activities-lock-unlock-scripts @@ -0,0 +1 @@ +* Fixed generating duplicate activities when locking or unlocking a host with scripts disabled. diff --git a/server/datastore/mysql/scripts.go b/server/datastore/mysql/scripts.go index 8d22724fb2..e5fb77e744 100644 --- a/server/datastore/mysql/scripts.go +++ b/server/datastore/mysql/scripts.go @@ -54,6 +54,17 @@ func newHostScriptExecutionRequest(ctx context.Context, request *fleet.HostScrip } func (ds *Datastore) SetHostScriptExecutionResult(ctx context.Context, result *fleet.HostScriptResultPayload) (*fleet.HostScriptResult, error) { + const resultExistsStmt = ` + SELECT + 1 + FROM + host_script_results + WHERE + host_id = ? AND + execution_id = ? AND + exit_code IS NOT NULL +` + const updStmt = ` UPDATE host_script_results SET output = ?, @@ -91,6 +102,16 @@ func (ds *Datastore) SetHostScriptExecutionResult(ctx context.Context, result *f var hsr *fleet.HostScriptResult err := ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error { + var resultExists bool + err := sqlx.GetContext(ctx, tx, &resultExists, resultExistsStmt, result.HostID, result.ExecutionID) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + return ctxerr.Wrap(ctx, err, "check if host script result exists") + } + if resultExists { + // succeed but leave hsr nil + return nil + } + res, err := tx.ExecContext(ctx, updStmt, output, result.Runtime, diff --git a/server/datastore/mysql/scripts_test.go b/server/datastore/mysql/scripts_test.go index 5042d41e79..2466497e8e 100644 --- a/server/datastore/mysql/scripts_test.go +++ b/server/datastore/mysql/scripts_test.go @@ -90,6 +90,17 @@ func testHostScriptResult(t *testing.T, ds *Datastore) { }) require.NoError(t, err) + // record a duplicate result for this execution, will be ignored + hsr, err := ds.SetHostScriptExecutionResult(ctx, &fleet.HostScriptResultPayload{ + HostID: 1, + ExecutionID: createdScript.ExecutionID, + Output: "foobarbaz", + Runtime: 22, + ExitCode: 1, + }) + require.NoError(t, err) + require.Nil(t, hsr) + // it is not pending anymore pending, err = ds.ListPendingHostScriptExecutions(ctx, 1) require.NoError(t, err)