-
Notifications
You must be signed in to change notification settings - Fork 10
Add event subscription management for server events and metrics #494
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
9333c7d
ecec4f8
ea45555
dbad795
4e40c37
62cbaf9
bbad483
a527b0a
388d742
dbe2b7f
f41134a
654d26f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -25,6 +25,14 @@ var ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dataFS embed.FS | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type Collection struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Members []Member `json:"Members"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type Member struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| OdataID string `json:"@odata.id"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LockedResourceState = "Locked" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| UnlockedResourceState = "Unlocked" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -81,6 +89,8 @@ func (s *MockServer) redfishHandler(w http.ResponseWriter, r *http.Request) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| s.handleRedfishPOST(w, r) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case http.MethodPatch: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| s.handleRedfishPATCH(w, r) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case http.MethodDelete: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| s.handleRedfishDelete(w, r) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| default: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -180,6 +190,47 @@ func resolvePath(urlPath string) string { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return path.Join("data", trimmed, "index.json") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func (s *MockServer) handleRedfishDelete(w http.ResponseWriter, r *http.Request) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| s.log.Info("Received request", "method", r.Method, "path", r.URL.Path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| urlPath := resolvePath(r.URL.Path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| s.mu.RLock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| defer s.mu.Unlock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+197
to
+198
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical: Lock mismatch causes deadlock.
Additionally, this function modifies 🔎 Proposed fix func (s *MockServer) handleRedfishDelete(w http.ResponseWriter, r *http.Request) {
s.log.Info("Received request", "method", r.Method, "path", r.URL.Path)
urlPath := resolvePath(r.URL.Path)
- s.mu.RLock()
- defer s.mu.Unlock()
+ s.mu.Lock()
+ defer s.mu.Unlock()
_, hasOverride := s.overrides[urlPath]📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _, hasOverride := s.overrides[urlPath] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if hasOverride { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // remove the resource | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| delete(s.overrides, urlPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // get collection of the resource | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| collectionPath := path.Dir(urlPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cached, hasOverride := s.overrides[collectionPath] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var collection Collection | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if hasOverride { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| collection = cached.(Collection) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data, err := dataFS.ReadFile(collectionPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| http.NotFound(w, r) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err := json.Unmarshal(data, &collection); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| http.Error(w, "Corrupt embedded JSON", http.StatusInternalServerError) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+206
to
+220
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unsafe type assertion may cause panic. Line 209 performs a type assertion 🔎 Proposed fix cached, hasOverride := s.overrides[collectionPath]
var collection Collection
if hasOverride {
- collection = cached.(Collection)
+ var ok bool
+ collection, ok = cached.(Collection)
+ if !ok {
+ http.Error(w, "Invalid collection type", http.StatusInternalServerError)
+ return
+ }
} else { |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // remove member from collection | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| newMembers := make([]Member, 0) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for _, member := range collection.Members { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if member.OdataID != r.URL.Path { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| newMembers = append(newMembers, member) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| s.log.Info("Removing member from collection", "members", newMembers, "collection", collectionPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| collection.Members = newMembers | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| s.overrides[collectionPath] = collection | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| w.WriteHeader(http.StatusNoContent) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func (s *MockServer) handleRedfishGET(w http.ResponseWriter, r *http.Request) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| urlPath := resolvePath(r.URL.Path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -239,9 +290,15 @@ func (s *MockServer) handleRedfishPOST(w http.ResponseWriter, r *http.Request) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }(r.Body) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| s.log.Info("POST body received", "body", string(body)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var update map[string]any | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err := json.Unmarshal(body, &update); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| http.Error(w, "Invalid JSON", http.StatusBadRequest) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| s.log.Info("POST body received", "body", string(body)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| urlPath := resolvePath(r.URL.Path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| switch { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case strings.Contains(urlPath, "Actions/ComputerSystem.Reset"): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Simulate a system reset action | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -304,7 +361,55 @@ func (s *MockServer) handleRedfishPOST(w http.ResponseWriter, r *http.Request) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case strings.Contains(urlPath, "UpdateService/Actions/UpdateService.SimpleUpdate"): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Simulate a firmware update action | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| default: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| s.log.Info("Unhandled POST request", "path", urlPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Handle resource creation in collections | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| s.mu.Lock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| defer s.mu.Unlock() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cached, hasOverride := s.overrides[urlPath] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var base Collection | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if hasOverride { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| s.log.Info("Using overridden data for POST", "path", urlPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| base = cached.(Collection) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| s.log.Info("Using embedded data for POST", "path", urlPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data, err := dataFS.ReadFile(urlPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| s.log.Error(err, "Failed to read embedded data for POST", "path", urlPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| http.NotFound(w, r) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err := json.Unmarshal(data, &base); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| http.Error(w, "Corrupt embedded JSON", http.StatusInternalServerError) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+369
to
+384
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unsafe type assertion may cause panic. Line 371 performs 🔎 Proposed fix if hasOverride {
s.log.Info("Using overridden data for POST", "path", urlPath)
- base = cached.(Collection)
+ var ok bool
+ base, ok = cached.(Collection)
+ if !ok {
+ http.Error(w, "Resource is not a collection", http.StatusBadRequest)
+ return
+ }
} else {📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // If resource collection (has "Members"), add a new member | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if len(base.Members) > 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| newID := fmt.Sprintf("%d", len(base.Members)+1) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| location := path.Join(r.URL.Path, newID) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| newMemberPath := resolvePath(location) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| base.Members = append(base.Members, Member{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| OdataID: location, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| s.log.Info("Adding new member", "id", newID, "location", location, "memberPath", newMemberPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if strings.HasSuffix(r.URL.Path, "/Subscriptions") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| w.Header().Set("Location", location) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| s.overrides[urlPath] = base | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| s.overrides[newMemberPath] = update | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| base.Members = make([]Member, 0) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| location := r.URL.JoinPath("1").String() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| base.Members = []Member{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| OdataID: r.URL.JoinPath("1").String(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| s.overrides[urlPath] = base | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if strings.HasSuffix(r.URL.Path, "/Subscriptions") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| w.Header().Set("Location", location) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+399
to
+411
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resource data not stored for empty collections. When the collection is initially empty (line 399 else branch), the code stores the collection with the new member (line 407) but does not store the 🔎 Proposed fix } else {
- base.Members = make([]Member, 0)
location := r.URL.JoinPath("1").String()
+ newMemberPath := resolvePath(location)
base.Members = []Member{
{
OdataID: r.URL.JoinPath("1").String(),
},
}
s.overrides[urlPath] = base
+ s.overrides[newMemberPath] = update
if strings.HasSuffix(r.URL.Path, "/Subscriptions") {
w.Header().Set("Location", location)
}
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| s.log.Info("Storing updated data for POST", "path", urlPath, "data", update) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| w.Header().Set("Content-Type", "application/json") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| w.WriteHeader(http.StatusCreated) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _, err = w.Write([]byte(`{"status": "created"}`)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -10,6 +10,7 @@ import ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "fmt" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "io" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "net/http" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "net/url" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "slices" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "strings" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "time" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -996,3 +997,80 @@ func (r *RedfishBMC) GetBMCUpgradeTask(ctx context.Context, manufacturer string, | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return oem.GetTaskMonitorDetails(ctx, respTask) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type subscriptionPayload struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Destination string `json:"Destination,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| EventTypes []redfish.EventType `json:"EventTypes,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| EventFormatType redfish.EventFormatType `json:"EventFormatType,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| RegistryPrefixes []string `json:"RegistryPrefixes,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ResourceTypes []string `json:"ResourceTypes,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DeliveryRetryPolicy redfish.DeliveryRetryPolicy `json:"DeliveryRetryPolicy,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| HTTPHeaders map[string]string `json:"HttpHeaders,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Oem interface{} `json:"Oem,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Protocol redfish.EventDestinationProtocol `json:"Protocol,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Context string `json:"Context,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func (r *RedfishBMC) CreateEventSubscription( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ctx context.Context, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| destination string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| eventFormatType redfish.EventFormatType, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| retry redfish.DeliveryRetryPolicy, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) (string, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| service := r.client.GetService() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ev, err := service.EventService() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "", fmt.Errorf("failed to get event service: %w", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if !ev.ServiceEnabled { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "", fmt.Errorf("event service is not enabled") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| payload := &subscriptionPayload{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Destination: destination, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| EventFormatType: eventFormatType, // event or metricreport | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Protocol: redfish.RedfishEventDestinationProtocol, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DeliveryRetryPolicy: retry, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Context: "metal3-operator", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| client := ev.GetClient() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // some implementations (like Dell) do not support ResourceTypes and RegistryPrefixes | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if len(ev.ResourceTypes) == 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| payload.EventTypes = []redfish.EventType{} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| payload.RegistryPrefixes = []string{""} // Filters by the prefix of the event's MessageId, which points to a Message Registry: [Base, ResourceEvent, iLOEvents] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| payload.ResourceTypes = []string{""} // Filters by the schema name (Resource Type) of the event's OriginOfCondition: [Chassis, ComputerSystem, Power] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| resp, err := client.Post(ev.Subscriptions, payload) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "", err | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // return subscription link from returned location | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| subscriptionLink := resp.Header.Get("Location") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| urlParser, err := url.ParseRequestURI(subscriptionLink) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err == nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| subscriptionLink = urlParser.RequestURI() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return subscriptionLink, nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1037
to
+1053
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resource leak: HTTP response body not closed. The response from Also, setting 🔎 Suggested fix resp, err := client.Post(ev.Subscriptions, payload)
if err != nil {
return "", err
}
+ defer resp.Body.Close()
// return subscription link from returned location
subscriptionLink := resp.Header.Get("Location")For the empty strings issue, consider either omitting these fields or providing meaningful filter values: - if len(ev.ResourceTypes) == 0 {
- payload.EventTypes = []redfish.EventType{}
- } else {
- payload.RegistryPrefixes = []string{""} // Filters by the prefix of the event's MessageId
- payload.ResourceTypes = []string{""} // Filters by the schema name
- }
+ // Omit ResourceTypes and RegistryPrefixes to receive all events
+ // Some implementations (like Dell) do not support these fields📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func (r RedfishBMC) DeleteEventSubscription(ctx context.Context, uri string) error { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| service := r.client.GetService() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ev, err := service.EventService() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return fmt.Errorf("failed to get event service: %w", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if !ev.ServiceEnabled { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return fmt.Errorf("event service is not enabled") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| event, err := ev.GetEventSubscription(uri) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return fmt.Errorf("failed to get event subscription: %w", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if event == nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err := ev.DeleteEventSubscription(uri); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return fmt.Errorf("failed to delete event subscription: %w", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1056
to
+1076
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Value receiver on All other methods on 🔎 Suggested fix-func (r RedfishBMC) DeleteEventSubscription(ctx context.Context, uri string) error {
+func (r *RedfishBMC) DeleteEventSubscription(ctx context.Context, uri string) error {📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -13,6 +13,7 @@ import ( | |||||||||||||||||||||||||||||||||||||||||||||||||
| "time" | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/ironcore-dev/controller-utils/conditionutils" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/ironcore-dev/metal-operator/internal/serverevents" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| webhookmetalv1alpha1 "github.com/ironcore-dev/metal-operator/internal/webhook/v1alpha1" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "sigs.k8s.io/controller-runtime/pkg/manager" | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -73,6 +74,9 @@ func main() { // nolint: gocyclo | |||||||||||||||||||||||||||||||||||||||||||||||||
| registryPort int | ||||||||||||||||||||||||||||||||||||||||||||||||||
| registryProtocol string | ||||||||||||||||||||||||||||||||||||||||||||||||||
| registryURL string | ||||||||||||||||||||||||||||||||||||||||||||||||||
| eventPort int | ||||||||||||||||||||||||||||||||||||||||||||||||||
| eventURL string | ||||||||||||||||||||||||||||||||||||||||||||||||||
| eventProtocol string | ||||||||||||||||||||||||||||||||||||||||||||||||||
| registryResyncInterval time.Duration | ||||||||||||||||||||||||||||||||||||||||||||||||||
| webhookPort int | ||||||||||||||||||||||||||||||||||||||||||||||||||
| enforceFirstBoot bool | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -118,6 +122,10 @@ func main() { // nolint: gocyclo | |||||||||||||||||||||||||||||||||||||||||||||||||
| flag.StringVar(®istryURL, "registry-url", "", "The URL of the registry.") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| flag.StringVar(®istryProtocol, "registry-protocol", "http", "The protocol to use for the registry.") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| flag.IntVar(®istryPort, "registry-port", 10000, "The port to use for the registry.") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| flag.StringVar(&eventURL, "event-url", "", "The URL of the server events endpoint for alerts and metrics.") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| flag.IntVar(&eventPort, "event-port", 10001, "The port to use for the server events endpoint for alerts and metrics.") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| flag.StringVar(&eventProtocol, "event-protocol", "http", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "The protocol to use for the server events endpoint for alerts and metrics.") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| flag.StringVar(&probeImage, "probe-image", "", "Image for the first boot probing of a Server.") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| flag.StringVar(&probeOSImage, "probe-os-image", "", "OS image for the first boot probing of a Server.") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| flag.StringVar(&managerNamespace, "manager-namespace", "default", "Namespace the manager is running in.") | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -189,6 +197,15 @@ func main() { // nolint: gocyclo | |||||||||||||||||||||||||||||||||||||||||||||||||
| registryURL = fmt.Sprintf("%s://%s:%d", registryProtocol, registryAddr, registryPort) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // set the correct event URL by getting the address from the environment | ||||||||||||||||||||||||||||||||||||||||||||||||||
| var eventAddr string | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if eventURL == "" { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| eventAddr = os.Getenv("EVENT_ADDRESS") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if eventAddr != "" { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| eventURL = fmt.Sprintf("%s://%s:%d", eventProtocol, eventAddr, eventPort) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+200
to
+207
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Logic bug: event server won't start when If the user provides The condition should check 🔎 Suggested fix- if eventAddr != "" {
+ if eventURL != "" {
setupLog.Info("starting event server for alerts and metrics", "EventURL", eventURL)Also applies to: 575-584 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // if the enable-http2 flag is false (the default), http/2 should be disabled | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // due to its vulnerabilities. More specifically, disabling http/2 will | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // prevent from being vulnerable to the HTTP/2 Stream Cancelation and | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -329,6 +346,7 @@ func main() { // nolint: gocyclo | |||||||||||||||||||||||||||||||||||||||||||||||||
| BMCResetWaitTime: bmcResetWaitingInterval, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| BMCClientRetryInterval: bmcResetResyncInterval, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ManagerNamespace: managerNamespace, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| EventURL: eventURL, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Conditions: conditionutils.NewAccessor(conditionutils.AccessorOptions{}), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| BMCOptions: bmc.Options{ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| BasicAuth: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -554,6 +572,17 @@ func main() { // nolint: gocyclo | |||||||||||||||||||||||||||||||||||||||||||||||||
| os.Exit(1) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| if eventAddr != "" { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| setupLog.Info("starting event server for alerts and metrics", "EventURL", eventURL) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| eventServer := serverevents.NewServer(setupLog, fmt.Sprintf(":%d", eventPort)) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| go func() { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if err := eventServer.Start(ctx); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| setupLog.Error(err, "problem running event server") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| os.Exit(1) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }() | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+575
to
+584
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Inconsistent lifecycle management: event server started via goroutine instead of manager runnable. The registry server (lines 562-573) is added as a Issues:
🔎 Suggested fix: use manager runnable pattern if eventAddr != "" {
- setupLog.Info("starting event server for alerts and metrics", "EventURL", eventURL)
- eventServer := serverevents.NewServer(setupLog, fmt.Sprintf(":%d", eventPort))
- go func() {
- if err := eventServer.Start(ctx); err != nil {
- setupLog.Error(err, "problem running event server")
- os.Exit(1)
- }
- }()
+ if err := mgr.Add(manager.RunnableFunc(func(ctx context.Context) error {
+ setupLog.Info("starting event server for alerts and metrics", "EventURL", eventURL)
+ eventServer := serverevents.NewServer(setupLog, fmt.Sprintf(":%d", eventPort))
+ if err := eventServer.Start(ctx); err != nil {
+ return fmt.Errorf("unable to start event server: %w", err)
+ }
+ <-ctx.Done()
+ return nil
+ })); err != nil {
+ setupLog.Error(err, "unable to add event server runnable to manager")
+ os.Exit(1)
+ }
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| setupLog.Info("starting manager") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if err := mgr.Start(ctx); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| setupLog.Error(err, "problem running manager") | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor typo in comment.
Line 124 has a trailing 'q' in the comment: "manager.q" should be "manager."
🔎 Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents