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

mindersec / minder / 23800567614

31 Mar 2026 01:44PM UTC coverage: 58.25% (-0.04%) from 58.29%
23800567614

Pull #6218

github

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

86 of 170 new or added lines in 3 files covered. (50.59%)

19300 of 33133 relevant lines covered (58.25%)

35.76 hits per line

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

58.11
/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(
119
        ctx context.Context,
120
        params *paramsPR,
121
        cmd interfaces.ActionCmd,
122
        actionParams interfaces.ActionsParams,
123
) (json.RawMessage, error) {
3✔
124
        logger := zerolog.Ctx(ctx)
3✔
125

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

133
        switch cmd {
3✔
134
        // ActionCmdOn: The evaluation failed. Set status to failure.
135
        case interfaces.ActionCmdOn:
2✔
136
                commitStatus := &github.RepoStatus{
2✔
137
                        Context:     github.String(contextStr),
2✔
138
                        State:       github.String("failure"),
2✔
139
                        Description: github.String("Minder evaluation failed"),
2✔
140
                }
2✔
141

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

154
                now := time.Now()
1✔
155
                newMeta, err := json.Marshal(alertMetadata{
1✔
156
                        SubmittedAt: &now,
1✔
157
                })
1✔
158
                if err != nil {
1✔
NEW
159
                        return nil, fmt.Errorf("error marshalling alert metadata json: %w", err)
×
NEW
160
                }
×
161

162
                logger.Info().Str("commit_sha", params.CommitSha).Msg("PR commit status updated to failure")
1✔
163
                return newMeta, nil
1✔
164

165
        // ActionCmdOff: The evaluation succeeded (or alert turned off). Set status to success.
166
        case interfaces.ActionCmdOff:
1✔
167
                commitStatus := &github.RepoStatus{
1✔
168
                        Context:     github.String(contextStr),
1✔
169
                        State:       github.String("success"),
1✔
170
                        Description: github.String("Minder evaluation succeeded"),
1✔
171
                }
1✔
172

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

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

NEW
188
        case interfaces.ActionCmdDoNothing:
×
NEW
189
                // Return the previous alert status.
×
NEW
190
                return alert.runDoNothing(ctx, params)
×
191
        }
NEW
192
        return nil, enginerr.ErrActionSkipped
×
193
}
194

195
// runDry runs the commit status action in dry run mode, logging what it would do
196
func (alert *Alert) runDry(
197
        ctx context.Context,
198
        params *paramsPR,
199
        cmd interfaces.ActionCmd,
200
        actionParams interfaces.ActionsParams,
NEW
201
) (json.RawMessage, error) {
×
NEW
202
        logger := zerolog.Ctx(ctx)
×
NEW
203

×
NEW
204
        contextStr := "minder"
×
NEW
205
        if rule := actionParams.GetRule(); rule != nil {
×
NEW
206
                contextStr = fmt.Sprintf("minder/%s", rule.Name)
×
NEW
207
        }
×
208

NEW
209
        switch cmd {
×
NEW
210
        case interfaces.ActionCmdOn:
×
NEW
211
                logger.Info().Msgf("dry run: set commit status to failure for context %s on PR %d in repo %s/%s",
×
NEW
212
                        contextStr, params.Number, params.Owner, params.Repo)
×
NEW
213
                return nil, nil
×
NEW
214
        case interfaces.ActionCmdOff:
×
NEW
215
                logger.Info().Msgf("dry run: set commit status to success for context %s on PR %d in repo %s/%s",
×
NEW
216
                        contextStr, params.Number, params.Owner, params.Repo)
×
NEW
217
                return nil, nil
×
NEW
218
        case interfaces.ActionCmdDoNothing:
×
NEW
219
                return alert.runDoNothing(ctx, params)
×
220
        }
NEW
221
        return nil, enginerr.ErrActionSkipped
×
222
}
223

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

×
NEW
229
        err := enginerr.AlertStatusAsError(params.prevStatus)
×
NEW
230
        if params.prevStatus != nil {
×
NEW
231
                return params.prevStatus.AlertMetadata, err
×
NEW
232
        }
×
NEW
233
        return nil, err
×
234
}
235

236
// getParamsForCommitStatus extracts the details from the entity
237
func (*Alert) getParamsForCommitStatus(
238
        ctx context.Context,
239
        pr *pbinternal.PullRequest,
240
        params interfaces.ActionsParams,
241
        metadata *json.RawMessage,
242
) (*paramsPR, error) {
3✔
243
        logger := zerolog.Ctx(ctx)
3✔
244
        result := &paramsPR{
3✔
245
                prevStatus: params.GetEvalStatusFromDb(),
3✔
246
                Owner:      pr.GetRepoOwner(),
3✔
247
                Repo:       pr.GetRepoName(),
3✔
248
                CommitSha:  pr.GetCommitSha(),
3✔
249
        }
3✔
250

3✔
251
        if pr.Number > int64(math.MaxInt32) {
3✔
NEW
252
                return nil, fmt.Errorf("pr number is too large")
×
NEW
253
        }
×
254
        result.Number = int(pr.Number)
3✔
255

3✔
256
        if metadata != nil {
3✔
NEW
257
                meta := &alertMetadata{}
×
NEW
258
                err := json.Unmarshal(*metadata, meta)
×
NEW
259
                if err != nil {
×
NEW
260
                        logger.Debug().Msgf("error unmarshalling alert metadata: %v", err)
×
NEW
261
                } else {
×
NEW
262
                        result.Metadata = meta
×
NEW
263
                }
×
264
        }
265

266
        return result, nil
3✔
267
}
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