• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mindersec / minder / 23797736810

31 Mar 2026 12:39PM UTC coverage: 58.252% (-0.04%) from 58.29%
23797736810

Pull #6218

github

web-flow
Merge a3c82f8ce into 6a56c3c8d
Pull Request #6218: Support reporting status checks and comments from PR rules

86 of 169 new or added lines in 3 files covered. (50.89%)

19300 of 33132 relevant lines covered (58.25%)

35.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

58.5
/internal/engine/actions/alert/commit_status/commit_status.go
1
// SPDX-FileCopyrightText: Copyright 2024 The Minder Authors
2
// SPDX-License-Identifier: Apache-2.0
3

4
// Package commit_status provides necessary interfaces and implementations for
5
// processing pull request commit status check alerts.
6
package commit_status
7

8
import (
9
        "context"
10
        "encoding/json"
11
        "fmt"
12
        "math"
13
        "time"
14

15
        "github.com/google/go-github/v63/github"
16
        "github.com/rs/zerolog"
17
        "google.golang.org/protobuf/reflect/protoreflect"
18

19
        "github.com/mindersec/minder/internal/db"
20
        enginerr "github.com/mindersec/minder/internal/engine/errors"
21
        "github.com/mindersec/minder/internal/engine/interfaces"
22
        pbinternal "github.com/mindersec/minder/internal/proto"
23
        pb "github.com/mindersec/minder/pkg/api/protobuf/go/minder/v1"
24
        "github.com/mindersec/minder/pkg/profiles/models"
25
        provifv1 "github.com/mindersec/minder/pkg/providers/v1"
26
)
27

28
const (
29
        // AlertType is the type of the commit status alert engine
30
        AlertType = "commit_status"
31
)
32

33
// Alert is the structure backing the commit status alert
34
type Alert struct {
35
        actionType interfaces.ActionType
36
        gh         provifv1.GitHub
37
        alertCfg   *pb.RuleType_Definition_Alert_AlertTypeCommitStatus
38
        setting    models.ActionOpt
39
}
40

41
type paramsPR struct {
42
        Owner      string
43
        Repo       string
44
        CommitSha  string
45
        Number     int
46
        Metadata   *alertMetadata
47
        prevStatus *db.ListRuleEvaluationsByProfileIdRow
48
}
49

50
type alertMetadata struct {
51
        SubmittedAt *time.Time `json:"submitted_at,omitempty"`
52
}
53

54
// NewCommitStatusAlert creates a new commit status alert action
55
func NewCommitStatusAlert(
56
        actionType interfaces.ActionType,
57
        alertCfg *pb.RuleType_Definition_Alert_AlertTypeCommitStatus,
58
        gh provifv1.GitHub,
59
        setting models.ActionOpt,
60
) (*Alert, error) {
3✔
61
        if actionType == "" {
3✔
NEW
62
                return nil, fmt.Errorf("action type cannot be empty")
×
NEW
63
        }
×
64

65
        return &Alert{
3✔
66
                actionType: actionType,
3✔
67
                gh:         gh,
3✔
68
                alertCfg:   alertCfg,
3✔
69
                setting:    setting,
3✔
70
        }, nil
3✔
71
}
72

73
// Class returns the action type of the commit status alert engine
74
func (alert *Alert) Class() interfaces.ActionType {
1✔
75
        return alert.actionType
1✔
76
}
1✔
77

78
// Type returns the action subtype of the commit status alert engine
NEW
79
func (*Alert) Type() string {
×
NEW
80
        return AlertType
×
NEW
81
}
×
82

83
// GetOnOffState returns the alert action state read from the profile
NEW
84
func (alert *Alert) GetOnOffState() models.ActionOpt {
×
NEW
85
        return models.ActionOptOrDefault(alert.setting, models.ActionOptOff)
×
NEW
86
}
×
87

88
// Do sets the commit status on a pull request
89
func (alert *Alert) Do(
90
        ctx context.Context,
91
        cmd interfaces.ActionCmd,
92
        entity protoreflect.ProtoMessage,
93
        params interfaces.ActionsParams,
94
        metadata *json.RawMessage,
95
) (json.RawMessage, error) {
3✔
96
        pr, ok := entity.(*pbinternal.PullRequest)
3✔
97
        if !ok {
3✔
NEW
98
                return nil, fmt.Errorf("expected pull request, got %T", entity)
×
NEW
99
        }
×
100

101
        commitStatusParams, err := alert.getParamsForCommitStatus(ctx, pr, params, metadata)
3✔
102
        if err != nil {
3✔
NEW
103
                return nil, fmt.Errorf("error extracting parameters for commit status: %w", err)
×
NEW
104
        }
×
105

106
        // Process the command based on the action setting
107
        switch alert.setting {
3✔
108
        case models.ActionOptOn:
3✔
109
                return alert.run(ctx, commitStatusParams, cmd, params)
3✔
NEW
110
        case models.ActionOptDryRun:
×
NEW
111
                return alert.runDry(ctx, commitStatusParams, cmd, params)
×
NEW
112
        case models.ActionOptOff, models.ActionOptUnknown:
×
NEW
113
                return nil, fmt.Errorf("unexpected action setting: %w", enginerr.ErrActionFailed)
×
114
        }
NEW
115
        return nil, enginerr.ErrActionSkipped
×
116
}
117

118
func (alert *Alert) run(ctx context.Context, params *paramsPR, cmd interfaces.ActionCmd, actionParams interfaces.ActionsParams) (json.RawMessage, error) {
3✔
119
        logger := zerolog.Ctx(ctx)
3✔
120

3✔
121
        // Context string for the commit status check (e.g. "minder/rule_name")
3✔
122
        // If rule_name isn't available, we fallback to "minder"
3✔
123
        contextStr := "minder"
3✔
124
        if rule := actionParams.GetRule(); rule != nil {
6✔
125
                contextStr = fmt.Sprintf("minder/%s", rule.Name)
3✔
126
        }
3✔
127

128
        switch cmd {
3✔
129
        // ActionCmdOn: The evaluation failed. Set status to failure.
130
        case interfaces.ActionCmdOn:
2✔
131
                commitStatus := &github.RepoStatus{
2✔
132
                        Context:     github.String(contextStr),
2✔
133
                        State:       github.String("failure"),
2✔
134
                        Description: github.String("Minder evaluation failed"),
2✔
135
                }
2✔
136

2✔
137
                _, err := alert.gh.SetCommitStatus(
2✔
138
                        ctx,
2✔
139
                        params.Owner,
2✔
140
                        params.Repo,
2✔
141
                        params.CommitSha,
2✔
142
                        commitStatus,
2✔
143
                )
2✔
144
                if err != nil {
3✔
145
                        logger.Error().Err(err).Msg("error setting commit status")
1✔
146
                        return nil, enginerr.ErrActionFailed
1✔
147
                }
1✔
148

149
                now := time.Now()
1✔
150
                newMeta, err := json.Marshal(alertMetadata{
1✔
151
                        SubmittedAt: &now,
1✔
152
                })
1✔
153
                if err != nil {
1✔
NEW
154
                        return nil, fmt.Errorf("error marshalling alert metadata json: %w", err)
×
NEW
155
                }
×
156

157
                logger.Info().Str("commit_sha", params.CommitSha).Msg("PR commit status updated to failure")
1✔
158
                return newMeta, nil
1✔
159

160
        // ActionCmdOff: The evaluation succeeded (or alert turned off). Set status to success.
161
        case interfaces.ActionCmdOff:
1✔
162
                commitStatus := &github.RepoStatus{
1✔
163
                        Context:     github.String(contextStr),
1✔
164
                        State:       github.String("success"),
1✔
165
                        Description: github.String("Minder evaluation succeeded"),
1✔
166
                }
1✔
167

1✔
168
                _, err := alert.gh.SetCommitStatus(
1✔
169
                        ctx,
1✔
170
                        params.Owner,
1✔
171
                        params.Repo,
1✔
172
                        params.CommitSha,
1✔
173
                        commitStatus,
1✔
174
                )
1✔
175
                if err != nil {
1✔
NEW
176
                        logger.Error().Err(err).Msg("error setting commit status")
×
NEW
177
                }
×
178

179
                logger.Info().Str("commit_sha", params.CommitSha).Msg("PR commit status updated to success")
1✔
180
                // Return ErrActionTurnedOff to indicate the action resolved appropriately
1✔
181
                return nil, fmt.Errorf("%s : %w", alert.Class(), enginerr.ErrActionTurnedOff)
1✔
182

NEW
183
        case interfaces.ActionCmdDoNothing:
×
NEW
184
                // Return the previous alert status.
×
NEW
185
                return alert.runDoNothing(ctx, params)
×
186
        }
NEW
187
        return nil, enginerr.ErrActionSkipped
×
188
}
189

190
// runDry runs the commit status action in dry run mode, logging what it would do
NEW
191
func (alert *Alert) runDry(ctx context.Context, params *paramsPR, cmd interfaces.ActionCmd, actionParams interfaces.ActionsParams) (json.RawMessage, error) {
×
NEW
192
        logger := zerolog.Ctx(ctx)
×
NEW
193

×
NEW
194
        contextStr := "minder"
×
NEW
195
        if rule := actionParams.GetRule(); rule != nil {
×
NEW
196
                contextStr = fmt.Sprintf("minder/%s", rule.Name)
×
NEW
197
        }
×
198

NEW
199
        switch cmd {
×
NEW
200
        case interfaces.ActionCmdOn:
×
NEW
201
                logger.Info().Msgf("dry run: set commit status to failure for context %s on PR %d in repo %s/%s",
×
NEW
202
                        contextStr, params.Number, params.Owner, params.Repo)
×
NEW
203
                return nil, nil
×
NEW
204
        case interfaces.ActionCmdOff:
×
NEW
205
                logger.Info().Msgf("dry run: set commit status to success for context %s on PR %d in repo %s/%s",
×
NEW
206
                        contextStr, params.Number, params.Owner, params.Repo)
×
NEW
207
        case interfaces.ActionCmdDoNothing:
×
NEW
208
                return alert.runDoNothing(ctx, params)
×
209
        }
NEW
210
        return nil, enginerr.ErrActionSkipped
×
211
}
212

213
// runDoNothing returns the previous alert status
NEW
214
func (*Alert) runDoNothing(ctx context.Context, params *paramsPR) (json.RawMessage, error) {
×
NEW
215
        logger := zerolog.Ctx(ctx).With().Str("repo", params.Repo).Logger()
×
NEW
216
        logger.Debug().Msg("Running do nothing")
×
NEW
217

×
NEW
218
        err := enginerr.AlertStatusAsError(params.prevStatus)
×
NEW
219
        if params.prevStatus != nil {
×
NEW
220
                return params.prevStatus.AlertMetadata, err
×
NEW
221
        }
×
NEW
222
        return nil, err
×
223
}
224

225
// getParamsForCommitStatus extracts the details from the entity
226
func (alert *Alert) getParamsForCommitStatus(
227
        ctx context.Context,
228
        pr *pbinternal.PullRequest,
229
        params interfaces.ActionsParams,
230
        metadata *json.RawMessage,
231
) (*paramsPR, error) {
3✔
232
        logger := zerolog.Ctx(ctx)
3✔
233
        result := &paramsPR{
3✔
234
                prevStatus: params.GetEvalStatusFromDb(),
3✔
235
                Owner:      pr.GetRepoOwner(),
3✔
236
                Repo:       pr.GetRepoName(),
3✔
237
                CommitSha:  pr.GetCommitSha(),
3✔
238
        }
3✔
239

3✔
240
        if pr.Number > math.MaxInt32 {
3✔
NEW
241
                return nil, fmt.Errorf("pr number is too large")
×
NEW
242
        }
×
243
        result.Number = int(pr.Number)
3✔
244

3✔
245
        if metadata != nil {
3✔
NEW
246
                meta := &alertMetadata{}
×
NEW
247
                err := json.Unmarshal(*metadata, meta)
×
NEW
248
                if err != nil {
×
NEW
249
                        logger.Debug().Msgf("error unmarshalling alert metadata: %v", err)
×
NEW
250
                } else {
×
NEW
251
                        result.Metadata = meta
×
NEW
252
                }
×
253
        }
254

255
        return result, nil
3✔
256
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc