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

opendefensecloud / solution-arsenal / 21947575810

12 Feb 2026 12:58PM UTC coverage: 62.56% (-1.9%) from 64.444%
21947575810

Pull #153

github

web-flow
Merge 3f5e1137d into 717f548f7
Pull Request #153: Check if ComponentVersion needs updates

43 of 164 new or added lines in 5 files covered. (26.22%)

11 existing lines in 2 files now uncovered.

787 of 1258 relevant lines covered (62.56%)

5.64 hits per line

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

59.8
/pkg/discovery/handler/handler.go
1
// Copyright 2026 BWI GmbH and Solution Arsenal contributors
2
// SPDX-License-Identifier: Apache-2.0
3

4
package handler
5

6
import (
7
        "context"
8
        "fmt"
9
        "strings"
10

11
        "github.com/cenkalti/backoff/v4"
12
        "github.com/go-logr/logr"
13
        "ocm.software/ocm/api/ocm"
14
        "ocm.software/ocm/api/ocm/extensions/repositories/ocireg"
15

16
        "go.opendefense.cloud/solar/pkg/discovery"
17
)
18

19
var (
20
        // handlerRegistry is a map of handler types to their corresponding handlers.
21
        handlerRegistry = make(map[HandlerType]InitHandlerFunc)
22
)
23

24
type InitHandlerFunc func(log logr.Logger) ComponentHandler
25

26
func RegisterComponentHandler(t HandlerType, fn InitHandlerFunc) {
1✔
27
        if fn == nil {
1✔
28
                panic("cannot register nil handler")
×
29
        }
30

31
        if _, exists := handlerRegistry[t]; exists {
1✔
32
                panic(fmt.Sprintf("handler %q already registered", t))
×
33
        }
34

35
        handlerRegistry[t] = fn
1✔
36
}
37

38
type Handler struct {
39
        *discovery.Runner[discovery.ComponentVersionEvent, discovery.WriteAPIResourceEvent]
40
        provider *discovery.RegistryProvider
41
        handler  map[HandlerType]ComponentHandler
42
}
43

44
func NewHandlerOptions(opts ...discovery.RunnerOption[discovery.ComponentVersionEvent, discovery.WriteAPIResourceEvent]) []discovery.RunnerOption[discovery.ComponentVersionEvent, discovery.WriteAPIResourceEvent] {
1✔
45
        return opts
1✔
46
}
1✔
47

48
func NewHandler(
49
        provider *discovery.RegistryProvider,
50
        in <-chan discovery.ComponentVersionEvent,
51
        out chan<- discovery.WriteAPIResourceEvent,
52
        err chan<- discovery.ErrorEvent,
53
        opts ...discovery.RunnerOption[discovery.ComponentVersionEvent, discovery.WriteAPIResourceEvent],
54
) *Handler {
2✔
55
        p := &Handler{
2✔
56
                provider: provider,
2✔
57
                handler:  make(map[HandlerType]ComponentHandler),
2✔
58
        }
2✔
59
        p.Runner = discovery.NewRunner(p, in, out, err)
2✔
60
        for _, opt := range opts {
4✔
61
                opt(p.Runner)
2✔
62
        }
2✔
63

64
        return p
2✔
65
}
66

67
// isRetryable determines if we should wait and try again
68
func isRetryable(err error) bool {
×
69
        msg := strings.ToLower(err.Error())
×
70
        // OCM often wraps errors, so we check the string for common rate-limit indicators
×
71
        return strings.Contains(msg, "429") ||
×
72
                strings.Contains(msg, "too many requests") ||
×
73
                strings.Contains(msg, "connection refused")
×
74
}
×
75

76
func (rs *Handler) Process(ctx context.Context, ev discovery.ComponentVersionEvent) ([]discovery.WriteAPIResourceEvent, error) {
1✔
77
        rs.Logger().Info("processing component version event", "event", ev)
1✔
78
        comp := ev.Component
1✔
79
        version := ev.Source.Version
1✔
80

1✔
81
        // Analyze resources contained in component descriptor.
1✔
82
        helmChartCount := 0
1✔
83
        handlerType := HandlerType("")
1✔
84

1✔
85
        // Get registry configuration
1✔
86
        registry := rs.provider.Get(ev.Source.Registry)
1✔
87
        if registry == nil {
1✔
NEW
88
                rs.Logger().V(2).Info("invalid registry", "registry", ev.Source.Registry)
×
NEW
89
                return nil, fmt.Errorf("invalid registry: %s", ev.Source.Registry)
×
UNCOV
90
        }
×
91

92
        // Create repository for the component
93
        baseURL := fmt.Sprintf("%s/%s", registry.GetURL(), ev.Namespace)
1✔
94
        octx := ocm.FromContext(ctx)
1✔
95
        repo, err := octx.RepositoryForSpec(ocireg.NewRepositorySpec(baseURL))
1✔
96
        if err != nil {
1✔
NEW
97
                rs.Logger().Error(err, "failed to create repo spec", "registry", ev.Source.Registry, "repository", ev.Source.Repository)
×
NEW
98
                return nil, fmt.Errorf("failed to create repository spec: %w", err)
×
UNCOV
99
        }
×
100
        defer func() { _ = repo.Close() }()
2✔
101

102
        // Lookup the specific component version
103
        var compVersion ocm.ComponentVersionAccess
1✔
104
        if rs.Backoff() == nil {
2✔
105
                compVersion, err = repo.LookupComponentVersion(comp, version)
1✔
106
        } else {
1✔
107
                // If backoff is configured, use it to retry on transient errors
×
108
                operation := func() error {
×
109
                        var err error
×
110
                        compVersion, err = repo.LookupComponentVersion(comp, version)
×
111
                        if err != nil {
×
112
                                // Check if the error is a 429 or transient
×
113
                                if isRetryable(err) {
×
114
                                        return err // Returning error triggers a retry
×
115
                                }
×
116

117
                                return backoff.Permanent(err) // Stops retrying for 401, 404, etc.
×
118
                        }
119

120
                        return nil
×
121
                }
NEW
122
                err = backoff.Retry(operation, rs.Backoff())
×
123
        }
124
        if err != nil {
1✔
NEW
125
                rs.Logger().Error(err, "failed to lookup component", "version", version)
×
NEW
126
                return nil, fmt.Errorf("failed to lookup component version %s: %w", version, err)
×
UNCOV
127
        }
×
128
        defer func() { _ = compVersion.Close() }()
2✔
129

130
        // Count the number of Helm chart resources in the component version and determine the handler type based on that.
131
        for _, res := range compVersion.GetDescriptor().ComponentSpec.Resources {
6✔
132
                if res.Type == string(HelmResource) {
6✔
133
                        helmChartCount++
1✔
134
                }
1✔
135
        }
136

137
        // Classify component based on contained resources as helm chart and send it to the corresponding handler.
138
        if helmChartCount == 1 {
2✔
139
                handlerType = HelmHandler
1✔
140
        }
1✔
141

142
        // If no handler type could be determined, log and publish error.
143
        if handlerType == "" {
1✔
144
                // No handler found for event, log and publish error.
×
NEW
145
                rs.Logger().Info("no handler found for event", "event", ev)
×
NEW
146
                return nil, fmt.Errorf("no handler found for component version event: %v", ev)
×
UNCOV
147
        }
×
148

149
        // Process component with determined handler type.
150
        h, err := rs.getHandler(handlerType)
1✔
151
        if err != nil {
1✔
NEW
152
                rs.Logger().Error(err, "failed to process component with handler", "handler", handlerType)
×
NEW
153
                return nil, fmt.Errorf("failed to process component with handler %q: %w", handlerType, err)
×
UNCOV
154
        }
×
155

156
        // Process component with determined handler. If processing fails, log and publish error.
157
        resEvent, err := h.Process(ctx, &ev, compVersion)
1✔
158
        if err != nil {
1✔
NEW
159
                rs.Logger().Error(err, "failed to process component with handler", "handler", handlerType)
×
NEW
160
                return nil, fmt.Errorf("failed to process component with handler %q: %w", handlerType, err)
×
UNCOV
161
        }
×
162

163
        return []discovery.WriteAPIResourceEvent{*resEvent}, nil
1✔
164
}
165

166
// getHandler returns the handler for the given type, initializing it if necessary.
167
func (rs *Handler) getHandler(t HandlerType) (ComponentHandler, error) {
1✔
168
        if rs.handler[HelmHandler] == nil {
2✔
169
                if initFn, ok := handlerRegistry[HelmHandler]; ok {
2✔
170
                        handler := initFn(rs.Logger().WithValues("handler", HelmHandler))
1✔
171
                        rs.handler[HelmHandler] = handler
1✔
172

1✔
173
                        return handler, nil
1✔
174
                }
1✔
175
        }
176

177
        return nil, fmt.Errorf("no handler registered for type %v", t)
×
178
}
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