-
Notifications
You must be signed in to change notification settings - Fork 23
fix(lookup): fix metadata extraction and error handling #107
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
Changes from all commits
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 |
|---|---|---|
|
|
@@ -10,10 +10,11 @@ import ( | |
| "path/filepath" | ||
| "reflect" | ||
| "regexp" | ||
| "strconv" | ||
| "strings" | ||
| "unsafe" | ||
|
|
||
| "google.golang.org/grpc/codes" | ||
| "google.golang.org/grpc/status" | ||
| "gopkg.in/yaml.v3" | ||
|
|
||
| "github.com/cosi-project/runtime/pkg/resource" | ||
|
|
@@ -562,20 +563,21 @@ func readUnexportedField(field reflect.Value) any { | |
|
|
||
| // builds resource with metadata, spec and stringSpec fields | ||
| func extractResourceData(r resource.Resource) (map[string]interface{}, error) { | ||
| // extract metadata | ||
| o, err := resource.MarshalYAML(r) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to marshal resource to YAML: %w", err) | ||
| } | ||
| m, err := yaml.Marshal(o) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to marshal metadata to YAML: %w", err) | ||
| } | ||
| res := make(map[string]interface{}) | ||
| if err := yaml.Unmarshal(m, &res); err != nil { | ||
| return nil, fmt.Errorf("failed to unmarshal metadata: %w", err) | ||
|
|
||
| // Extract metadata directly from resource methods | ||
| md := r.Metadata() | ||
| metadata := map[string]interface{}{ | ||
| "namespace": string(md.Namespace()), | ||
| "type": string(md.Type()), | ||
| "id": string(md.ID()), | ||
| "version": md.Version().String(), | ||
| "phase": md.Phase().String(), | ||
| "owner": string(md.Owner()), | ||
| } | ||
|
|
||
| res["metadata"] = metadata | ||
|
|
||
| // extract spec | ||
| val := reflect.ValueOf(r.Spec()) | ||
| if val.Kind() == reflect.Ptr { | ||
|
|
@@ -606,6 +608,13 @@ func newLookupFunction(ctx context.Context, c *client.Client) func(resource stri | |
|
|
||
| callbackResource := func(parentCtx context.Context, hostname string, r resource.Resource, callError error) error { | ||
| if callError != nil { | ||
| // Ignore NotFound and PermissionDenied errors - resource doesn't exist or is not accessible | ||
| errCode := status.Code(callError) | ||
| errStr := callError.Error() | ||
| if errCode == codes.NotFound || errCode == codes.PermissionDenied || | ||
| strings.Contains(errStr, "code = NotFound") || strings.Contains(errStr, "code = PermissionDenied") { | ||
| return nil | ||
| } | ||
|
Comment on lines
+611
to
+617
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. 🧩 Analysis chain🌐 Web query:
💡 Result: In grpc-go v1.77.0, the simplest/recommended way to detect gRPC codes (including when the error is wrapped) is to use import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
switch status.Code(err) {
case codes.NotFound:
// ...
case codes.PermissionDenied:
// ...
case codes.OK:
// err == nil
default:
// Unknown / other
}If you also want the full st := status.Convert(err) // convenience wrapper around FromError
if st.Code() == codes.NotFound { /* ... */ }Notes:
Sources: [1] [2] 🏁 Script executed: # Inspect the file and context around the mentioned lines
head -n 650 pkg/engine/engine.go | tail -n 80Repository: cozystack/talm Length of output: 2661 🏁 Script executed: # Check imports in the file
head -n 50 pkg/engine/engine.goRepository: cozystack/talm Length of output: 1470 🏁 Script executed: # Search for other error handling patterns in the file
rg "status\.Code|strings\.Contains.*code" pkg/engine/engine.go -A 2 -B 2Repository: cozystack/talm Length of output: 450 Remove brittle string matching; The code already uses ♻️ Simplify to if callError != nil {
// Ignore NotFound and PermissionDenied errors - resource doesn't exist or is not accessible
errCode := status.Code(callError)
- errStr := callError.Error()
- if errCode == codes.NotFound || errCode == codes.PermissionDenied ||
- strings.Contains(errStr, "code = NotFound") || strings.Contains(errStr, "code = PermissionDenied") {
+ if errCode == codes.NotFound || errCode == codes.PermissionDenied {
return nil
}🤖 Prompt for AI Agents |
||
| multiErr = multierror.Append(multiErr, callError) | ||
| return nil | ||
| } | ||
|
|
@@ -636,9 +645,10 @@ func newLookupFunction(ctx context.Context, c *client.Client) func(resource stri | |
| if id != "" && len(resources) == 1 { | ||
| return resources[0], nil | ||
| } | ||
| items := map[string]interface{}{} | ||
| // Return items as a slice for proper range iteration in templates | ||
| items := make([]interface{}, len(resources)) | ||
| for i, res := range resources { | ||
| items["_"+strconv.Itoa(i)] = res | ||
| items[i] = res | ||
| } | ||
| return map[string]interface{}{ | ||
| "apiVersion": "v1", | ||
|
|
||
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.
This logic can be made clearer by explicitly handling gRPC status errors separately from the string-matching fallback. Using
status.FromErrorprovides a clean way to check if the error is a gRPC status error. This refactoring improves readability by making the two cases (native gRPC error vs. string-based error) distinct. The string matching is brittle and should ideally only be a fallback when a proper status code isn't available.