Skip to content

Commit 47d4011

Browse files
authored
Dev server compat issues in CLI rewrite (#535)
1 parent 4528734 commit 47d4011

7 files changed

Lines changed: 70 additions & 30 deletions

File tree

temporalcli/commands.gen.go

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ type TemporalCommand struct {
2121
Env string
2222
EnvFile string
2323
LogLevel StringEnum
24-
LogFormat StringEnum
24+
LogFormat string
2525
Output StringEnum
2626
TimeFormat StringEnum
2727
Color StringEnum
@@ -46,11 +46,10 @@ func NewTemporalCommand(cctx *CommandContext) *TemporalCommand {
4646
cctx.BindFlagEnvVar(s.Command.PersistentFlags().Lookup("env"), "TEMPORAL_ENV")
4747
s.Command.PersistentFlags().StringVar(&s.EnvFile, "env-file", "", "File to read all environments (defaults to `$HOME/.config/temporalio/temporal.yaml`).")
4848
s.LogLevel = NewStringEnum([]string{"debug", "info", "warn", "error", "never"}, "info")
49-
s.Command.PersistentFlags().Var(&s.LogLevel, "log-level", "Log level. Accepted values: debug, info, warn, error, never.")
50-
s.LogFormat = NewStringEnum([]string{"text", "json"}, "text")
51-
s.Command.PersistentFlags().Var(&s.LogFormat, "log-format", "Log format. Accepted values: text, json.")
52-
s.Output = NewStringEnum([]string{"text", "json", "jsonl"}, "text")
53-
s.Command.PersistentFlags().VarP(&s.Output, "output", "o", "Data output format. Accepted values: text, json, jsonl.")
49+
s.Command.PersistentFlags().Var(&s.LogLevel, "log-level", "Log level. Default is \"info\" for most commands and \"warn\" for `server start-dev`. Accepted values: debug, info, warn, error, never.")
50+
s.Command.PersistentFlags().StringVar(&s.LogFormat, "log-format", "", "Log format. Options are \"text\" and \"json\". Default is \"text\".")
51+
s.Output = NewStringEnum([]string{"text", "json", "jsonl", "none"}, "text")
52+
s.Command.PersistentFlags().VarP(&s.Output, "output", "o", "Data output format. Note, this does not affect logging. Accepted values: text, json, jsonl, none.")
5453
s.TimeFormat = NewStringEnum([]string{"relative", "iso", "raw"}, "relative")
5554
s.Command.PersistentFlags().Var(&s.TimeFormat, "time-format", "Time format. Accepted values: relative, iso, raw.")
5655
s.Color = NewStringEnum([]string{"always", "never", "auto"}, "auto")
@@ -1243,7 +1242,6 @@ type TemporalServerStartDevCommand struct {
12431242
SqlitePragma []string
12441243
DynamicConfigValue []string
12451244
LogConfig bool
1246-
LogLevelServer StringEnum
12471245
}
12481246

12491247
func NewTemporalServerStartDevCommand(cctx *CommandContext, parent *TemporalServerCommand) *TemporalServerStartDevCommand {
@@ -1272,8 +1270,6 @@ func NewTemporalServerStartDevCommand(cctx *CommandContext, parent *TemporalServ
12721270
s.Command.Flags().StringArrayVar(&s.SqlitePragma, "sqlite-pragma", nil, "Specify SQLite pragma statements in pragma=value format.")
12731271
s.Command.Flags().StringArrayVar(&s.DynamicConfigValue, "dynamic-config-value", nil, "Dynamic config value, as KEY=JSON_VALUE (string values need quotes).")
12741272
s.Command.Flags().BoolVar(&s.LogConfig, "log-config", false, "Log the server config being used to stderr.")
1275-
s.LogLevelServer = NewStringEnum([]string{"debug", "info", "warn", "error", "never"}, "warn")
1276-
s.Command.Flags().Var(&s.LogLevelServer, "log-level-server", "Log level for the server only. Accepted values: debug, info, warn, error, never.")
12771273
s.Command.Run = func(c *cobra.Command, args []string) {
12781274
if err := s.run(cctx, args); err != nil {
12791275
cctx.Options.Fail(err)

temporalcli/commands.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -390,8 +390,9 @@ func (c *TemporalCommand) preRun(cctx *CommandContext) error {
390390
return fmt.Errorf("invalid log level %q: %w", c.LogLevel.Value, err)
391391
}
392392
var handler slog.Handler
393-
switch c.LogFormat.Value {
394-
case "text":
393+
switch c.LogFormat {
394+
// We have a "pretty" alias for compatibility
395+
case "", "text", "pretty":
395396
handler = slog.NewTextHandler(cctx.Options.Stderr, &slog.HandlerOptions{
396397
Level: level,
397398
// Remove the TZ
@@ -405,7 +406,7 @@ func (c *TemporalCommand) preRun(cctx *CommandContext) error {
405406
case "json":
406407
handler = slog.NewJSONHandler(cctx.Options.Stderr, &slog.HandlerOptions{Level: level})
407408
default:
408-
return fmt.Errorf("invalid log format %q", c.LogFormat.Value)
409+
return fmt.Errorf("invalid log format %q", c.LogFormat)
409410
}
410411
cctx.Logger = slog.New(handler)
411412
}
@@ -419,8 +420,13 @@ func (c *TemporalCommand) preRun(cctx *CommandContext) error {
419420
jsonIndent = " "
420421
}
421422
if cctx.Printer == nil {
423+
printerOutput := cctx.Options.Stdout
424+
// Disable printer by making writer noop if "none" chosen
425+
if c.Output.Value == "none" {
426+
printerOutput = nopWriter{}
427+
}
422428
cctx.Printer = &printer.Printer{
423-
Output: cctx.Options.Stdout,
429+
Output: printerOutput,
424430
JSON: cctx.JSONOutput,
425431
JSONIndent: jsonIndent,
426432
JSONPayloadShorthand: !c.NoJsonShorthandPayloads,
@@ -492,3 +498,7 @@ func timestampToTime(t *timestamppb.Timestamp) time.Time {
492498
}
493499
return t.AsTime()
494500
}
501+
502+
type nopWriter struct{}
503+
504+
func (nopWriter) Write(b []byte) (int, error) { return len(b), nil }

temporalcli/commands.schedule.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,9 +301,9 @@ func (c *TemporalScheduleCreateCommand) run(cctx *CommandContext, args []string)
301301
return err
302302
} else if opts.Overlap, err = enumspb.ScheduleOverlapPolicyFromString(c.OverlapPolicy.Value); err != nil {
303303
return err
304-
} else if opts.Memo, err = stringKeysJSONValues(c.ScheduleMemo); err != nil {
304+
} else if opts.Memo, err = stringKeysJSONValues(c.ScheduleMemo, false); err != nil {
305305
return fmt.Errorf("invalid memo values: %w", err)
306-
} else if opts.SearchAttributes, err = stringKeysJSONValues(c.ScheduleSearchAttribute); err != nil {
306+
} else if opts.SearchAttributes, err = stringKeysJSONValues(c.ScheduleSearchAttribute, false); err != nil {
307307
return fmt.Errorf("invalid search attribute values: %w", err)
308308
}
309309

temporalcli/commands.server.go

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package temporalcli
22

33
import (
4+
"encoding/json"
45
"fmt"
56

67
"github.com/google/uuid"
@@ -26,10 +27,18 @@ func (t *TemporalServerStartDevCommand) run(cctx *CommandContext, args []string)
2627
CurrentClusterName: "active",
2728
InitialFailoverVersion: 1,
2829
}
29-
if t.LogLevelServer.Value == "never" {
30+
// Set the log level value of the server to the overall log level given to the
31+
// CLI. But if it is "never" we have to do a special value, and if it was
32+
// never changed, we have to use the default of "warn" instead of the CLI
33+
// default of "info" since server is noisier.
34+
logLevel := t.Parent.Parent.LogLevel.Value
35+
if !t.Parent.Parent.LogLevel.ChangedFromDefault {
36+
logLevel = "warn"
37+
}
38+
if logLevel == "never" {
3039
opts.LogLevel = 100
31-
} else if err := opts.LogLevel.UnmarshalText([]byte(t.LogLevelServer.Value)); err != nil {
32-
return fmt.Errorf("invalid log level %q: %w", t.LogLevelServer.Value, err)
40+
} else if err := opts.LogLevel.UnmarshalText([]byte(logLevel)); err != nil {
41+
return fmt.Errorf("invalid log level %q: %w", logLevel, err)
3342
}
3443
// Setup UI
3544
if !t.Headless {
@@ -46,9 +55,25 @@ func (t *TemporalServerStartDevCommand) run(cctx *CommandContext, args []string)
4655
var err error
4756
if opts.SqlitePragmas, err = stringKeysValues(t.SqlitePragma); err != nil {
4857
return fmt.Errorf("invalid pragma: %w", err)
49-
} else if opts.DynamicConfigValues, err = stringKeysJSONValues(t.DynamicConfigValue); err != nil {
58+
} else if opts.DynamicConfigValues, err = stringKeysJSONValues(t.DynamicConfigValue, true); err != nil {
5059
return fmt.Errorf("invalid dynamic config values: %w", err)
5160
}
61+
// We have to convert all dynamic config values that JSON number to int if we
62+
// can because server dynamic config expecting int won't work with the default
63+
// float JSON unmarshal uses
64+
for k, v := range opts.DynamicConfigValues {
65+
if num, ok := v.(json.Number); ok {
66+
if newV, err := num.Int64(); err == nil {
67+
// Dynamic config only accepts int type, not int32 nor int64
68+
opts.DynamicConfigValues[k] = int(newV)
69+
} else if newV, err := num.Float64(); err == nil {
70+
opts.DynamicConfigValues[k] = newV
71+
} else {
72+
return fmt.Errorf("invalid JSON value for key %q", k)
73+
}
74+
}
75+
}
76+
5277
// If not using DB file, set persistent cluster ID
5378
if t.DbFilename == "" {
5479
opts.ClusterID = persistentClusterID()

temporalcli/commands.workflow_exec.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,13 +265,13 @@ func buildStartOptions(sw *SharedWorkflowStartOptions, w *WorkflowStartOptions)
265265
}
266266
if len(sw.Memo) > 0 {
267267
var err error
268-
if o.Memo, err = stringKeysJSONValues(sw.Memo); err != nil {
268+
if o.Memo, err = stringKeysJSONValues(sw.Memo, false); err != nil {
269269
return o, fmt.Errorf("invalid memo values: %w", err)
270270
}
271271
}
272272
if len(sw.SearchAttribute) > 0 {
273273
var err error
274-
if o.SearchAttributes, err = stringKeysJSONValues(sw.SearchAttribute); err != nil {
274+
if o.SearchAttributes, err = stringKeysJSONValues(sw.SearchAttribute, false); err != nil {
275275
return o, fmt.Errorf("invalid search attribute values: %w", err)
276276
}
277277
}

temporalcli/commandsmd/commands.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,11 @@ This document has a specific structure used by a parser. Here are the rules:
5151

5252
* `--env` (string) - Environment to read environment-specific flags from. Default: default. Env: TEMPORAL_ENV.
5353
* `--env-file` (string) - File to read all environments (defaults to `$HOME/.config/temporalio/temporal.yaml`).
54-
* `--log-level` (string-enum) - Log level. Options: debug, info, warn, error, never. Default: info.
55-
* `--log-format` (string-enum) - Log format. Options: text, json. Default: text.
56-
* `--output`, `-o` (string-enum) - Data output format. Options: text, json, jsonl. Default: text.
54+
* `--log-level` (string-enum) - Log level. Default is "info" for most commands and "warn" for `server start-dev`.
55+
Options: debug, info, warn, error, never. Default: info.
56+
* `--log-format` (string) - Log format. Options are "text" and "json". Default is "text".
57+
* `--output`, `-o` (string-enum) - Data output format. Note, this does not affect logging. Options: text, json, jsonl,
58+
none. Default: text.
5759
* `--time-format` (string-enum) - Time format. Options: relative, iso, raw. Default: relative.
5860
* `--color` (string-enum) - Set coloring. Options: always, never, auto. Default: auto.
5961
* `--no-json-shorthand-payloads` (bool) - Always show all payloads as raw payloads even if they are JSON.
@@ -594,8 +596,6 @@ To persist Workflows across runs, use:
594596
* `--sqlite-pragma` (string[]) - Specify SQLite pragma statements in pragma=value format.
595597
* `--dynamic-config-value` (string[]) - Dynamic config value, as KEY=JSON_VALUE (string values need quotes).
596598
* `--log-config` (bool) - Log the server config being used to stderr.
597-
* `--log-level-server` (string-enum) - Log level for the server only. Options: debug, info, warn, error, never. Default:
598-
warn.
599599

600600
### temporal task-queue: Manage Task Queues.
601601

temporalcli/strings.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
package temporalcli
22

33
import (
4+
"bytes"
45
"encoding/json"
56
"fmt"
67
"sort"
78
"strings"
89
)
910

1011
type StringEnum struct {
11-
Allowed []string
12-
Value string
12+
Allowed []string
13+
Value string
14+
ChangedFromDefault bool
1315
}
1416

1517
func NewStringEnum(allowed []string, value string) StringEnum {
@@ -22,6 +24,7 @@ func (s *StringEnum) Set(p string) error {
2224
for _, allowed := range s.Allowed {
2325
if p == allowed {
2426
s.Value = p
27+
s.ChangedFromDefault = true
2528
return nil
2629
}
2730
}
@@ -60,7 +63,7 @@ func stringKeysValues(s []string) (map[string]string, error) {
6063
return ret, nil
6164
}
6265

63-
func stringKeysJSONValues(s []string) (map[string]any, error) {
66+
func stringKeysJSONValues(s []string, useJSONNumber bool) (map[string]any, error) {
6467
if len(s) == 0 {
6568
return nil, nil
6669
}
@@ -70,9 +73,15 @@ func stringKeysJSONValues(s []string) (map[string]any, error) {
7073
if len(pieces) != 2 {
7174
return nil, fmt.Errorf("missing expected '=' in %q", item)
7275
}
76+
dec := json.NewDecoder(bytes.NewReader([]byte(pieces[1])))
77+
if useJSONNumber {
78+
dec.UseNumber()
79+
}
7380
var v any
74-
if err := json.Unmarshal([]byte(pieces[1]), &v); err != nil {
81+
if err := dec.Decode(&v); err != nil {
7582
return nil, fmt.Errorf("invalid JSON value for key %q: %w", pieces[0], err)
83+
} else if dec.InputOffset() != int64(len(pieces[1])) {
84+
return nil, fmt.Errorf("invalid JSON value for key %q: unexpected trailing data", pieces[0])
7685
}
7786
ret[pieces[0]] = v
7887
}

0 commit comments

Comments
 (0)