@@ -16,9 +16,13 @@ package ctx
16
16
17
17
import (
18
18
"context"
19
+ "encoding/json"
19
20
"errors"
21
+ "fmt"
22
+ "github.com/google/uuid"
20
23
"github.com/serverlessworkflow/sdk-go/v3/model"
21
24
"sync"
25
+ "time"
22
26
)
23
27
24
28
var ErrWorkflowContextNotFound = errors .New ("workflow context not found" )
@@ -29,34 +33,56 @@ type ctxKey string
29
33
30
34
const (
31
35
runnerCtxKey ctxKey = "wfRunnerContext"
32
- varsContext = "$context"
33
- varsInput = "$input"
34
- varsOutput = "$output"
35
- varsWorkflow = "$workflow"
36
+
37
+ varsContext = "$context"
38
+ varsInput = "$input"
39
+ varsOutput = "$output"
40
+ varsWorkflow = "$workflow"
41
+ varsRuntime = "$runtime"
42
+ varsTask = "$task"
43
+
44
+ // TODO: script during the release to update this value programmatically
45
+ runtimeVersion = "v3.1.0"
46
+ runtimeName = "CNCF Serverless Workflow Specification Go SDK"
36
47
)
37
48
38
49
type WorkflowContext interface {
50
+ SetStartedAt (t time.Time )
39
51
SetStatus (status StatusPhase )
40
- SetTaskStatus ( task string , status StatusPhase )
52
+ SetRawInput ( input interface {} )
41
53
SetInstanceCtx (value interface {})
42
54
GetInstanceCtx () interface {}
43
55
SetInput (input interface {})
44
56
GetInput () interface {}
45
57
SetOutput (output interface {})
46
58
GetOutput () interface {}
47
59
GetOutputAsMap () map [string ]interface {}
48
- AsJQVars () map [string ]interface {}
60
+ GetVars () map [string ]interface {}
61
+ SetTaskStatus (task string , status StatusPhase )
62
+ SetTaskRawInput (input interface {})
63
+ SetTaskRawOutput (output interface {})
64
+ SetTaskDef (task model.Task ) error
65
+ SetTaskStartedAt (startedAt time.Time )
66
+ SetTaskName (name string )
67
+ SetTaskReference (ref string )
68
+ GetTaskReference () string
69
+ ClearTaskContext ()
70
+ SetLocalExprVars (vars map [string ]interface {})
71
+ AddLocalExprVars (vars map [string ]interface {})
72
+ RemoveLocalExprVars (keys ... string )
49
73
}
50
74
51
75
// workflowContext holds the necessary data for the workflow execution within the instance.
52
76
type workflowContext struct {
53
- mu sync.Mutex
54
- input interface {} // $input can hold any type
55
- output interface {} // $output can hold any type
56
- context map [string ]interface {} // Holds `$context` as the key
57
- definition map [string ]interface {} // $workflow representation in the context
58
- StatusPhase []StatusPhaseLog
59
- TasksStatusPhase map [string ][]StatusPhaseLog
77
+ mu sync.Mutex
78
+ input interface {} // $input can hold any type
79
+ output interface {} // $output can hold any type
80
+ context map [string ]interface {} // Holds `$context` as the key
81
+ workflowDescriptor map [string ]interface {} // $workflow representation in the context
82
+ taskDescriptor map [string ]interface {} // $task representation in the context
83
+ localExprVars map [string ]interface {} // Local expression variables defined in a given task or private context. E.g. a For task $item.
84
+ StatusPhase []StatusPhaseLog
85
+ TasksStatusPhase map [string ][]StatusPhaseLog
60
86
}
61
87
62
88
func NewWorkflowContext (workflow * model.Workflow ) (WorkflowContext , error ) {
@@ -65,19 +91,110 @@ func NewWorkflowContext(workflow *model.Workflow) (WorkflowContext, error) {
65
91
if err != nil {
66
92
return nil , err
67
93
}
68
-
69
- workflowCtx .definition = workflowDef
94
+ workflowCtx .taskDescriptor = map [string ]interface {}{}
95
+ workflowCtx .workflowDescriptor = map [string ]interface {}{
96
+ varsWorkflow : map [string ]interface {}{
97
+ "id" : uuid .NewString (),
98
+ "definition" : workflowDef ,
99
+ },
100
+ }
70
101
workflowCtx .SetStatus (PendingStatus )
71
102
72
103
return workflowCtx , nil
73
104
}
74
105
75
- func (ctx * workflowContext ) AsJQVars () map [string ]interface {} {
106
+ // WithWorkflowContext adds the workflowContext to a parent context
107
+ func WithWorkflowContext (parent context.Context , wfCtx WorkflowContext ) context.Context {
108
+ return context .WithValue (parent , runnerCtxKey , wfCtx )
109
+ }
110
+
111
+ // GetWorkflowContext retrieves the workflowContext from a context
112
+ func GetWorkflowContext (ctx context.Context ) (WorkflowContext , error ) {
113
+ wfCtx , ok := ctx .Value (runnerCtxKey ).(* workflowContext )
114
+ if ! ok {
115
+ return nil , ErrWorkflowContextNotFound
116
+ }
117
+ return wfCtx , nil
118
+ }
119
+
120
+ func (ctx * workflowContext ) SetStartedAt (t time.Time ) {
121
+ ctx .mu .Lock ()
122
+ defer ctx .mu .Unlock ()
123
+
124
+ wf , ok := ctx .workflowDescriptor [varsWorkflow ].(map [string ]interface {})
125
+ if ! ok {
126
+ wf = make (map [string ]interface {})
127
+ ctx .workflowDescriptor [varsWorkflow ] = wf
128
+ }
129
+
130
+ startedAt , ok := wf ["startedAt" ].(map [string ]interface {})
131
+ if ! ok {
132
+ startedAt = make (map [string ]interface {})
133
+ wf ["startedAt" ] = startedAt
134
+ }
135
+
136
+ startedAt ["iso8601" ] = t .UTC ().Format (time .RFC3339 )
137
+ }
138
+
139
+ func (ctx * workflowContext ) SetRawInput (input interface {}) {
140
+ ctx .mu .Lock ()
141
+ defer ctx .mu .Unlock ()
142
+
143
+ // Ensure the outer "workflow" map
144
+ wf , ok := ctx .workflowDescriptor [varsWorkflow ].(map [string ]interface {})
145
+ if ! ok {
146
+ wf = make (map [string ]interface {})
147
+ ctx .workflowDescriptor [varsWorkflow ] = wf
148
+ }
149
+
150
+ // Store the input
151
+ wf ["input" ] = input
152
+ }
153
+
154
+ func (ctx * workflowContext ) AddLocalExprVars (vars map [string ]interface {}) {
155
+ ctx .mu .Lock ()
156
+ defer ctx .mu .Unlock ()
157
+ if ctx .localExprVars == nil {
158
+ ctx .localExprVars = map [string ]interface {}{}
159
+ }
160
+ for k , v := range vars {
161
+ ctx .localExprVars [k ] = v
162
+ }
163
+ }
164
+
165
+ func (ctx * workflowContext ) RemoveLocalExprVars (keys ... string ) {
166
+ ctx .mu .Lock ()
167
+ defer ctx .mu .Unlock ()
168
+
169
+ if ctx .localExprVars == nil {
170
+ return
171
+ }
172
+
173
+ for _ , k := range keys {
174
+ delete (ctx .localExprVars , k )
175
+ }
176
+ }
177
+
178
+ func (ctx * workflowContext ) SetLocalExprVars (vars map [string ]interface {}) {
179
+ ctx .mu .Lock ()
180
+ defer ctx .mu .Unlock ()
181
+ ctx .localExprVars = vars
182
+ }
183
+
184
+ func (ctx * workflowContext ) GetVars () map [string ]interface {} {
76
185
vars := make (map [string ]interface {})
77
186
vars [varsInput ] = ctx .GetInput ()
78
187
vars [varsOutput ] = ctx .GetOutput ()
79
188
vars [varsContext ] = ctx .GetInstanceCtx ()
80
- vars [varsOutput ] = ctx .definition
189
+ vars [varsTask ] = ctx .taskDescriptor [varsTask ]
190
+ vars [varsWorkflow ] = ctx .workflowDescriptor [varsWorkflow ]
191
+ vars [varsRuntime ] = map [string ]interface {}{
192
+ "name" : runtimeName ,
193
+ "version" : runtimeVersion ,
194
+ }
195
+ for varName , varValue := range ctx .localExprVars {
196
+ vars [varName ] = varValue
197
+ }
81
198
return vars
82
199
}
83
200
@@ -90,15 +207,6 @@ func (ctx *workflowContext) SetStatus(status StatusPhase) {
90
207
ctx .StatusPhase = append (ctx .StatusPhase , NewStatusPhaseLog (status ))
91
208
}
92
209
93
- func (ctx * workflowContext ) SetTaskStatus (task string , status StatusPhase ) {
94
- ctx .mu .Lock ()
95
- defer ctx .mu .Unlock ()
96
- if ctx .TasksStatusPhase == nil {
97
- ctx .TasksStatusPhase = map [string ][]StatusPhaseLog {}
98
- }
99
- ctx .TasksStatusPhase [task ] = append (ctx .TasksStatusPhase [task ], NewStatusPhaseLog (status ))
100
- }
101
-
102
210
// SetInstanceCtx safely sets the `$context` value
103
211
func (ctx * workflowContext ) SetInstanceCtx (value interface {}) {
104
212
ctx .mu .Lock ()
@@ -179,16 +287,121 @@ func (ctx *workflowContext) GetOutputAsMap() map[string]interface{} {
179
287
}
180
288
}
181
289
182
- // WithWorkflowContext adds the workflowContext to a parent context
183
- func WithWorkflowContext (parent context.Context , wfCtx WorkflowContext ) context.Context {
184
- return context .WithValue (parent , runnerCtxKey , wfCtx )
290
+ func (ctx * workflowContext ) SetTaskStatus (task string , status StatusPhase ) {
291
+ ctx .mu .Lock ()
292
+ defer ctx .mu .Unlock ()
293
+ if ctx .TasksStatusPhase == nil {
294
+ ctx .TasksStatusPhase = map [string ][]StatusPhaseLog {}
295
+ }
296
+ ctx .TasksStatusPhase [task ] = append (ctx .TasksStatusPhase [task ], NewStatusPhaseLog (status ))
185
297
}
186
298
187
- // GetWorkflowContext retrieves the workflowContext from a context
188
- func GetWorkflowContext (ctx context.Context ) (WorkflowContext , error ) {
189
- wfCtx , ok := ctx .Value (runnerCtxKey ).(* workflowContext )
299
+ func (ctx * workflowContext ) SetTaskRawInput (input interface {}) {
300
+ ctx .mu .Lock ()
301
+ defer ctx .mu .Unlock ()
302
+
303
+ task , ok := ctx .taskDescriptor [varsTask ].(map [string ]interface {})
190
304
if ! ok {
191
- return nil , ErrWorkflowContextNotFound
305
+ task = make (map [string ]interface {})
306
+ ctx .taskDescriptor [varsTask ] = task
192
307
}
193
- return wfCtx , nil
308
+
309
+ task ["input" ] = input
310
+ }
311
+
312
+ func (ctx * workflowContext ) SetTaskRawOutput (output interface {}) {
313
+ ctx .mu .Lock ()
314
+ defer ctx .mu .Unlock ()
315
+
316
+ task , ok := ctx .taskDescriptor [varsTask ].(map [string ]interface {})
317
+ if ! ok {
318
+ task = make (map [string ]interface {})
319
+ ctx .taskDescriptor [varsTask ] = task
320
+ }
321
+
322
+ task ["output" ] = output
323
+ }
324
+
325
+ func (ctx * workflowContext ) SetTaskDef (task model.Task ) error {
326
+ ctx .mu .Lock ()
327
+ defer ctx .mu .Unlock ()
328
+
329
+ if task == nil {
330
+ return errors .New ("SetTaskDef called with nil model.Task" )
331
+ }
332
+
333
+ defBytes , err := json .Marshal (task )
334
+ if err != nil {
335
+ return fmt .Errorf ("failed to marshal task: %w" , err )
336
+ }
337
+
338
+ var defMap map [string ]interface {}
339
+ if err := json .Unmarshal (defBytes , & defMap ); err != nil {
340
+ return fmt .Errorf ("failed to unmarshal task into map: %w" , err )
341
+ }
342
+
343
+ taskMap , ok := ctx .taskDescriptor [varsTask ].(map [string ]interface {})
344
+ if ! ok {
345
+ taskMap = make (map [string ]interface {})
346
+ ctx .taskDescriptor [varsTask ] = taskMap
347
+ }
348
+
349
+ taskMap ["definition" ] = defMap
350
+
351
+ return nil
352
+ }
353
+
354
+ func (ctx * workflowContext ) SetTaskStartedAt (startedAt time.Time ) {
355
+ ctx .mu .Lock ()
356
+ defer ctx .mu .Unlock ()
357
+
358
+ task , ok := ctx .taskDescriptor [varsTask ].(map [string ]interface {})
359
+ if ! ok {
360
+ task = make (map [string ]interface {})
361
+ ctx .taskDescriptor [varsTask ] = task
362
+ }
363
+
364
+ task ["startedAt" ] = startedAt .UTC ().Format (time .RFC3339 )
365
+ }
366
+
367
+ func (ctx * workflowContext ) SetTaskName (name string ) {
368
+ ctx .mu .Lock ()
369
+ defer ctx .mu .Unlock ()
370
+
371
+ task , ok := ctx .taskDescriptor [varsTask ].(map [string ]interface {})
372
+ if ! ok {
373
+ task = make (map [string ]interface {})
374
+ ctx .taskDescriptor [varsTask ] = task
375
+ }
376
+
377
+ task ["name" ] = name
378
+ }
379
+
380
+ func (ctx * workflowContext ) SetTaskReference (ref string ) {
381
+ ctx .mu .Lock ()
382
+ defer ctx .mu .Unlock ()
383
+
384
+ task , ok := ctx .taskDescriptor [varsTask ].(map [string ]interface {})
385
+ if ! ok {
386
+ task = make (map [string ]interface {})
387
+ ctx .taskDescriptor [varsTask ] = task
388
+ }
389
+
390
+ task ["reference" ] = ref
391
+ }
392
+
393
+ func (ctx * workflowContext ) GetTaskReference () string {
394
+ ctx .mu .Lock ()
395
+ defer ctx .mu .Unlock ()
396
+ task , ok := ctx .taskDescriptor [varsTask ].(map [string ]interface {})
397
+ if ! ok {
398
+ return ""
399
+ }
400
+ return task ["reference" ].(string )
401
+ }
402
+
403
+ func (ctx * workflowContext ) ClearTaskContext () {
404
+ ctx .mu .Lock ()
405
+ defer ctx .mu .Unlock ()
406
+ ctx .taskDescriptor [varsTask ] = make (map [string ]interface {})
194
407
}
0 commit comments