Skip to content

Commit 1e36332

Browse files
author
Praveenrajmani
committed
feat: add alerting API and types
Add alert types (AlertType, AlertEvent, AlertDetails) and the GetAlerts admin API for retrieving license and certificate expiry alerts.
1 parent 803d117 commit 1e36332

2 files changed

Lines changed: 132 additions & 0 deletions

File tree

alert.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// Copyright (c) 2015-2026 MinIO, Inc.
2+
//
3+
// This file is part of MinIO Object Storage stack
4+
//
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU Affero General Public License as published by
7+
// the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU Affero General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU Affero General Public License
16+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
18+
package madmin
19+
20+
import (
21+
"context"
22+
"encoding/json"
23+
"errors"
24+
"io"
25+
"iter"
26+
"net/http"
27+
"time"
28+
)
29+
30+
// AlertType represents the type of alert event
31+
type AlertType string
32+
33+
const (
34+
// AlertTypeLicense represents license expiration alerts
35+
AlertTypeLicense AlertType = "license-expiry"
36+
// AlertTypeCertificate represents TLS certificate expiration alerts
37+
AlertTypeCertificate AlertType = "certificate-expiry"
38+
)
39+
40+
// Alert represents a single alert event in the system.
41+
// It captures alert information with contextual metadata including deployment and cluster information.
42+
type Alert struct {
43+
Type AlertType `json:"type"`
44+
Timestamp time.Time `json:"timestamp"`
45+
Title string `json:"title"`
46+
Message string `json:"message"`
47+
Details AlertDetails `json:"details"`
48+
DeploymentID string `json:"deploymentId"`
49+
ClusterName string `json:"clusterName"`
50+
}
51+
52+
// AlertDetails is a strongly-typed union for alert-specific details.
53+
// Only one field should be populated based on the alert type.
54+
type AlertDetails struct {
55+
LicenseExpiry *LicenseExpiryDetails `json:"licenseExpiry,omitempty"`
56+
CertificateExpiry *CertificateExpiryDetails `json:"certificateExpiry,omitempty"`
57+
}
58+
59+
// LicenseExpiryDetails contains license-specific alert information.
60+
type LicenseExpiryDetails struct {
61+
LicenseID string `json:"licenseId"`
62+
ExpiresAt time.Time `json:"expiresAt"`
63+
State string `json:"state"` // "expiring_soon", "grace_period", "read_only", "fully_expired"
64+
}
65+
66+
// CertificateExpiryDetails contains certificate-specific alert information.
67+
type CertificateExpiryDetails struct {
68+
CommonName string `json:"commonName"`
69+
SerialNumber string `json:"serialNumber"`
70+
NotAfter time.Time `json:"notAfter"`
71+
DaysUntilExpiry int `json:"daysUntilExpiry"`
72+
DNSNames []string `json:"dnsNames"`
73+
}
74+
75+
// AlertLogOpts represents options for querying alerts
76+
type AlertLogOpts struct {
77+
Types []string `json:"types,omitempty"`
78+
Interval time.Duration `json:"interval,omitempty"`
79+
MaxPerNode int `json:"maxPerNode,omitempty"`
80+
}
81+
82+
// GetAlerts returns alerts stored in the system as a streaming JSON response
83+
// via POST /admin/alerts. Use AlertLogOpts.Interval to control the server-side
84+
// check interval.
85+
func (adm AdminClient) GetAlerts(ctx context.Context, opts AlertLogOpts) iter.Seq2[*Alert, error] {
86+
return func(yield func(*Alert, error) bool) {
87+
alertOpts, err := json.Marshal(opts)
88+
if err != nil {
89+
yield(nil, err)
90+
return
91+
}
92+
reqData := requestData{
93+
relPath: adminAPIPrefix + "/alerts",
94+
content: alertOpts,
95+
}
96+
resp, err := adm.executeMethod(ctx, http.MethodPost, reqData)
97+
if err != nil {
98+
yield(nil, err)
99+
return
100+
}
101+
defer closeResponse(resp)
102+
if resp.StatusCode != http.StatusOK {
103+
yield(nil, httpRespToErrorResponse(resp))
104+
return
105+
}
106+
dec := json.NewDecoder(resp.Body)
107+
for {
108+
var alert Alert
109+
if err = dec.Decode(&alert); err != nil {
110+
if errors.Is(err, io.EOF) {
111+
break
112+
}
113+
continue
114+
}
115+
select {
116+
case <-ctx.Done():
117+
return
118+
default:
119+
if !yield(&alert, nil) {
120+
return
121+
}
122+
}
123+
}
124+
}
125+
}

parse-config.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ const (
8888
LogAPIKafkaSubSys = "log_api_kafka"
8989
LogErrorKafkaSubSys = "log_error_kafka"
9090
LogAuditKafkaSubSys = "log_audit_kafka"
91+
92+
AlertInternalSubSys = "alert_internal"
93+
AlertWebhookSubSys = "alert_webhook"
94+
AlertKafkaSubSys = "alert_kafka"
9195
)
9296

9397
// SubSystems - list of all subsystems in MinIO
@@ -186,6 +190,9 @@ var EOSSubSystems = set.CreateStringSet(
186190
LogAPIKafkaSubSys,
187191
LogErrorKafkaSubSys,
188192
LogAuditKafkaSubSys,
193+
AlertInternalSubSys,
194+
AlertWebhookSubSys,
195+
AlertKafkaSubSys,
189196
)
190197

191198
// Standard config keys and values.

0 commit comments

Comments
 (0)