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

uber / cadence / 0188cd88-db0c-4b6a-85c6-b4e82ccd7de8

18 Jun 2023 08:04AM UTC coverage: 57.263%. Remained the same
0188cd88-db0c-4b6a-85c6-b4e82ccd7de8

push

buildkite

web-flow
Extract EventColorFunction from ColorEvent (#5321)

* Extract EventColorFunction from ColorEvent

* set 12.4 for postgres container

---------

Co-authored-by: David Porter <david.porter@uber.com>

20 of 20 new or added lines in 1 file covered. (100.0%)

87062 of 152039 relevant lines covered (57.26%)

2484.39 hits per line

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

57.72
/tools/cli/utils.go
1
// Copyright (c) 2017-2020 Uber Technologies Inc.
2
// Portions of the Software are attributed to Copyright (c) 2020 Temporal Technologies Inc.
3
//
4
// Permission is hereby granted, free of charge, to any person obtaining a copy
5
// of this software and associated documentation files (the "Software"), to deal
6
// in the Software without restriction, including without limitation the rights
7
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
// copies of the Software, and to permit persons to whom the Software is
9
// furnished to do so, subject to the following conditions:
10
//
11
// The above copyright notice and this permission notice shall be included in
12
// all copies or substantial portions of the Software.
13
//
14
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
// THE SOFTWARE.
21

22
package cli
23

24
import (
25
        "bufio"
26
        "bytes"
27
        "context"
28
        "encoding/json"
29
        "errors"
30
        "fmt"
31
        "io"
32
        "io/ioutil"
33
        "os"
34
        "reflect"
35
        "regexp"
36
        "runtime/debug"
37
        "sort"
38
        "strconv"
39
        "strings"
40
        "time"
41

42
        "github.com/cristalhq/jwt/v3"
43
        "github.com/fatih/color"
44
        "github.com/urfave/cli"
45
        "github.com/valyala/fastjson"
46

47
        "github.com/uber/cadence/client/frontend"
48
        "github.com/uber/cadence/common"
49
        "github.com/uber/cadence/common/authorization"
50
        "github.com/uber/cadence/common/pagination"
51
        "github.com/uber/cadence/common/types"
52
)
53

54
// JSONHistorySerializer is used to encode history event in JSON
55
type JSONHistorySerializer struct{}
56

57
// Serialize serializes history.
58
func (j *JSONHistorySerializer) Serialize(h *types.History) ([]byte, error) {
×
59
        return json.Marshal(h.Events)
×
60
}
×
61

62
// Deserialize deserializes history
63
func (j *JSONHistorySerializer) Deserialize(data []byte) (*types.History, error) {
×
64
        var events []*types.HistoryEvent
×
65
        err := json.Unmarshal(data, &events)
×
66
        if err != nil {
×
67
                return nil, err
×
68
        }
×
69
        return &types.History{Events: events}, nil
×
70
}
71

72
// GetHistory helper method to iterate over all pages and return complete list of history events
73
func GetHistory(ctx context.Context, workflowClient frontend.Client, domain, workflowID, runID string) (*types.History, error) {
4✔
74
        events := []*types.HistoryEvent{}
4✔
75
        iterator, err := GetWorkflowHistoryIterator(ctx, workflowClient, domain, workflowID, runID, false, types.HistoryEventFilterTypeAllEvent.Ptr())
4✔
76
        for iterator.HasNext() {
8✔
77
                entity, err := iterator.Next()
4✔
78
                if err != nil {
4✔
79
                        return nil, err
×
80
                }
×
81
                events = append(events, entity.(*types.HistoryEvent))
4✔
82
        }
83
        history := &types.History{}
4✔
84
        history.Events = events
4✔
85
        return history, err
4✔
86
}
87

88
// GetWorkflowHistoryIterator returns a HistoryEvent iterator
89
func GetWorkflowHistoryIterator(
90
        ctx context.Context,
91
        workflowClient frontend.Client,
92
        domain,
93
        workflowID,
94
        runID string,
95
        isLongPoll bool,
96
        filterType *types.HistoryEventFilterType,
97
) (pagination.Iterator, error) {
11✔
98
        paginate := func(ctx context.Context, pageToken pagination.PageToken) (pagination.Page, error) {
22✔
99
                tcCtx, cancel := context.WithTimeout(ctx, 25*time.Second)
11✔
100
                defer cancel()
11✔
101

11✔
102
                var nextPageToken []byte
11✔
103
                if pageToken != nil {
11✔
104
                        nextPageToken, _ = pageToken.([]byte)
×
105
                }
×
106
                request := &types.GetWorkflowExecutionHistoryRequest{
11✔
107
                        Domain: domain,
11✔
108
                        Execution: &types.WorkflowExecution{
11✔
109
                                WorkflowID: workflowID,
11✔
110
                                RunID:      runID,
11✔
111
                        },
11✔
112
                        WaitForNewEvent:        isLongPoll,
11✔
113
                        HistoryEventFilterType: filterType,
11✔
114
                        NextPageToken:          nextPageToken,
11✔
115
                        SkipArchival:           isLongPoll,
11✔
116
                }
11✔
117

11✔
118
                var resp *types.GetWorkflowExecutionHistoryResponse
11✔
119
                var err error
11✔
120
        Loop:
11✔
121
                for {
22✔
122
                        resp, err = workflowClient.GetWorkflowExecutionHistory(tcCtx, request)
11✔
123
                        if err != nil {
11✔
124
                                return pagination.Page{}, err
×
125
                        }
×
126

127
                        if isLongPoll && len(resp.History.Events) == 0 && len(resp.NextPageToken) != 0 {
11✔
128
                                request.NextPageToken = resp.NextPageToken
×
129
                                continue Loop
×
130
                        }
131
                        break Loop
11✔
132
                }
133
                entities := make([]pagination.Entity, len(resp.History.Events))
11✔
134
                for i, e := range resp.History.Events {
22✔
135
                        entities[i] = e
11✔
136
                }
11✔
137
                var nextToken interface{} = resp.NextPageToken
11✔
138
                if len(resp.NextPageToken) == 0 {
22✔
139
                        nextToken = nil
11✔
140
                }
11✔
141
                page := pagination.Page{
11✔
142
                        CurrentToken: pageToken,
11✔
143
                        NextToken:    nextToken,
11✔
144
                        Entities:     entities,
11✔
145
                }
11✔
146
                return page, err
11✔
147
        }
148
        return pagination.NewIterator(ctx, nil, paginate), nil
11✔
149
}
150

151
// HistoryEventToString convert HistoryEvent to string
152
func HistoryEventToString(e *types.HistoryEvent, printFully bool, maxFieldLength int) string {
6✔
153
        data := getEventAttributes(e)
6✔
154
        return anyToString(data, printFully, maxFieldLength)
6✔
155
}
6✔
156

157
func anyToString(d interface{}, printFully bool, maxFieldLength int) string {
39✔
158
        // fields related to schedule are of time.Time type, and we shouldn't dive
39✔
159
        // into it with reflection - it's fields are private.
39✔
160
        tm, ok := d.(time.Time)
39✔
161
        if ok {
42✔
162
                return trimText(tm.String(), maxFieldLength)
3✔
163
        }
3✔
164

165
        v := reflect.ValueOf(d)
36✔
166
        switch v.Kind() {
36✔
167
        case reflect.Ptr:
9✔
168
                return anyToString(v.Elem().Interface(), printFully, maxFieldLength)
9✔
169
        case reflect.Struct:
27✔
170
                var buf bytes.Buffer
27✔
171
                t := reflect.TypeOf(d)
27✔
172
                buf.WriteString("{")
27✔
173
                for i := 0; i < v.NumField(); i++ {
339✔
174
                        f := v.Field(i)
312✔
175
                        if f.Kind() == reflect.Invalid {
312✔
176
                                continue
×
177
                        }
178
                        fieldValue := valueToString(f, printFully, maxFieldLength)
312✔
179
                        if len(fieldValue) == 0 {
522✔
180
                                continue
210✔
181
                        }
182
                        if buf.Len() > 1 {
177✔
183
                                buf.WriteString(", ")
75✔
184
                        }
75✔
185
                        fieldName := t.Field(i).Name
102✔
186
                        if !isAttributeName(fieldName) {
203✔
187
                                if !printFully {
168✔
188
                                        fieldValue = trimTextAndBreakWords(fieldValue, maxFieldLength)
67✔
189
                                } else if maxFieldLength != 0 { // for command run workflow and observe history
101✔
190
                                        fieldValue = trimText(fieldValue, maxFieldLength)
×
191
                                }
×
192
                        }
193
                        if fieldName == "Reason" || fieldName == "Details" || fieldName == "Cause" {
102✔
194
                                buf.WriteString(fmt.Sprintf("%s:%s", color.RedString(fieldName), color.MagentaString(fieldValue)))
×
195
                        } else {
102✔
196
                                buf.WriteString(fmt.Sprintf("%s:%s", fieldName, fieldValue))
102✔
197
                        }
102✔
198
                }
199
                buf.WriteString("}")
27✔
200
                return buf.String()
27✔
201
        default:
×
202
                return fmt.Sprint(d)
×
203
        }
204
}
205

206
func valueToString(v reflect.Value, printFully bool, maxFieldLength int) string {
524✔
207
        switch v.Kind() {
524✔
208
        case reflect.Ptr:
212✔
209
                return valueToString(v.Elem(), printFully, maxFieldLength)
212✔
210
        case reflect.Struct:
18✔
211
                return anyToString(v.Interface(), printFully, maxFieldLength)
18✔
212
        case reflect.Invalid:
180✔
213
                return ""
180✔
214
        case reflect.Slice:
21✔
215
                if v.Type().Elem().Kind() == reflect.Uint8 {
42✔
216
                        n := string(v.Bytes())
21✔
217
                        if n != "" && n[len(n)-1] == '\n' {
21✔
218
                                return fmt.Sprintf("[%v]", n[:len(n)-1])
×
219
                        }
×
220
                        return fmt.Sprintf("[%v]", n)
21✔
221
                }
222
                return fmt.Sprintf("[len=%d]", v.Len())
×
223
        case reflect.Map:
11✔
224
                str := "map{"
11✔
225
                for i, key := range v.MapKeys() {
14✔
226
                        str += key.String() + ":"
3✔
227
                        val := v.MapIndex(key)
3✔
228
                        switch val.Interface().(type) {
3✔
229
                        case []byte:
3✔
230
                                str += string(val.Interface().([]byte))
3✔
231
                        default:
×
232
                                str += val.String()
×
233
                        }
234
                        if i != len(v.MapKeys())-1 {
4✔
235
                                str += ", "
1✔
236
                        }
1✔
237
                }
238
                str += "}"
11✔
239
                return str
11✔
240
        default:
82✔
241
                return fmt.Sprint(v.Interface())
82✔
242
        }
243
}
244

245
// limit the maximum length for each field
246
func trimText(input string, maxFieldLength int) string {
70✔
247
        if len(input) > maxFieldLength {
72✔
248
                input = fmt.Sprintf("%s ... %s", input[:maxFieldLength/2], input[(len(input)-maxFieldLength/2):])
2✔
249
        }
2✔
250
        return input
70✔
251
}
252

253
// limit the maximum length for each field, and break long words for table item correctly wrap words
254
func trimTextAndBreakWords(input string, maxFieldLength int) string {
67✔
255
        input = trimText(input, maxFieldLength)
67✔
256
        return breakLongWords(input, maxWordLength)
67✔
257
}
67✔
258

259
// long words will make output in table cell looks bad,
260
// break long text "ltltltltllt..." to "ltlt ltlt lt..." will make use of table autowrap so that output is pretty.
261
func breakLongWords(input string, maxWordLength int) string {
73✔
262
        if len(input) <= maxWordLength {
141✔
263
                return input
68✔
264
        }
68✔
265

266
        cnt := 0
5✔
267
        for i := 0; i < len(input); i++ {
553✔
268
                if cnt == maxWordLength {
558✔
269
                        cnt = 0
10✔
270
                        input = input[:i] + " " + input[i:]
10✔
271
                        continue
10✔
272
                }
273
                cnt++
538✔
274
                if input[i] == ' ' {
543✔
275
                        cnt = 0
5✔
276
                }
5✔
277
        }
278
        return input
5✔
279
}
280

281
// ColorEvent takes an event and return string with color
282
// Event with color mapping rules:
283
//
284
//        Failed - red
285
//        Timeout - yellow
286
//        Canceled - magenta
287
//        Completed - green
288
//        Started - blue
289
//        Others - default (white/black)
290
func ColorEvent(e *types.HistoryEvent) string {
11✔
291
        f := EventColorFunction(*e.EventType)
11✔
292
        return f(e.EventType.String())
11✔
293
}
11✔
294

295
func EventColorFunction(eventType types.EventType) func(format string, a ...interface{}) string {
11✔
296
        var colorFunc func(format string, a ...interface{}) string
11✔
297
        noColorFunc := func(format string, a ...interface{}) string {
11✔
298
                return format
×
299
        }
×
300
        switch eventType {
11✔
301
        case types.EventTypeWorkflowExecutionStarted,
302
                types.EventTypeChildWorkflowExecutionStarted:
11✔
303
                colorFunc = color.BlueString
11✔
304

305
        case types.EventTypeWorkflowExecutionCompleted,
306
                types.EventTypeChildWorkflowExecutionCompleted:
×
307
                colorFunc = color.GreenString
×
308

309
        case types.EventTypeWorkflowExecutionFailed,
310
                types.EventTypeRequestCancelActivityTaskFailed,
311
                types.EventTypeCancelTimerFailed,
312
                types.EventTypeStartChildWorkflowExecutionFailed,
313
                types.EventTypeChildWorkflowExecutionFailed,
314
                types.EventTypeRequestCancelExternalWorkflowExecutionFailed,
315
                types.EventTypeSignalExternalWorkflowExecutionFailed,
316
                types.EventTypeActivityTaskFailed:
×
317
                colorFunc = color.RedString
×
318

319
        case types.EventTypeWorkflowExecutionTimedOut,
320
                types.EventTypeActivityTaskTimedOut,
321
                types.EventTypeWorkflowExecutionCanceled,
322
                types.EventTypeChildWorkflowExecutionTimedOut,
323
                types.EventTypeDecisionTaskTimedOut:
×
324
                colorFunc = color.YellowString
×
325

326
        case types.EventTypeChildWorkflowExecutionCanceled:
×
327
                colorFunc = color.MagentaString
×
328

329
        default:
×
330
                colorFunc = noColorFunc
×
331
        }
332

333
        return colorFunc
11✔
334
}
335

336
func getEventAttributes(e *types.HistoryEvent) interface{} {
6✔
337
        var data interface{}
6✔
338
        switch e.GetEventType() {
6✔
339
        case types.EventTypeWorkflowExecutionStarted:
6✔
340
                data = e.WorkflowExecutionStartedEventAttributes
6✔
341

342
        case types.EventTypeWorkflowExecutionCompleted:
×
343
                data = e.WorkflowExecutionCompletedEventAttributes
×
344

345
        case types.EventTypeWorkflowExecutionFailed:
×
346
                data = e.WorkflowExecutionFailedEventAttributes
×
347

348
        case types.EventTypeWorkflowExecutionTimedOut:
×
349
                data = e.WorkflowExecutionTimedOutEventAttributes
×
350

351
        case types.EventTypeDecisionTaskScheduled:
×
352
                data = e.DecisionTaskScheduledEventAttributes
×
353

354
        case types.EventTypeDecisionTaskStarted:
×
355
                data = e.DecisionTaskStartedEventAttributes
×
356

357
        case types.EventTypeDecisionTaskCompleted:
×
358
                data = e.DecisionTaskCompletedEventAttributes
×
359

360
        case types.EventTypeDecisionTaskTimedOut:
×
361
                data = e.DecisionTaskTimedOutEventAttributes
×
362

363
        case types.EventTypeActivityTaskScheduled:
×
364
                data = e.ActivityTaskScheduledEventAttributes
×
365

366
        case types.EventTypeActivityTaskStarted:
×
367
                data = e.ActivityTaskStartedEventAttributes
×
368

369
        case types.EventTypeActivityTaskCompleted:
×
370
                data = e.ActivityTaskCompletedEventAttributes
×
371

372
        case types.EventTypeActivityTaskFailed:
×
373
                data = e.ActivityTaskFailedEventAttributes
×
374

375
        case types.EventTypeActivityTaskTimedOut:
×
376
                data = e.ActivityTaskTimedOutEventAttributes
×
377

378
        case types.EventTypeActivityTaskCancelRequested:
×
379
                data = e.ActivityTaskCancelRequestedEventAttributes
×
380

381
        case types.EventTypeRequestCancelActivityTaskFailed:
×
382
                data = e.RequestCancelActivityTaskFailedEventAttributes
×
383

384
        case types.EventTypeActivityTaskCanceled:
×
385
                data = e.ActivityTaskCanceledEventAttributes
×
386

387
        case types.EventTypeTimerStarted:
×
388
                data = e.TimerStartedEventAttributes
×
389

390
        case types.EventTypeTimerFired:
×
391
                data = e.TimerFiredEventAttributes
×
392

393
        case types.EventTypeCancelTimerFailed:
×
394
                data = e.CancelTimerFailedEventAttributes
×
395

396
        case types.EventTypeTimerCanceled:
×
397
                data = e.TimerCanceledEventAttributes
×
398

399
        case types.EventTypeWorkflowExecutionCancelRequested:
×
400
                data = e.WorkflowExecutionCancelRequestedEventAttributes
×
401

402
        case types.EventTypeWorkflowExecutionCanceled:
×
403
                data = e.WorkflowExecutionCanceledEventAttributes
×
404

405
        case types.EventTypeRequestCancelExternalWorkflowExecutionInitiated:
×
406
                data = e.RequestCancelExternalWorkflowExecutionInitiatedEventAttributes
×
407

408
        case types.EventTypeRequestCancelExternalWorkflowExecutionFailed:
×
409
                data = e.RequestCancelExternalWorkflowExecutionFailedEventAttributes
×
410

411
        case types.EventTypeExternalWorkflowExecutionCancelRequested:
×
412
                data = e.ExternalWorkflowExecutionCancelRequestedEventAttributes
×
413

414
        case types.EventTypeMarkerRecorded:
×
415
                data = e.MarkerRecordedEventAttributes
×
416

417
        case types.EventTypeWorkflowExecutionSignaled:
×
418
                data = e.WorkflowExecutionSignaledEventAttributes
×
419

420
        case types.EventTypeWorkflowExecutionTerminated:
×
421
                data = e.WorkflowExecutionTerminatedEventAttributes
×
422

423
        case types.EventTypeWorkflowExecutionContinuedAsNew:
×
424
                data = e.WorkflowExecutionContinuedAsNewEventAttributes
×
425

426
        case types.EventTypeStartChildWorkflowExecutionInitiated:
×
427
                data = e.StartChildWorkflowExecutionInitiatedEventAttributes
×
428

429
        case types.EventTypeStartChildWorkflowExecutionFailed:
×
430
                data = e.StartChildWorkflowExecutionFailedEventAttributes
×
431

432
        case types.EventTypeChildWorkflowExecutionStarted:
×
433
                data = e.ChildWorkflowExecutionStartedEventAttributes
×
434

435
        case types.EventTypeChildWorkflowExecutionCompleted:
×
436
                data = e.ChildWorkflowExecutionCompletedEventAttributes
×
437

438
        case types.EventTypeChildWorkflowExecutionFailed:
×
439
                data = e.ChildWorkflowExecutionFailedEventAttributes
×
440

441
        case types.EventTypeChildWorkflowExecutionCanceled:
×
442
                data = e.ChildWorkflowExecutionCanceledEventAttributes
×
443

444
        case types.EventTypeChildWorkflowExecutionTimedOut:
×
445
                data = e.ChildWorkflowExecutionTimedOutEventAttributes
×
446

447
        case types.EventTypeChildWorkflowExecutionTerminated:
×
448
                data = e.ChildWorkflowExecutionTerminatedEventAttributes
×
449

450
        case types.EventTypeSignalExternalWorkflowExecutionInitiated:
×
451
                data = e.SignalExternalWorkflowExecutionInitiatedEventAttributes
×
452

453
        case types.EventTypeSignalExternalWorkflowExecutionFailed:
×
454
                data = e.SignalExternalWorkflowExecutionFailedEventAttributes
×
455

456
        case types.EventTypeExternalWorkflowExecutionSignaled:
×
457
                data = e.ExternalWorkflowExecutionSignaledEventAttributes
×
458

459
        default:
×
460
                data = e
×
461
        }
462
        return data
6✔
463
}
464

465
func isAttributeName(name string) bool {
104✔
466
        for i := types.EventType(0); i < types.EventType(40); i++ {
4,186✔
467
                if name == i.String()+"EventAttributes" {
4,084✔
468
                        return true
2✔
469
                }
2✔
470
        }
471
        return false
102✔
472
}
473

474
func getCurrentUserFromEnv() string {
×
475
        for _, n := range envKeysForUserName {
×
476
                if len(os.Getenv(n)) > 0 {
×
477
                        return os.Getenv(n)
×
478
                }
×
479
        }
480
        return "unknown"
×
481
}
482

483
func prettyPrintJSONObject(o interface{}) {
3✔
484
        b, err := json.MarshalIndent(o, "", "  ")
3✔
485
        if err != nil {
3✔
486
                fmt.Printf("Error when try to print pretty: %v\n", err)
×
487
                fmt.Println(o)
×
488
        }
×
489
        os.Stdout.Write(b)
3✔
490
        fmt.Println()
3✔
491
}
492

493
func mapKeysToArray(m map[string]string) []string {
3✔
494
        var out []string
3✔
495
        for k := range m {
24✔
496
                out = append(out, k)
21✔
497
        }
21✔
498
        return out
3✔
499
}
500

501
func intSliceToSet(s []int) map[int]struct{} {
×
502
        var ret = make(map[int]struct{}, len(s))
×
503
        for _, v := range s {
×
504
                ret[v] = struct{}{}
×
505
        }
×
506
        return ret
×
507
}
508

509
func printMessage(msg string) {
×
510
        fmt.Printf("%s %s\n", "cadence:", msg)
×
511
}
×
512

513
func printError(msg string, err error) {
24✔
514
        if err != nil {
46✔
515
                fmt.Printf("%s %s\n%s %+v\n", colorRed("Error:"), msg, colorMagenta("Error Details:"), err)
22✔
516
                if os.Getenv(showErrorStackEnv) != `` {
22✔
517
                        fmt.Printf("Stack trace:\n")
×
518
                        debug.PrintStack()
×
519
                } else {
22✔
520
                        fmt.Printf("('export %s=1' to see stack traces)\n", showErrorStackEnv)
22✔
521
                }
22✔
522
        } else {
2✔
523
                fmt.Printf("%s %s\n", colorRed("Error:"), msg)
2✔
524
        }
2✔
525
}
526

527
// ErrorAndExit print easy to understand error msg first then error detail in a new line
528
func ErrorAndExit(msg string, err error) {
24✔
529
        printError(msg, err)
24✔
530
        osExit(1)
24✔
531
}
24✔
532

533
func getWorkflowClient(c *cli.Context) frontend.Client {
41✔
534
        return cFactory.ServerFrontendClient(c)
41✔
535
}
41✔
536

537
func getRequiredOption(c *cli.Context, optionName string) string {
44✔
538
        value := c.String(optionName)
44✔
539
        if len(value) == 0 {
44✔
540
                ErrorAndExit(fmt.Sprintf("Option %s is required", optionName), nil)
×
541
        }
×
542
        return value
44✔
543
}
544

545
func getRequiredInt64Option(c *cli.Context, optionName string) int64 {
×
546
        if !c.IsSet(optionName) {
×
547
                ErrorAndExit(fmt.Sprintf("Option %s is required", optionName), nil)
×
548
        }
×
549
        return c.Int64(optionName)
×
550
}
551

552
func getRequiredIntOption(c *cli.Context, optionName string) int {
1✔
553
        if !c.IsSet(optionName) {
1✔
554
                ErrorAndExit(fmt.Sprintf("Option %s is required", optionName), nil)
×
555
        }
×
556
        return c.Int(optionName)
1✔
557
}
558

559
func getRequiredGlobalOption(c *cli.Context, optionName string) string {
59✔
560
        value := c.GlobalString(optionName)
59✔
561
        if len(value) == 0 {
59✔
562
                ErrorAndExit(fmt.Sprintf("Global option %s is required", optionName), nil)
×
563
        }
×
564
        return value
59✔
565
}
566

567
func timestampPtrToStringPtr(unixNanoPtr *int64, onlyTime bool) *string {
×
568
        if unixNanoPtr == nil {
×
569
                return nil
×
570
        }
×
571
        return common.StringPtr(convertTime(*unixNanoPtr, onlyTime))
×
572
}
573

574
func convertTime(unixNano int64, onlyTime bool) string {
16✔
575
        t := time.Unix(0, unixNano)
16✔
576
        var result string
16✔
577
        if onlyTime {
16✔
578
                result = t.Format(defaultTimeFormat)
×
579
        } else {
16✔
580
                result = t.Format(defaultDateTimeFormat)
16✔
581
        }
16✔
582
        return result
16✔
583
}
584

585
func parseTime(timeStr string, defaultValue int64) int64 {
49✔
586
        if len(timeStr) == 0 {
66✔
587
                return defaultValue
17✔
588
        }
17✔
589

590
        // try to parse
591
        parsedTime, err := time.Parse(defaultDateTimeFormat, timeStr)
32✔
592
        if err == nil {
33✔
593
                return parsedTime.UnixNano()
1✔
594
        }
1✔
595

596
        // treat as raw time
597
        resultValue, err := strconv.ParseInt(timeStr, 10, 64)
31✔
598
        if err == nil {
32✔
599
                return resultValue
1✔
600
        }
1✔
601

602
        // treat as time range format
603
        parsedTime, err = parseTimeRange(timeStr)
30✔
604
        if err != nil {
30✔
605
                ErrorAndExit(fmt.Sprintf("Cannot parse time '%s', use UTC format '2006-01-02T15:04:05Z', "+
×
606
                        "time range or raw UnixNano directly. See help for more details.", timeStr), err)
×
607
        }
×
608
        return parsedTime.UnixNano()
30✔
609
}
610

611
// parseTimeRange parses a given time duration string (in format X<time-duration>) and
612
// returns parsed timestamp given that duration in the past from current time.
613
// All valid values must contain a number followed by a time-duration, from the following list (long form/short form):
614
// - second/s
615
// - minute/m
616
// - hour/h
617
// - day/d
618
// - week/w
619
// - month/M
620
// - year/y
621
// For example, possible input values, and their result:
622
// - "3d" or "3day" --> three days --> time.Now().Add(-3 * 24 * time.Hour)
623
// - "2m" or "2minute" --> two minutes --> time.Now().Add(-2 * time.Minute)
624
// - "1w" or "1week" --> one week --> time.Now().Add(-7 * 24 * time.Hour)
625
// - "30s" or "30second" --> thirty seconds --> time.Now().Add(-30 * time.Second)
626
// Note: Duration strings are case-sensitive, and should be used as mentioned above only.
627
// Limitation: Value of numerical multiplier, X should be in b/w 0 - 1e6 (1 million), boundary values excluded i.e.
628
// 0 < X < 1e6. Also, the maximum time in the past can be 1 January 1970 00:00:00 UTC (epoch time),
629
// so giving "1000y" will result in epoch time.
630
func parseTimeRange(timeRange string) (time.Time, error) {
30✔
631
        match, err := regexp.MatchString(defaultDateTimeRangeShortRE, timeRange)
30✔
632
        if !match { // fallback on to check if it's of longer notation
44✔
633
                _, err = regexp.MatchString(defaultDateTimeRangeLongRE, timeRange)
14✔
634
        }
14✔
635
        if err != nil {
30✔
636
                return time.Time{}, err
×
637
        }
×
638

639
        re, _ := regexp.Compile(defaultDateTimeRangeNum)
30✔
640
        idx := re.FindStringSubmatchIndex(timeRange)
30✔
641
        if idx == nil {
30✔
642
                return time.Time{}, fmt.Errorf("cannot parse timeRange %s", timeRange)
×
643
        }
×
644

645
        num, err := strconv.Atoi(timeRange[idx[0]:idx[1]])
30✔
646
        if err != nil {
30✔
647
                return time.Time{}, fmt.Errorf("cannot parse timeRange %s", timeRange)
×
648
        }
×
649
        if num >= 1e6 {
30✔
650
                return time.Time{}, fmt.Errorf("invalid time-duation multiplier %d, allowed range is 0 < multiplier < 1000000", num)
×
651
        }
×
652

653
        dur, err := parseTimeDuration(timeRange[idx[1]:])
30✔
654
        if err != nil {
30✔
655
                return time.Time{}, fmt.Errorf("cannot parse timeRange %s", timeRange)
×
656
        }
×
657

658
        res := time.Now().Add(time.Duration(-num) * dur) // using server's local timezone
30✔
659
        epochTime := time.Unix(0, 0)
30✔
660
        if res.Before(epochTime) {
32✔
661
                res = epochTime
2✔
662
        }
2✔
663
        return res, nil
30✔
664
}
665

666
func parseSingleTs(ts string) (time.Time, error) {
×
667
        var tsOut time.Time
×
668
        var err error
×
669
        formats := []string{"2006-01-02T15:04:05", "2006-01-02T15:04", "2006-01-02", "2006-01-02T15:04:05+0700", time.RFC3339}
×
670
        for _, format := range formats {
×
671
                if tsOut, err = time.Parse(format, ts); err == nil {
×
672
                        return tsOut, err
×
673
                }
×
674
        }
675
        return tsOut, err
×
676
}
677

678
// parseTimeDuration parses the given time duration in either short or long convention
679
// and returns the time.Duration
680
// Valid values (long notation/short notation):
681
// - second/s
682
// - minute/m
683
// - hour/h
684
// - day/d
685
// - week/w
686
// - month/M
687
// - year/y
688
// NOTE: the input "duration" is case-sensitive
689
func parseTimeDuration(duration string) (dur time.Duration, err error) {
30✔
690
        switch duration {
30✔
691
        case "s", "second":
4✔
692
                dur = time.Second
4✔
693
        case "m", "minute":
4✔
694
                dur = time.Minute
4✔
695
        case "h", "hour":
4✔
696
                dur = time.Hour
4✔
697
        case "d", "day":
4✔
698
                dur = day
4✔
699
        case "w", "week":
4✔
700
                dur = week
4✔
701
        case "M", "month":
4✔
702
                dur = month
4✔
703
        case "y", "year":
6✔
704
                dur = year
6✔
705
        default:
×
706
                err = fmt.Errorf("unknown time duration %s", duration)
×
707
        }
708
        return
30✔
709
}
710

711
func strToTaskListType(str string) types.TaskListType {
2✔
712
        if strings.ToLower(str) == "activity" {
3✔
713
                return types.TaskListTypeActivity
1✔
714
        }
1✔
715
        return types.TaskListTypeDecision
1✔
716
}
717

718
func getCliIdentity() string {
15✔
719
        return fmt.Sprintf("cadence-cli@%s", getHostName())
15✔
720
}
15✔
721

722
func getHostName() string {
15✔
723
        hostName, err := os.Hostname()
15✔
724
        if err != nil {
15✔
725
                hostName = "UnKnown"
×
726
        }
×
727
        return hostName
15✔
728
}
729

730
func processJWTFlags(ctx context.Context, cliCtx *cli.Context) context.Context {
79✔
731
        path := getJWTPrivateKey(cliCtx)
79✔
732
        t := getJWT(cliCtx)
79✔
733
        var token string
79✔
734

79✔
735
        if t != "" {
79✔
736
                token = t
×
737
        } else if path != "" {
79✔
738
                createdToken, err := createJWT(path)
×
739
                if err != nil {
×
740
                        ErrorAndExit("Error creating JWT token", err)
×
741
                }
×
742
                token = *createdToken
×
743
        }
744

745
        ctx = context.WithValue(ctx, CtxKeyJWT, token)
79✔
746
        return ctx
79✔
747
}
748

749
func populateContextFromCLIContext(ctx context.Context, cliCtx *cli.Context) context.Context {
79✔
750
        ctx = processJWTFlags(ctx, cliCtx)
79✔
751
        return ctx
79✔
752
}
79✔
753

754
func newContext(c *cli.Context) (context.Context, context.CancelFunc) {
50✔
755
        return newTimedContext(c, defaultContextTimeout)
50✔
756
}
50✔
757

758
func newContextForLongPoll(c *cli.Context) (context.Context, context.CancelFunc) {
22✔
759
        return newTimedContext(c, defaultContextTimeoutForLongPoll)
22✔
760
}
22✔
761

762
func newIndefiniteContext(c *cli.Context) (context.Context, context.CancelFunc) {
7✔
763
        if c.GlobalIsSet(FlagContextTimeout) {
7✔
764
                return newTimedContext(c, time.Duration(c.GlobalInt(FlagContextTimeout))*time.Second)
×
765
        }
×
766

767
        return context.WithCancel(populateContextFromCLIContext(context.Background(), c))
7✔
768
}
769

770
func newTimedContext(c *cli.Context, timeout time.Duration) (context.Context, context.CancelFunc) {
72✔
771
        if c.GlobalIsSet(FlagContextTimeout) {
72✔
772
                timeout = time.Duration(c.GlobalInt(FlagContextTimeout)) * time.Second
×
773
        }
×
774
        ctx := populateContextFromCLIContext(context.Background(), c)
72✔
775
        return context.WithTimeout(ctx, timeout)
72✔
776
}
777

778
// process and validate input provided through cmd or file
779
func processJSONInput(c *cli.Context) string {
11✔
780
        return processJSONInputHelper(c, jsonTypeInput)
11✔
781
}
11✔
782

783
// process and validate json
784
func processJSONInputHelper(c *cli.Context, jType jsonType) string {
23✔
785
        var flagNameOfRawInput string
23✔
786
        var flagNameOfInputFileName string
23✔
787

23✔
788
        switch jType {
23✔
789
        case jsonTypeInput:
11✔
790
                flagNameOfRawInput = FlagInput
11✔
791
                flagNameOfInputFileName = FlagInputFile
11✔
792
        case jsonTypeMemo:
6✔
793
                flagNameOfRawInput = FlagMemo
6✔
794
                flagNameOfInputFileName = FlagMemoFile
6✔
795
        case jsonTypeHeader:
6✔
796
                flagNameOfRawInput = FlagHeaderValue
6✔
797
                flagNameOfInputFileName = FlagHeaderFile
6✔
798
        case jsonTypeSignal:
×
799
                flagNameOfRawInput = FlagSignalInput
×
800
                flagNameOfInputFileName = FlagSignalInputFile
×
801
        default:
×
802
                return ""
×
803
        }
804

805
        var input string
23✔
806
        if c.IsSet(flagNameOfRawInput) {
23✔
807
                input = c.String(flagNameOfRawInput)
×
808
        } else if c.IsSet(flagNameOfInputFileName) {
23✔
809
                inputFile := c.String(flagNameOfInputFileName)
×
810
                // This method is purely used to parse input from the CLI. The input comes from a trusted user
×
811
                // #nosec
×
812
                data, err := ioutil.ReadFile(inputFile)
×
813
                if err != nil {
×
814
                        ErrorAndExit("Error reading input file", err)
×
815
                }
×
816
                input = string(data)
×
817
        }
818
        if input != "" {
23✔
819
                if err := validateJSONs(input); err != nil {
×
820
                        ErrorAndExit("Input is not valid JSON, or JSONs concatenated with spaces/newlines.", err)
×
821
                }
×
822
        }
823
        return input
23✔
824
}
825

826
func processMultipleKeys(rawKey, separator string) []string {
12✔
827
        var keys []string
12✔
828
        if strings.TrimSpace(rawKey) != "" {
12✔
829
                keys = strings.Split(rawKey, separator)
×
830
        }
×
831
        return keys
12✔
832
}
833

834
func processMultipleJSONValues(rawValue string) []string {
12✔
835
        var values []string
12✔
836
        var sc fastjson.Scanner
12✔
837
        sc.Init(rawValue)
12✔
838
        for sc.Next() {
12✔
839
                values = append(values, sc.Value().String())
×
840
        }
×
841
        if err := sc.Error(); err != nil {
12✔
842
                ErrorAndExit("Parse json error.", err)
×
843
        }
×
844
        return values
12✔
845
}
846

847
func mapFromKeysValues(keys, values []string) map[string][]byte {
12✔
848
        fields := map[string][]byte{}
12✔
849
        for i, key := range keys {
12✔
850
                fields[key] = []byte(values[i])
×
851
        }
×
852
        return fields
12✔
853
}
854

855
// validate whether str is a valid json or multi valid json concatenated with spaces/newlines
856
func validateJSONs(str string) error {
×
857
        input := []byte(str)
×
858
        dec := json.NewDecoder(bytes.NewReader(input))
×
859
        for {
×
860
                _, err := dec.Token()
×
861
                if err == io.EOF {
×
862
                        return nil // End of input, valid JSON
×
863
                }
×
864
                if err != nil {
×
865
                        return err // Invalid input
×
866
                }
×
867
        }
868
}
869

870
// use parseBool to ensure all BOOL search attributes only be "true" or "false"
871
func parseBool(str string) (bool, error) {
9✔
872
        switch str {
9✔
873
        case "true":
2✔
874
                return true, nil
2✔
875
        case "false":
2✔
876
                return false, nil
2✔
877
        }
878
        return false, fmt.Errorf("not parseable bool value: %s", str)
5✔
879
}
880

881
func trimSpace(strs []string) []string {
×
882
        result := make([]string, len(strs))
×
883
        for i, v := range strs {
×
884
                result[i] = strings.TrimSpace(v)
×
885
        }
×
886
        return result
×
887
}
888

889
func parseArray(v string) (interface{}, error) {
10✔
890
        if len(v) > 0 && v[0] == '[' && v[len(v)-1] == ']' {
17✔
891
                parsedValues, err := fastjson.Parse(v)
7✔
892
                if err != nil {
8✔
893
                        return nil, err
1✔
894
                }
1✔
895
                arr, err := parsedValues.Array()
6✔
896
                if err != nil {
6✔
897
                        return nil, err
×
898
                }
×
899
                result := make([]interface{}, len(arr))
6✔
900
                for i, item := range arr {
22✔
901
                        s := item.String()
16✔
902
                        if len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"' { // remove addition quote from json
26✔
903
                                s = s[1 : len(s)-1]
10✔
904
                                if sTime, err := time.Parse(defaultDateTimeFormat, s); err == nil {
12✔
905
                                        result[i] = sTime
2✔
906
                                        continue
2✔
907
                                }
908
                        }
909
                        result[i] = s
14✔
910
                }
911
                return result, nil
6✔
912
        }
913
        return nil, errors.New("not array")
3✔
914
}
915

916
func convertStringToRealType(v string) interface{} {
7✔
917
        var genVal interface{}
7✔
918
        var err error
7✔
919

7✔
920
        if genVal, err = strconv.ParseInt(v, 10, 64); err == nil {
8✔
921

1✔
922
        } else if genVal, err = parseBool(v); err == nil {
9✔
923

2✔
924
        } else if genVal, err = strconv.ParseFloat(v, 64); err == nil {
7✔
925

1✔
926
        } else if genVal, err = time.Parse(defaultDateTimeFormat, v); err == nil {
5✔
927

1✔
928
        } else if genVal, err = parseArray(v); err == nil {
4✔
929

1✔
930
        } else {
2✔
931
                genVal = v
1✔
932
        }
1✔
933

934
        return genVal
7✔
935
}
936

937
func truncate(str string) string {
3✔
938
        if len(str) > maxOutputStringLength {
3✔
939
                return str[:maxOutputStringLength]
×
940
        }
×
941
        return str
3✔
942
}
943

944
// this only works for ANSI terminal, which means remove existing lines won't work if users redirect to file
945
// ref: https://en.wikipedia.org/wiki/ANSI_escape_code
946
func removePrevious2LinesFromTerminal() {
×
947
        fmt.Printf("\033[1A")
×
948
        fmt.Printf("\033[2K")
×
949
        fmt.Printf("\033[1A")
×
950
        fmt.Printf("\033[2K")
×
951
}
×
952

953
func showNextPage() bool {
×
954
        fmt.Printf("Press %s to show next page, press %s to quit: ",
×
955
                color.GreenString("Enter"), color.RedString("any other key then Enter"))
×
956
        var input string
×
957
        fmt.Scanln(&input)
×
958
        return strings.Trim(input, " ") == ""
×
959
}
×
960

961
// prompt will show input msg, then waiting user input y/yes to continue
962
func prompt(msg string) {
×
963
        reader := bufio.NewReader(os.Stdin)
×
964
        fmt.Println(msg)
×
965
        text, _ := reader.ReadString('\n')
×
966
        textLower := strings.ToLower(strings.TrimRight(text, "\n"))
×
967
        if textLower != "y" && textLower != "yes" {
×
968
                os.Exit(0)
×
969
        }
×
970
}
971
func getInputFile(inputFile string) *os.File {
×
972
        if len(inputFile) == 0 {
×
973
                info, err := os.Stdin.Stat()
×
974
                if err != nil {
×
975
                        ErrorAndExit("Failed to stat stdin file handle", err)
×
976
                }
×
977
                if info.Mode()&os.ModeCharDevice != 0 || info.Size() <= 0 {
×
978
                        fmt.Fprintln(os.Stderr, "Provide a filename or pass data to STDIN")
×
979
                        os.Exit(1)
×
980
                }
×
981
                return os.Stdin
×
982
        }
983
        // This code is executed from the CLI. All user input is from a CLI user.
984
        // #nosec
985
        f, err := os.Open(inputFile)
×
986
        if err != nil {
×
987
                ErrorAndExit(fmt.Sprintf("Failed to open input file for reading: %v", inputFile), err)
×
988
        }
×
989
        return f
×
990
}
991

992
// createJWT defines the logic to create a JWT
993
func createJWT(keyPath string) (*string, error) {
×
994
        claims := authorization.JWTClaims{
×
995
                Admin: true,
×
996
                Iat:   time.Now().Unix(),
×
997
                TTL:   60 * 10,
×
998
        }
×
999

×
1000
        privateKey, err := common.LoadRSAPrivateKey(keyPath)
×
1001
        if err != nil {
×
1002
                return nil, err
×
1003
        }
×
1004

1005
        signer, err := jwt.NewSignerRS(jwt.RS256, privateKey)
×
1006
        if err != nil {
×
1007
                return nil, err
×
1008
        }
×
1009
        builder := jwt.NewBuilder(signer)
×
1010
        token, err := builder.Build(claims)
×
1011
        if token == nil {
×
1012
                return nil, err
×
1013
        }
×
1014
        tokenString := token.String()
×
1015
        return &tokenString, nil
×
1016
}
1017

1018
func getWorkflowMemo(input map[string]interface{}) (*types.Memo, error) {
1✔
1019
        if input == nil {
1✔
1020
                return nil, nil
×
1021
        }
×
1022

1023
        memo := make(map[string][]byte)
1✔
1024
        for k, v := range input {
2✔
1025
                memoBytes, err := json.Marshal(v)
1✔
1026
                if err != nil {
1✔
1027
                        return nil, fmt.Errorf("encode workflow memo error: %v", err.Error())
×
1028
                }
×
1029
                memo[k] = memoBytes
1✔
1030
        }
1031
        return &types.Memo{Fields: memo}, nil
1✔
1032
}
1033

1034
func serializeSearchAttributes(input map[string]interface{}) (*types.SearchAttributes, error) {
×
1035
        if input == nil {
×
1036
                return nil, nil
×
1037
        }
×
1038

1039
        attr := make(map[string][]byte)
×
1040
        for k, v := range input {
×
1041
                attrBytes, err := json.Marshal(v)
×
1042
                if err != nil {
×
1043
                        return nil, fmt.Errorf("encode search attribute [%s] error: %v", k, err)
×
1044
                }
×
1045
                attr[k] = attrBytes
×
1046
        }
1047
        return &types.SearchAttributes{IndexedFields: attr}, nil
×
1048
}
1049

1050
// parseIntMultiRange will parse string of multiple integer ranges separates by commas.
1051
// Single range can be an integer or inclusive range separated by dash.
1052
// The result is a sorted set union of integers.
1053
// Example: "3,8-8,5-6" -> [3,4,5,8]
1054
func parseIntMultiRange(s string) ([]int, error) {
9✔
1055
        set := map[int]struct{}{}
9✔
1056
        ranges := strings.Split(strings.TrimSpace(s), ",")
9✔
1057
        for _, r := range ranges {
21✔
1058
                r = strings.TrimSpace(r)
12✔
1059
                if len(r) == 0 {
14✔
1060
                        continue
2✔
1061
                }
1062
                parts := strings.Split(r, "-")
10✔
1063
                switch len(parts) {
10✔
1064
                case 1:
3✔
1065
                        i, err := strconv.Atoi(strings.TrimSpace(parts[0]))
3✔
1066
                        if err != nil {
4✔
1067
                                return nil, fmt.Errorf("single number %q: %v", r, err)
1✔
1068
                        }
1✔
1069
                        set[i] = struct{}{}
2✔
1070
                case 2:
6✔
1071
                        lower, err := strconv.Atoi(strings.TrimSpace(parts[0]))
6✔
1072
                        if err != nil {
7✔
1073
                                return nil, fmt.Errorf("lower range of %q: %v", r, err)
1✔
1074
                        }
1✔
1075
                        upper, err := strconv.Atoi(strings.TrimSpace(parts[1]))
5✔
1076
                        if err != nil {
6✔
1077
                                return nil, fmt.Errorf("upper range of %q: %v", r, err)
1✔
1078
                        }
1✔
1079
                        for i := lower; i <= upper; i++ {
16✔
1080
                                set[i] = struct{}{}
12✔
1081
                        }
12✔
1082
                default:
1✔
1083
                        return nil, fmt.Errorf("invalid range %q", r)
1✔
1084
                }
1085
        }
1086

1087
        result := []int{}
5✔
1088
        for i := range set {
17✔
1089
                result = append(result, i)
12✔
1090
        }
12✔
1091
        sort.Ints(result)
5✔
1092
        return result, nil
5✔
1093
}
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