drone/internal/pipeline/canceler/canceler.go
2023-09-13 15:22:26 +00:00

144 lines
3.7 KiB
Go

package canceler
import (
"context"
"fmt"
"time"
"github.com/harness/gitness/internal/pipeline/scheduler"
"github.com/harness/gitness/internal/sse"
"github.com/harness/gitness/internal/store"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/log"
)
type service struct {
executionStore store.ExecutionStore
sseStreamer sse.Streamer
repoStore store.RepoStore
scheduler scheduler.Scheduler
stageStore store.StageStore
stepStore store.StepStore
}
// Canceler cancels a build.
type Canceler interface {
// Cancel cancels the provided execution.
Cancel(ctx context.Context, repo *types.Repository, execution *types.Execution) error
}
// New returns a cancellation service that encapsulates
// all cancellation operations.
func New(
executionStore store.ExecutionStore,
sseStreamer sse.Streamer,
repoStore store.RepoStore,
scheduler scheduler.Scheduler,
stageStore store.StageStore,
stepStore store.StepStore,
) Canceler {
return &service{
executionStore: executionStore,
sseStreamer: sseStreamer,
repoStore: repoStore,
scheduler: scheduler,
stageStore: stageStore,
stepStore: stepStore,
}
}
func (s *service) Cancel(ctx context.Context, repo *types.Repository, execution *types.Execution) error {
log := log.With().
Int64("execution.id", execution.ID).
Str("execution.status", string(execution.Status)).
Str("execution.Ref", execution.Ref).
Logger()
// do not cancel the build if the build status is
// complete. only cancel the build if the status is
// running or pending.
switch execution.Status {
case enum.CIStatusPending, enum.CIStatusRunning:
default:
return nil
}
// update the build status to killed. if the update fails
// due to an optimistic lock error it means the build has
// already started, and should now be ignored.
now := time.Now().UnixMilli()
execution.Status = enum.CIStatusKilled
execution.Finished = now
if execution.Started == 0 {
execution.Started = now
}
err := s.executionStore.Update(ctx, execution)
if err != nil {
return fmt.Errorf("could not update execution status to canceled: %w", err)
}
stages, err := s.stageStore.ListWithSteps(ctx, execution.ID)
if err != nil {
return fmt.Errorf("could not list stages with steps: %w", err)
}
// update the status of all steps to indicate they
// were killed or skipped.
for _, stage := range stages {
if stage.Status.IsDone() {
continue
}
if stage.Started != 0 {
stage.Status = enum.CIStatusKilled
} else {
stage.Status = enum.CIStatusSkipped
stage.Started = now
}
stage.Stopped = now
err := s.stageStore.Update(ctx, stage)
if err != nil {
log.Debug().Err(err).
Int64("stage.number", stage.Number).
Msg("canceler: cannot update stage status")
}
// update the status of all steps to indicate they
// were killed or skipped.
for _, step := range stage.Steps {
if step.Status.IsDone() {
continue
}
if step.Started != 0 {
step.Status = enum.CIStatusKilled
} else {
step.Status = enum.CIStatusSkipped
step.Started = now
}
step.Stopped = now
step.ExitCode = 130
err := s.stepStore.Update(ctx, step)
if err != nil {
log.Debug().Err(err).
Int64("stage.number", stage.Number).
Int64("step.number", step.Number).
Msg("canceler: cannot update step status")
}
}
}
execution.Stages = stages
log.Info().Msg("canceler: successfully cancelled build")
// trigger a SSE to notify subscribers that
// the execution was cancelled.
err = s.sseStreamer.Publish(ctx, repo.ParentID, enum.SSETypeExecutionCanceled, execution)
if err != nil {
log.Debug().Err(err).Msg("canceler: failed to publish server-sent event")
}
return nil
}