Skip to content

Commit 4f3a706

Browse files
committed
Add executor module for external system updates
This commit introduces a new executor module that decouples investigation logic from external system actions. Key changes: **New packages:** - pkg/investigations/types: Shared Action and ExecutionContext types - pkg/executor: Executor implementation with action builders **Architecture:** - Action interface defines external system updates (service logs, limited support, PagerDuty notes, incident management) - ExecutionContext provides strongly-typed resources for action execution - Executor executes actions with retry logic and error handling - Builder pattern for constructing actions and results **Features:** - Type-safe action builders (no interface{} types) - Sequential and concurrent action execution - Automatic retry for transient failures - Validation before execution - Support for dry-run mode **Backwards compatibility:** - InvestigationResult extended with Actions field - Legacy fields (ServiceLogSent, etc.) preserved - No breaking changes to existing investigations Related documentation: - docs/architecture/builder-pattern.md: Builder pattern guide
1 parent 849fe12 commit 4f3a706

File tree

10 files changed

+1778
-0
lines changed

10 files changed

+1778
-0
lines changed

docs/architecture/builder-pattern.md

Lines changed: 713 additions & 0 deletions
Large diffs are not rendered by default.

pkg/executor/action_builders.go

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
package executor
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/openshift/configuration-anomaly-detection/pkg/notewriter"
8+
"github.com/openshift/configuration-anomaly-detection/pkg/ocm"
9+
)
10+
11+
// ServiceLogActionBuilder builds ServiceLogAction instances
12+
type ServiceLogActionBuilder struct {
13+
severity string
14+
serviceName string
15+
summary string
16+
description string
17+
internalOnly bool
18+
reason string
19+
allowDuplicates bool
20+
}
21+
22+
// NewServiceLogAction creates a builder with required fields
23+
// severity: "Info", "Warning", "Major", "Critical"
24+
// summary: Brief title of the service log
25+
func NewServiceLogAction(severity, summary string) *ServiceLogActionBuilder {
26+
return &ServiceLogActionBuilder{
27+
severity: severity,
28+
summary: summary,
29+
serviceName: "SREManualAction", // Default service name
30+
internalOnly: false, // Default to customer-visible
31+
allowDuplicates: false, // Default to skip duplicates
32+
}
33+
}
34+
35+
// WithDescription sets the detailed description
36+
func (b *ServiceLogActionBuilder) WithDescription(description string) *ServiceLogActionBuilder {
37+
b.description = description
38+
return b
39+
}
40+
41+
// WithServiceName sets the service name (defaults to "SREManualAction")
42+
func (b *ServiceLogActionBuilder) WithServiceName(name string) *ServiceLogActionBuilder {
43+
b.serviceName = name
44+
return b
45+
}
46+
47+
// InternalOnly marks the service log as internal-only (not visible to customer)
48+
func (b *ServiceLogActionBuilder) InternalOnly() *ServiceLogActionBuilder {
49+
b.internalOnly = true
50+
return b
51+
}
52+
53+
// WithReason sets the reason for logging purposes
54+
func (b *ServiceLogActionBuilder) WithReason(reason string) *ServiceLogActionBuilder {
55+
b.reason = reason
56+
return b
57+
}
58+
59+
// AllowDuplicates permits sending even if identical service log exists
60+
func (b *ServiceLogActionBuilder) AllowDuplicates() *ServiceLogActionBuilder {
61+
b.allowDuplicates = true
62+
return b
63+
}
64+
65+
// Build creates the ServiceLogAction
66+
func (b *ServiceLogActionBuilder) Build() Action {
67+
return &ServiceLogAction{
68+
ServiceLog: &ocm.ServiceLog{
69+
Severity: b.severity,
70+
ServiceName: b.serviceName,
71+
Summary: b.summary,
72+
Description: b.description,
73+
InternalOnly: b.internalOnly,
74+
},
75+
Reason: b.reason,
76+
AllowDuplicates: b.allowDuplicates,
77+
}
78+
}
79+
80+
// LimitedSupportActionBuilder builds LimitedSupportAction instances
81+
type LimitedSupportActionBuilder struct {
82+
summary string
83+
details string
84+
context string
85+
allowDuplicates bool
86+
}
87+
88+
// NewLimitedSupportAction creates a builder with required fields
89+
// summary: Brief reason for limited support
90+
// details: Detailed explanation including remediation steps
91+
func NewLimitedSupportAction(summary, details string) *LimitedSupportActionBuilder {
92+
return &LimitedSupportActionBuilder{
93+
summary: summary,
94+
details: details,
95+
allowDuplicates: false, // Default to skip duplicates
96+
}
97+
}
98+
99+
// WithContext adds context for logging/metrics
100+
func (b *LimitedSupportActionBuilder) WithContext(context string) *LimitedSupportActionBuilder {
101+
b.context = context
102+
return b
103+
}
104+
105+
// AllowDuplicates permits setting even if identical LS exists
106+
func (b *LimitedSupportActionBuilder) AllowDuplicates() *LimitedSupportActionBuilder {
107+
b.allowDuplicates = true
108+
return b
109+
}
110+
111+
// Build creates the LimitedSupportAction
112+
func (b *LimitedSupportActionBuilder) Build() Action {
113+
return &LimitedSupportAction{
114+
Reason: &ocm.LimitedSupportReason{
115+
Summary: b.summary,
116+
Details: b.details,
117+
},
118+
Context: b.context,
119+
AllowDuplicates: b.allowDuplicates,
120+
}
121+
}
122+
123+
// PagerDutyNoteActionBuilder builds PagerDutyNoteAction instances
124+
type PagerDutyNoteActionBuilder struct {
125+
content strings.Builder
126+
}
127+
128+
// NewPagerDutyNoteAction creates a builder
129+
// Can be initialized empty and built up, or with initial content
130+
func NewPagerDutyNoteAction(initialContent ...string) *PagerDutyNoteActionBuilder {
131+
b := &PagerDutyNoteActionBuilder{}
132+
133+
if len(initialContent) > 0 {
134+
b.content.WriteString(initialContent[0])
135+
}
136+
137+
return b
138+
}
139+
140+
// WithContent sets the note content (replaces existing)
141+
func (b *PagerDutyNoteActionBuilder) WithContent(content string) *PagerDutyNoteActionBuilder {
142+
b.content.Reset()
143+
b.content.WriteString(content)
144+
return b
145+
}
146+
147+
// AppendLine adds a line to the note
148+
func (b *PagerDutyNoteActionBuilder) AppendLine(line string) *PagerDutyNoteActionBuilder {
149+
if b.content.Len() > 0 {
150+
b.content.WriteString("\n")
151+
}
152+
b.content.WriteString(line)
153+
return b
154+
}
155+
156+
// AppendSection adds a section with a header
157+
func (b *PagerDutyNoteActionBuilder) AppendSection(header, content string) *PagerDutyNoteActionBuilder {
158+
if b.content.Len() > 0 {
159+
b.content.WriteString("\n\n")
160+
}
161+
b.content.WriteString(fmt.Sprintf("## %s\n%s", header, content))
162+
return b
163+
}
164+
165+
// FromNoteWriter uses a notewriter's content
166+
func (b *PagerDutyNoteActionBuilder) FromNoteWriter(nw *notewriter.NoteWriter) *PagerDutyNoteActionBuilder {
167+
return b.WithContent(nw.String())
168+
}
169+
170+
// Build creates the PagerDutyNoteAction
171+
func (b *PagerDutyNoteActionBuilder) Build() Action {
172+
return &PagerDutyNoteAction{
173+
Content: b.content.String(),
174+
}
175+
}
176+
177+
// SilenceIncidentActionBuilder builds SilenceIncidentAction instances
178+
type SilenceIncidentActionBuilder struct {
179+
reason string
180+
}
181+
182+
// NewSilenceIncidentAction creates a builder
183+
func NewSilenceIncidentAction(reason string) *SilenceIncidentActionBuilder {
184+
return &SilenceIncidentActionBuilder{
185+
reason: reason,
186+
}
187+
}
188+
189+
// WithReason sets the reason for silencing
190+
func (b *SilenceIncidentActionBuilder) WithReason(reason string) *SilenceIncidentActionBuilder {
191+
b.reason = reason
192+
return b
193+
}
194+
195+
// Build creates the SilenceIncidentAction
196+
func (b *SilenceIncidentActionBuilder) Build() Action {
197+
return &SilenceIncidentAction{
198+
Reason: b.reason,
199+
}
200+
}
201+
202+
// EscalateIncidentActionBuilder builds EscalateIncidentAction instances
203+
type EscalateIncidentActionBuilder struct {
204+
reason string
205+
}
206+
207+
// NewEscalateIncidentAction creates a builder
208+
func NewEscalateIncidentAction(reason string) *EscalateIncidentActionBuilder {
209+
return &EscalateIncidentActionBuilder{
210+
reason: reason,
211+
}
212+
}
213+
214+
// WithReason sets the reason for escalating
215+
func (b *EscalateIncidentActionBuilder) WithReason(reason string) *EscalateIncidentActionBuilder {
216+
b.reason = reason
217+
return b
218+
}
219+
220+
// Build creates the EscalateIncidentAction
221+
func (b *EscalateIncidentActionBuilder) Build() Action {
222+
return &EscalateIncidentAction{
223+
Reason: b.reason,
224+
}
225+
}
226+
227+
// BackplaneReportActionBuilder builds BackplaneReportAction instances
228+
type BackplaneReportActionBuilder struct {
229+
report BackplaneReport
230+
reportType string
231+
}
232+
233+
// NewBackplaneReportAction creates a builder with required fields
234+
func NewBackplaneReportAction(reportType string, report BackplaneReport) *BackplaneReportActionBuilder {
235+
return &BackplaneReportActionBuilder{
236+
reportType: reportType,
237+
report: report,
238+
}
239+
}
240+
241+
// WithReport sets the report payload
242+
func (b *BackplaneReportActionBuilder) WithReport(report BackplaneReport) *BackplaneReportActionBuilder {
243+
b.report = report
244+
return b
245+
}
246+
247+
// Build creates the BackplaneReportAction
248+
func (b *BackplaneReportActionBuilder) Build() Action {
249+
return &BackplaneReportAction{
250+
Report: b.report,
251+
ReportType: b.reportType,
252+
}
253+
}
254+
255+
// Convenience functions for simple cases
256+
257+
// ServiceLog creates a basic service log action
258+
func ServiceLog(severity, summary, description string) Action {
259+
return NewServiceLogAction(severity, summary).
260+
WithDescription(description).
261+
Build()
262+
}
263+
264+
// LimitedSupport creates a basic limited support action
265+
func LimitedSupport(summary, details string) Action {
266+
return NewLimitedSupportAction(summary, details).Build()
267+
}
268+
269+
// Note creates a PagerDuty note action
270+
func Note(content string) Action {
271+
return NewPagerDutyNoteAction(content).Build()
272+
}
273+
274+
// NoteFrom creates a PagerDuty note from a notewriter
275+
func NoteFrom(nw *notewriter.NoteWriter) Action {
276+
return NewPagerDutyNoteAction().FromNoteWriter(nw).Build()
277+
}
278+
279+
// Silence creates a silence incident action
280+
func Silence(reason string) Action {
281+
return NewSilenceIncidentAction(reason).Build()
282+
}
283+
284+
// Escalate creates an escalate incident action
285+
func Escalate(reason string) Action {
286+
return NewEscalateIncidentAction(reason).Build()
287+
}

0 commit comments

Comments
 (0)