@@ -2,12 +2,18 @@ package syncer
2
2
3
3
import (
4
4
"context"
5
+ "crypto/sha256"
5
6
"encoding/hex"
6
7
"errors"
7
8
"fmt"
8
9
10
+ "github.com/smartcontractkit/chainlink-common/pkg/custmsg"
9
11
"github.com/smartcontractkit/chainlink-common/pkg/types/core"
12
+ "github.com/smartcontractkit/chainlink-common/pkg/workflows/wasm/host"
10
13
"github.com/smartcontractkit/chainlink/v2/core/logger"
14
+ "github.com/smartcontractkit/chainlink/v2/core/platform"
15
+ "github.com/smartcontractkit/chainlink/v2/core/services/job"
16
+ "github.com/smartcontractkit/chainlink/v2/core/services/workflows"
11
17
"github.com/smartcontractkit/chainlink/v2/core/services/workflows/store"
12
18
)
13
19
@@ -87,15 +93,28 @@ type WorkflowRegistryWorkflowDeletedV1 struct {
87
93
WorkflowName string
88
94
}
89
95
96
+ type secretsFetcher interface {
97
+ SecretsFor (ctx context.Context , workflowOwner , workflowName string ) (map [string ]string , error )
98
+ }
99
+
100
+ // secretsFetcherFunc implements the secretsFetcher interface for a function.
101
+ type secretsFetcherFunc func (ctx context.Context , workflowOwner , workflowName string ) (map [string ]string , error )
102
+
103
+ func (f secretsFetcherFunc ) SecretsFor (ctx context.Context , workflowOwner , workflowName string ) (map [string ]string , error ) {
104
+ return f (ctx , workflowOwner , workflowName )
105
+ }
106
+
90
107
// eventHandler is a handler for WorkflowRegistryEvent events. Each event type has a corresponding
91
108
// method that handles the event.
92
109
type eventHandler struct {
93
110
lggr logger.Logger
94
- orm WorkflowSecretsDS
111
+ orm WorkflowRegistryDS
95
112
fetcher FetcherFunc
96
113
workflowStore store.Store
97
114
capRegistry core.CapabilitiesRegistry
98
115
engineRegistry * engineRegistry
116
+ emitter custmsg.MessageEmitter
117
+ secretsFetcher secretsFetcher
99
118
}
100
119
101
120
// newEventHandler returns a new eventHandler instance.
@@ -106,6 +125,8 @@ func newEventHandler(
106
125
workflowStore store.Store ,
107
126
capRegistry core.CapabilitiesRegistry ,
108
127
engineRegistry * engineRegistry ,
128
+ emitter custmsg.MessageEmitter ,
129
+ secretsFetcher secretsFetcher ,
109
130
) * eventHandler {
110
131
return & eventHandler {
111
132
lggr : lggr ,
@@ -114,15 +135,50 @@ func newEventHandler(
114
135
workflowStore : workflowStore ,
115
136
capRegistry : capRegistry ,
116
137
engineRegistry : engineRegistry ,
138
+ emitter : emitter ,
139
+ secretsFetcher : secretsFetcher ,
117
140
}
118
141
}
119
142
120
143
func (h * eventHandler ) Handle (ctx context.Context , event WorkflowRegistryEvent ) error {
121
144
switch event .EventType {
122
145
case ForceUpdateSecretsEvent :
123
- return h .forceUpdateSecretsEvent (ctx , event )
146
+ payload , ok := event .Data .(WorkflowRegistryForceUpdateSecretsRequestedV1 )
147
+ if ! ok {
148
+ return newHandlerTypeError (event .Data )
149
+ }
150
+
151
+ cma := h .emitter .With (
152
+ platform .KeyWorkflowName , payload .WorkflowName ,
153
+ platform .KeyWorkflowOwner , hex .EncodeToString (payload .Owner ),
154
+ )
155
+
156
+ if err := h .forceUpdateSecretsEvent (ctx , payload ); err != nil {
157
+ logCustMsg (ctx , cma , fmt .Sprintf ("failed to handle force update secrets event: %v" , err ), h .lggr )
158
+ return err
159
+ }
160
+
161
+ return nil
124
162
case WorkflowRegisteredEvent :
125
- return h .workflowRegisteredEvent (ctx , event )
163
+ payload , ok := event .Data .(WorkflowRegistryWorkflowRegisteredV1 )
164
+ if ! ok {
165
+ return newHandlerTypeError (event .Data )
166
+ }
167
+ wfID := hex .EncodeToString (payload .WorkflowID [:])
168
+
169
+ cma := h .emitter .With (
170
+ platform .KeyWorkflowID , wfID ,
171
+ platform .KeyWorkflowName , payload .WorkflowName ,
172
+ platform .KeyWorkflowOwner , hex .EncodeToString (payload .WorkflowOwner ),
173
+ )
174
+
175
+ if err := h .workflowRegisteredEvent (ctx , payload ); err != nil {
176
+ logCustMsg (ctx , cma , fmt .Sprintf ("failed to handle workflow registered event: %v" , err ), h .lggr )
177
+ return err
178
+ }
179
+
180
+ h .lggr .Debugf ("workflow 0x%x registered and started" , wfID )
181
+ return nil
126
182
case WorkflowUpdatedEvent :
127
183
return h .workflowUpdatedEvent (ctx , event )
128
184
case WorkflowPausedEvent :
@@ -135,12 +191,97 @@ func (h *eventHandler) Handle(ctx context.Context, event WorkflowRegistryEvent)
135
191
}
136
192
137
193
// workflowRegisteredEvent handles the WorkflowRegisteredEvent event type.
138
- // TODO: Implement this method
139
194
func (h * eventHandler ) workflowRegisteredEvent (
140
- _ context.Context ,
141
- _ WorkflowRegistryEvent ,
195
+ ctx context.Context ,
196
+ payload WorkflowRegistryWorkflowRegisteredV1 ,
142
197
) error {
143
- return ErrNotImplemented
198
+ wfID := hex .EncodeToString (payload .WorkflowID [:])
199
+
200
+ // Download the contents of binaryURL, configURL and secretsURL and cache them locally.
201
+ binary , err := h .fetcher (ctx , payload .BinaryURL )
202
+ if err != nil {
203
+ return fmt .Errorf ("failed to fetch binary from %s : %w" , payload .BinaryURL , err )
204
+ }
205
+
206
+ config , err := h .fetcher (ctx , payload .ConfigURL )
207
+ if err != nil {
208
+ return fmt .Errorf ("failed to fetch config from %s : %w" , payload .ConfigURL , err )
209
+ }
210
+
211
+ secrets , err := h .fetcher (ctx , payload .SecretsURL )
212
+ if err != nil {
213
+ return fmt .Errorf ("failed to fetch secrets from %s : %w" , payload .SecretsURL , err )
214
+ }
215
+
216
+ // Calculate the hash of the binary and config files
217
+ hash := workflowID (binary , config , []byte (payload .SecretsURL ))
218
+
219
+ // Pre-check: verify that the workflowID matches; if it doesn’t abort and log an error via Beholder.
220
+ if hash != wfID {
221
+ return fmt .Errorf ("workflowID mismatch: %s != %s" , hash , wfID )
222
+ }
223
+
224
+ // Save the workflow secrets
225
+ urlHash , err := h .orm .GetSecretsURLHash (payload .WorkflowOwner , []byte (payload .SecretsURL ))
226
+ if err != nil {
227
+ return fmt .Errorf ("failed to get secrets URL hash: %w" , err )
228
+ }
229
+
230
+ // Create a new entry in the workflow_spec table corresponding for the new workflow, with the contents of the binaryURL + configURL in the table
231
+ status := job .WorkflowSpecStatusActive
232
+ if payload .Status == 1 {
233
+ status = job .WorkflowSpecStatusPaused
234
+ }
235
+
236
+ entry := & job.WorkflowSpec {
237
+ Workflow : hex .EncodeToString (binary ),
238
+ Config : string (config ),
239
+ WorkflowID : wfID ,
240
+ Status : status ,
241
+ WorkflowOwner : hex .EncodeToString (payload .WorkflowOwner ),
242
+ WorkflowName : payload .WorkflowName ,
243
+ SpecType : job .WASMFile ,
244
+ BinaryURL : payload .BinaryURL ,
245
+ ConfigURL : payload .ConfigURL ,
246
+ }
247
+ if _ , err = h .orm .UpsertWorkflowSpecWithSecrets (ctx , entry , payload .SecretsURL , hex .EncodeToString (urlHash ), string (secrets )); err != nil {
248
+ return fmt .Errorf ("failed to upsert workflow spec with secrets: %w" , err )
249
+ }
250
+
251
+ if status != job .WorkflowSpecStatusActive {
252
+ return nil
253
+ }
254
+
255
+ // If status == active, start a new WorkflowEngine instance, and add it to local engine registry
256
+ moduleConfig := & host.ModuleConfig {Logger : h .lggr , Labeler : h .emitter }
257
+ sdkSpec , err := host .GetWorkflowSpec (ctx , moduleConfig , binary , config )
258
+ if err != nil {
259
+ return fmt .Errorf ("failed to get workflow sdk spec: %w" , err )
260
+ }
261
+
262
+ cfg := workflows.Config {
263
+ Lggr : h .lggr ,
264
+ Workflow : * sdkSpec ,
265
+ WorkflowID : wfID ,
266
+ WorkflowOwner : hex .EncodeToString (payload .WorkflowOwner ),
267
+ WorkflowName : payload .WorkflowName ,
268
+ Registry : h .capRegistry ,
269
+ Store : h .workflowStore ,
270
+ Config : config ,
271
+ Binary : binary ,
272
+ SecretsFetcher : h .secretsFetcher ,
273
+ }
274
+ e , err := workflows .NewEngine (ctx , cfg )
275
+ if err != nil {
276
+ return fmt .Errorf ("failed to create workflow engine: %w" , err )
277
+ }
278
+
279
+ if err := e .Start (ctx ); err != nil {
280
+ return fmt .Errorf ("failed to start workflow engine: %w" , err )
281
+ }
282
+
283
+ h .engineRegistry .Add (wfID , e )
284
+ return nil
144
285
}
145
286
146
287
// workflowUpdatedEvent handles the WorkflowUpdatedEvent event type.
@@ -170,32 +311,47 @@ func (h *eventHandler) workflowActivatedEvent(
170
311
// forceUpdateSecretsEvent handles the ForceUpdateSecretsEvent event type.
171
312
func (h * eventHandler ) forceUpdateSecretsEvent (
172
313
ctx context.Context ,
173
- event WorkflowRegistryEvent ,
314
+ payload WorkflowRegistryForceUpdateSecretsRequestedV1 ,
174
315
) error {
175
316
// Get the URL of the secrets file from the event data
176
- data , ok := event .Data .(WorkflowRegistryForceUpdateSecretsRequestedV1 )
177
- if ! ok {
178
- return fmt .Errorf ("invalid data type %T for event" , event .Data )
179
- }
180
-
181
- hash := hex .EncodeToString (data .SecretsURLHash )
317
+ hash := hex .EncodeToString (payload .SecretsURLHash )
182
318
183
319
url , err := h .orm .GetSecretsURLByHash (ctx , hash )
184
320
if err != nil {
185
- h .lggr .Errorf ("failed to get URL by hash %s : %s" , hash , err )
186
- return err
321
+ return fmt .Errorf ("failed to get URL by hash %s : %w" , hash , err )
187
322
}
188
323
189
324
// Fetch the contents of the secrets file from the url via the fetcher
190
325
secrets , err := h .fetcher (ctx , url )
191
326
if err != nil {
192
- return err
327
+ return fmt . Errorf ( "failed to fetch secrets from url %s : %w" , url , err )
193
328
}
194
329
195
330
// Update the secrets in the ORM
196
331
if _ , err := h .orm .Update (ctx , hash , string (secrets )); err != nil {
197
- return err
332
+ return fmt . Errorf ( "failed to update secrets: %w" , err )
198
333
}
199
334
200
335
return nil
201
336
}
337
+
338
+ // workflowID returns a hex encoded sha256 hash of the wasm, config and secretsURL.
339
+ func workflowID (wasm , config , secretsURL []byte ) string {
340
+ sum := sha256 .New ()
341
+ sum .Write (wasm )
342
+ sum .Write (config )
343
+ sum .Write (secretsURL )
344
+ return hex .EncodeToString (sum .Sum (nil ))
345
+ }
346
+
347
+ // logCustMsg emits a custom message to the external sink and logs an error if that fails.
348
+ func logCustMsg (ctx context.Context , cma custmsg.MessageEmitter , msg string , log logger.Logger ) {
349
+ err := cma .Emit (ctx , msg )
350
+ if err != nil {
351
+ log .Helper (1 ).Errorf ("failed to send custom message with msg: %s, err: %v" , msg , err )
352
+ }
353
+ }
354
+
355
+ func newHandlerTypeError (data any ) error {
356
+ return fmt .Errorf ("invalid data type %T for event" , data )
357
+ }
0 commit comments