blob: 5fb51bbbe81926361965aa290dd342019e00c965 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package rules
import (
"fmt"
"strings"
"time"
ds "go.chromium.org/luci/gae/service/datastore"
)
// AuditStatus is the enum for RelevantCommit.Status.
type AuditStatus int
const (
// AuditScheduled is the status when an audit is scheduled.
AuditScheduled AuditStatus = iota
// AuditCompleted is the status when an audit has been completed.
AuditCompleted
// AuditCompletedWithActionRequired is the status when an audit has
// completed but requires some actions. e.g. notifications.
AuditCompletedWithActionRequired
// AuditFailed is the status when an audit has failed.
AuditFailed
// AuditPending is the status when some rules may not be decidable yet.
AuditPending
)
// ToString returns a human-readable version of this status.
func (as AuditStatus) ToString() string {
switch as {
case AuditScheduled:
return "Audit Scheduled"
case AuditCompleted:
return "Audited OK"
case AuditCompletedWithActionRequired:
return "Action Required"
case AuditFailed:
return "Audit Failed"
default:
return fmt.Sprintf("Unknown status: %d", int(as))
}
}
// ColorCode returns a string used to color code the string, as a css class for
// example.
func (as AuditStatus) ColorCode() string {
switch as {
case AuditCompleted:
return "green-status"
case AuditCompletedWithActionRequired:
return "red-status"
case AuditFailed:
return "red-status"
default:
return "normal-status"
}
}
// ToShortString returns a short string version of this status meant to be used
// as datapoint labels for metrics.
func (as AuditStatus) ToShortString() string {
switch as {
case AuditCompleted:
return "passed"
case AuditCompletedWithActionRequired:
return "action required"
case AuditFailed:
return "failed"
case AuditScheduled:
return "scheduled"
case AuditPending:
return "pending recheck"
default:
return fmt.Sprintf("unknown:%d", int(as))
}
}
// RuleStatus is the enum for RuleResult.RuleResultStatus.
type RuleStatus int
const (
// RuleFailed is the status when a rule has failed.
RuleFailed RuleStatus = iota
// RulePassed is the status when a rule has passed.
RulePassed
// RuleSkipped is the status when a rule has been skipped.
RuleSkipped
// NotificationRequired is the status when a rule requires notifications.
NotificationRequired
// RulePending is the status when a rule's result cannot be decided.
RulePending
// RuleInvalid is an invalid value, for testing.
RuleInvalid
)
// ToString returns a human-readable version of this status.
func (rs RuleStatus) ToString() string {
switch rs {
case RuleFailed:
return "Rule Failed"
case RulePassed:
return "Rule Passed"
case RuleSkipped:
return "Rule Skipped"
case NotificationRequired:
return "Notification Required"
case RulePending:
return "Rule Pending"
default:
return fmt.Sprintf("Unknown status: %d", int(rs))
}
}
// ColorCode returns a stirng used to color code the string, as a css class for
// example.
func (rs RuleStatus) ColorCode() string {
switch rs {
case RuleFailed:
return "red-status"
case RulePassed:
return "green-status"
default:
return "normal-status"
}
}
// RepoState contains the state for each ref we audit. Including
// parameters applicable to dynamically configured refs.
type RepoState struct {
// RepoURL is expected to point to a branch e.g.
// https://chromium.googlesource.com/chromium/src.git/+/master
RepoURL string `gae:"$id"`
LastKnownCommit string
LastKnownCommitTime time.Time
LastRelevantCommit string
LastRelevantCommitTime time.Time
// LastUpdatedTime is the wall clock time when the last scan was
// successful.
LastUpdatedTime time.Time
// Paused indicates whether this ref paused begin audited. This pausing
// could be caused by: 1) A non-fast-forward update makes LastKnownCommit
// become an unaccessible commit; 2) Gitiles git.Log API returns too many
// commits or encounters some errors, which causes the time diff between
// current time and LastUpdatedTime to exceed staleHours; 3) The number
// of commits that Gitiles git.Log API returns equals to
// MaxCommitsPerRefUpdate.
Paused bool
// AcceptedOverwriteLastKnownCommit saves those OverwriteLastKnownCommit
// which have already been used for unpausing a ref.
AcceptedOverwriteLastKnownCommit string
// This is the key of the configuration in RuleMap that applies to
// this git ref. Note that each ref can only be matched to one such
// configuration.
ConfigName string
// These are overridden in the repo config differently for each ref.
Metadata string
BranchName string
}
// RelevantCommit points to a node in a linked list of commits that have
// been considered relevant by CommitScanner.
type RelevantCommit struct {
RepoStateKey *ds.Key `gae:"$parent"`
CommitHash string `gae:"$id"`
PreviousRelevantCommit string
Status AuditStatus
Result []RuleResult
CommitTime time.Time
CommitterAccount string
AuthorAccount string
CommitMessage string `gae:",noindex"`
Retries int32
// This will catch deprecated fields such as IssueID
LegacyFields ds.PropertyMap `gae:",extra"`
// NotifiedAll will be true if all applicable notifications have been
// processed.
NotifiedAll bool
// NotificationStates will have strings of the form `key:value` where
// the key identifies a specific ruleset that might apply to this
// commit and value is a freeform string that makes sense to the
// notification function, used to keep track of the state between
// retries. e.g. To avoid sending duplicate emails if the notification
// sends multiple emails and only partially succeeds on the first
// attempt.
NotificationStates []string
// LastExternalPoll records when the commit was last attempted
// to be audited. This is useful for audit rules that can be left
// undecided for a period of time, such as TBR auditing in order to
// limit the frequency of polling external systems.
LastExternalPoll time.Time
}
// SetResult appends the given RuleResult to the array of results for this commit,
// or update it if one with the same RuleName is already present.
// Returns a boolean indicating whether a change was performed.
func (rc *RelevantCommit) SetResult(rr RuleResult) bool {
for i, curr := range rc.Result {
if curr.RuleName == rr.RuleName {
if curr == rr {
return false
}
rc.Result[i] = rr
return true
}
}
rc.Result = append(rc.Result, rr)
return true
}
// RuleResult represents the result of applying a single rule to a commit.
type RuleResult struct {
RuleName string
RuleResultStatus RuleStatus
Message string
// Freeform string that can be used by rules to pass data to notifiers.
// Notably used by the .GetToken and .SetToken methods.
MetaData string `gae:",noindex"`
}
// GetViolations returns the subset of RuleResults that are violations.
func (rc *RelevantCommit) GetViolations() []RuleResult {
violations := []RuleResult{}
for _, rr := range rc.Result {
if rr.RuleResultStatus == RuleFailed {
violations = append(violations, rr)
}
}
return violations
}
// SetNotificationState stores the state for a given rule set.
func (rc *RelevantCommit) SetNotificationState(ruleSetName string, state string) {
prefix := fmt.Sprintf("%s:", ruleSetName)
fullTag := fmt.Sprintf("%s:%s", ruleSetName, state)
for i, v := range rc.NotificationStates {
if strings.HasPrefix(v, prefix) {
rc.NotificationStates[i] = fullTag
return
}
}
rc.NotificationStates = append(rc.NotificationStates, fullTag)
}
// GetNotificationState retrieves the state for a rule set from the
// NotificationStates field.
func (rc *RelevantCommit) GetNotificationState(ruleSetName string) string {
prefix := fmt.Sprintf("%s:", ruleSetName)
for _, v := range rc.NotificationStates {
if strings.HasPrefix(v, prefix) {
return strings.TrimPrefix(v, prefix)
}
}
return ""
}