mirror of https://github.com/agola-io/agola
207 lines
5.2 KiB
Go
207 lines
5.2 KiB
Go
// Copyright 2019 Sorint.lab
|
|
//
|
|
// 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 notification
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/sorintlab/errors"
|
|
|
|
"agola.io/agola/internal/services/common"
|
|
"agola.io/agola/internal/services/gateway/action"
|
|
"agola.io/agola/internal/sqlg/lock"
|
|
"agola.io/agola/internal/sqlg/sql"
|
|
"agola.io/agola/services/notification/types"
|
|
rstypes "agola.io/agola/services/runservice/types"
|
|
)
|
|
|
|
const (
|
|
commitStatusesCleanerLockKey = "commitStatusesCleaner"
|
|
|
|
maxCommitStatusesQueryLimit = 40
|
|
|
|
commitStatusesCleanerInterval = 1 * 24 * time.Hour
|
|
)
|
|
|
|
type commitStatus struct {
|
|
ProjectID string
|
|
CommitSHA string
|
|
State types.CommitState
|
|
RunCounter uint64
|
|
Description string
|
|
Context string
|
|
}
|
|
|
|
func (n *NotificationService) generateCommitStatus(ctx context.Context, ev *rstypes.RunEvent) (*commitStatus, error) {
|
|
var state types.CommitState
|
|
if ev.Phase == rstypes.RunPhaseSetupError {
|
|
state = types.CommitStateError
|
|
}
|
|
if ev.Phase == rstypes.RunPhaseCancelled {
|
|
state = types.CommitStateError
|
|
}
|
|
if ev.Phase == rstypes.RunPhaseRunning && ev.Result == rstypes.RunResultUnknown {
|
|
state = types.CommitStatePending
|
|
}
|
|
if ev.Phase == rstypes.RunPhaseFinished && ev.Result != rstypes.RunResultUnknown {
|
|
switch ev.Result {
|
|
case rstypes.RunResultSuccess:
|
|
state = types.CommitStateSuccess
|
|
case rstypes.RunResultStopped:
|
|
fallthrough
|
|
case rstypes.RunResultFailed:
|
|
state = types.CommitStateFailed
|
|
}
|
|
}
|
|
|
|
if state == "" {
|
|
return nil, nil
|
|
}
|
|
|
|
run, _, err := n.runserviceClient.GetRun(ctx, ev.RunID, nil)
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
groupType, groupID, err := common.GroupTypeIDFromRunGroup(run.RunConfig.Group)
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
// ignore user direct runs
|
|
if groupType == common.GroupTypeUser {
|
|
return nil, nil
|
|
}
|
|
|
|
project, _, err := n.configstoreClient.GetProject(ctx, groupID)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to get project %s", groupID)
|
|
}
|
|
|
|
context := fmt.Sprintf("%s/%s/%s", n.gc.ID, project.Name, run.RunConfig.Name)
|
|
|
|
return &commitStatus{
|
|
ProjectID: project.ID,
|
|
State: state,
|
|
CommitSHA: run.Run.Annotations[action.AnnotationCommitSHA],
|
|
RunCounter: run.Run.Counter,
|
|
Description: statusDescription(state),
|
|
Context: context,
|
|
}, nil
|
|
}
|
|
|
|
func webRunURL(webExposedURL, projectID string, runNumber uint64) (string, error) {
|
|
u, err := url.Parse(webExposedURL + "/run")
|
|
if err != nil {
|
|
return "", errors.WithStack(err)
|
|
}
|
|
q := url.Values{}
|
|
q.Set("projectref", projectID)
|
|
q.Set("runnumber", strconv.FormatUint(runNumber, 10))
|
|
|
|
u.RawQuery = q.Encode()
|
|
|
|
return u.String(), nil
|
|
}
|
|
|
|
func statusDescription(state types.CommitState) string {
|
|
switch state {
|
|
case types.CommitStatePending:
|
|
return "The run is pending"
|
|
case types.CommitStateSuccess:
|
|
return "The run finished successfully"
|
|
case types.CommitStateError:
|
|
return "The run encountered an error"
|
|
case types.CommitStateFailed:
|
|
return "The run failed"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func (n *NotificationService) commitStatusesCleanerLoop(ctx context.Context, commitStatusExpireInterval time.Duration) {
|
|
n.log.Debug().Msg("commitStatusesCleanerLoop")
|
|
|
|
for {
|
|
if err := n.commitStatusesCleaner(ctx, commitStatusExpireInterval); err != nil {
|
|
n.log.Warn().Err(err).Msg("commitStatusesCleaner error")
|
|
}
|
|
|
|
sleepCh := time.NewTimer(commitStatusesCleanerInterval).C
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-sleepCh:
|
|
}
|
|
}
|
|
}
|
|
|
|
func (n *NotificationService) commitStatusesCleaner(ctx context.Context, commitStatusExpireInterval time.Duration) error {
|
|
l := n.lf.NewLock(commitStatusesCleanerLockKey)
|
|
if err := l.TryLock(ctx); err != nil {
|
|
if errors.Is(err, lock.ErrLocked) {
|
|
return nil
|
|
}
|
|
return errors.WithStack(err)
|
|
}
|
|
defer func() { _ = l.Unlock() }()
|
|
|
|
var afterCommitStatusID string
|
|
|
|
for {
|
|
var commitStatuses []*types.CommitStatus
|
|
|
|
err := n.d.Do(ctx, func(tx *sql.Tx) error {
|
|
var err error
|
|
commitStatuses, err = n.d.GetCommitStatusesAfterCommitStatusID(tx, afterCommitStatusID, maxCommitStatusesQueryLimit)
|
|
if err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
|
|
for _, c := range commitStatuses {
|
|
if time.Since(c.CreationTime) < commitStatusExpireInterval {
|
|
continue
|
|
}
|
|
|
|
err = n.d.DeleteCommitStatusDeliveriesByCommitStatusID(tx, c.ID)
|
|
if err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
|
|
err = n.d.DeleteCommitStatus(tx, c.ID)
|
|
if err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
|
|
if len(commitStatuses) < maxCommitStatusesQueryLimit {
|
|
break
|
|
}
|
|
|
|
afterCommitStatusID = commitStatuses[len(commitStatuses)-1].ID
|
|
}
|
|
|
|
return nil
|
|
}
|