drone/internal/pipeline/canceler/canceler.go

158 lines
4.3 KiB
Go

// Copyright 2023 Harness, Inc.
//
// 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 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,
}
}
//nolint:gocognit // refactor if needed.
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.
if execution.Status != enum.CIStatusPending &&
execution.Status != enum.CIStatusRunning {
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
}