1 package example
2
3 import (
4 "context"
5 "strconv"
6
7 "github.com/fsnotify/fsnotify"
8 "github.com/gin-gonic/gin"
9 "github.com/spf13/afero"
10 corev1 "k8s.io/api/core/v1"
11 toolscache "k8s.io/client-go/tools/cache"
12
13 "edge-infra.dev/pkg/lib/fog"
14 "edge-infra.dev/pkg/lib/logging"
15 "edge-infra.dev/pkg/sds/interlock/internal/config"
16 "edge-infra.dev/pkg/sds/interlock/topic"
17 "edge-infra.dev/pkg/sds/interlock/websocket"
18 )
19
20
21 var TopicName = "example"
22
23
24 type Example struct {
25 topic topic.Topic
26 }
27
28
29
30 func New(ctx context.Context, cfg *config.Config, wm *websocket.Manager) (*Example, error) {
31 topic := topic.NewTopic(
32 TopicName,
33 newState(),
34 cfg,
35 wm,
36 )
37 example := &Example{
38 topic: topic,
39 }
40
41 if err := example.SetupAPIInformers(ctx, cfg); err != nil {
42 return nil, err
43 }
44
45 if err := example.SetupFSWatchers(ctx, cfg); err != nil {
46 return nil, err
47 }
48 return example, nil
49 }
50
51
52
53
54 func (e *Example) SetupAPIInformers(ctx context.Context, cfg *config.Config) error {
55
56 informer, err := cfg.Cache.GetInformer(ctx, &corev1.ConfigMap{})
57 if err != nil {
58 return err
59 }
60
61 filteredEventHandler := toolscache.FilteringResourceEventHandler{
62 FilterFunc: FilterConfigMap,
63 Handler: &EventHandler{e.topic.UpdateState},
64 }
65 if _, err := informer.AddEventHandler(filteredEventHandler); err != nil {
66 return err
67 }
68 return nil
69 }
70
71
72
73 func FilterConfigMap(obj interface{}) bool {
74 cm, ok := obj.(*corev1.ConfigMap)
75 if !ok {
76 return false
77 }
78 if cm.Name != "example-name" || cm.Namespace != "example-namespace" {
79 return false
80 }
81 return true
82 }
83
84
85
86 type EventHandler struct {
87 UpdateFunc func(topic.UpdateFunc) error
88 }
89
90
91 func (h *EventHandler) OnAdd(obj interface{}, _ bool) {
92 cm := obj.(*corev1.ConfigMap)
93 updateIntField(h.UpdateFunc, cm)
94 }
95
96
97 func (h *EventHandler) OnUpdate(_, newObj interface{}) {
98 cm := newObj.(*corev1.ConfigMap)
99 updateIntField(h.UpdateFunc, cm)
100 }
101
102
103
104 func (h *EventHandler) OnDelete(_ interface{}) {}
105
106
107
108 func updateIntField(updateFunc func(topic.UpdateFunc) error, cm *corev1.ConfigMap) {
109 log := logging.NewLogger()
110 stringField := cm.Data["example"]
111 intField, err := strconv.Atoi(stringField)
112 if err != nil {
113 log.Error(err, "failed to convert str to int in API informer")
114 return
115 }
116
117
118 err = updateFunc(func(obj interface{}) error {
119 state := obj.(*State)
120 state.IntField = intField
121 return nil
122 })
123 if err != nil {
124 log.Error(err, "failed to update IntField in API informer")
125 }
126 }
127
128
129
130
131 func (e *Example) SetupFSWatchers(ctx context.Context, cfg *config.Config) error {
132
133 watcher, err := fsnotify.NewWatcher()
134 if err != nil {
135 return err
136 }
137 defer watcher.Close()
138
139
140 go updateStringField(ctx, watcher, cfg.Fs, e.topic.UpdateState)
141
142
143 return watcher.Add("example-file.txt")
144 }
145
146
147
148 func updateStringField(ctx context.Context, watcher *fsnotify.Watcher, fs afero.Fs, update func(topic.UpdateFunc) error) {
149 log := fog.FromContext(ctx)
150 for {
151 select {
152 case event, ok := <-watcher.Events:
153 if !ok {
154 return
155 }
156
157
158
159 if event.Has(fsnotify.Write) {
160 err := update(func(obj interface{}) error {
161 content, err := afero.ReadFile(fs, "example-file.txt")
162 if err != nil {
163 return err
164 }
165 state := obj.(*State)
166 state.StringField = string(content)
167 return nil
168 })
169 if err != nil {
170 log.Error(err, "failed to read example file in filesystem watcher")
171 }
172 }
173 case err, ok := <-watcher.Errors:
174 if !ok {
175 return
176 }
177 log.Error(err, "failed to update IntField in filesystem watcher hook")
178 }
179 }
180 }
181
182
183 func (e *Example) RegisterEndpoints(r *gin.Engine) {
184 v1 := r.Group("/v1/example")
185 v1.GET("", e.topic.DefaultGet)
186 v1.PATCH("", e.topic.DefaultPatch)
187 }
188
189
190
191
192
193 type State struct {
194
195
196
197
198
199
200 StringField string `json:"example-string" binding:"required"`
201
202
203
204
205
206
207
208 IntField int `json:"example-int" binding:"min=3"`
209
210
211 NestedFields NestedFields `json:"example-nested"`
212 }
213
214
215
216
217
218 type NestedFields struct {
219
220 BoolField bool `json:"example-bool"`
221 }
222
223
224 func newState() *State {
225 return &State{
226 StringField: "",
227 IntField: 0,
228 NestedFields: NestedFields{
229 BoolField: true,
230 },
231 }
232 }
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256 type PatchParametersWrapper struct {
257
258
259
260 State State `json:"state"`
261 }
262
263
264
265
266 type StateResponseWrapper struct {
267
268
269
270 State State `json:"state"`
271 }
272
View as plain text