Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions internal/temporalcli/commands.config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package temporalcli

import (
"bytes"
"errors"
"fmt"
"os"
"path/filepath"
Expand All @@ -14,6 +16,10 @@ import (
)

func (c *TemporalConfigDeleteCommand) run(cctx *CommandContext, _ []string) error {
if c.Prop == updateCheckConfigProp {
configFile, _ := resolveConfigFile(cctx.Options)
return setUpdateCheckEnabled(configFile, false)
}
// Load config
profileName := envConfigProfileName(cctx)
conf, confProfile, err := loadEnvConfigProfile(cctx, profileName, true)
Expand Down Expand Up @@ -58,6 +64,21 @@ func (c *TemporalConfigDeleteProfileCommand) run(cctx *CommandContext, _ []strin
}

func (c *TemporalConfigGetCommand) run(cctx *CommandContext, _ []string) error {
if c.Prop == updateCheckConfigProp {
configFile, _ := resolveConfigFile(cctx.Options)
_, state, err := loadUpdateCheckState(configFile)
if err != nil {
return err
}
type prop struct {
Property string `json:"property"`
Value any `json:"value"`
}
return cctx.Printer.PrintStructured(
prop{Property: c.Prop, Value: state.Enabled},
printer.StructuredOptions{Table: &printer.TableOptions{}},
)
}
// Load config profile
profileName := envConfigProfileName(cctx)
conf, confProfile, err := loadEnvConfigProfile(cctx, profileName, true)
Expand Down Expand Up @@ -166,6 +187,13 @@ func (c *TemporalConfigListCommand) run(cctx *CommandContext, _ []string) error
}

func (c *TemporalConfigSetCommand) run(cctx *CommandContext, _ []string) error {
if c.Prop == updateCheckConfigProp {
if c.Value != "true" && c.Value != "false" {
return fmt.Errorf("must be 'true' or 'false' to set this property")
}
configFile, _ := resolveConfigFile(cctx.Options)
return setUpdateCheckEnabled(configFile, c.Value == "true")
}
// Load config
conf, confProfile, err := loadEnvConfigProfile(cctx, envConfigProfileName(cctx), false)
if err != nil {
Expand Down Expand Up @@ -328,6 +356,27 @@ func writeEnvConfigFile(cctx *CommandContext, conf *envconfig.ClientConfig) erro
if err != nil {
return fmt.Errorf("failed building TOML: %w", err)
}
// The SDK config type only knows about connection profiles. Preserve CLI-owned
// top-level configuration when rewriting those profiles.
if existing, readErr := os.ReadFile(configFile); readErr == nil {
var existingRaw, generatedRaw map[string]any
if _, decodeErr := toml.Decode(string(existing), &existingRaw); decodeErr != nil {
return fmt.Errorf("failed parsing existing config: %w", decodeErr)
}
if _, decodeErr := toml.Decode(string(b), &generatedRaw); decodeErr != nil {
return fmt.Errorf("failed parsing generated config: %w", decodeErr)
}
if cliConfig, ok := existingRaw["cli"]; ok {
generatedRaw["cli"] = cliConfig
}
var buf bytes.Buffer
if encodeErr := toml.NewEncoder(&buf).Encode(generatedRaw); encodeErr != nil {
return fmt.Errorf("failed preserving CLI config: %w", encodeErr)
}
b = buf.Bytes()
} else if !errors.Is(readErr, os.ErrNotExist) {
return fmt.Errorf("failed reading existing config: %w", readErr)
}

// Write to file, making dirs as needed
if err := os.MkdirAll(filepath.Dir(configFile), 0700); err != nil {
Expand Down
28 changes: 28 additions & 0 deletions internal/temporalcli/commands.config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,34 @@ disable_host_verification = true`))
}
}

func TestConfig_UpdateCheckOptIn(t *testing.T) {
h := NewCommandHarness(t)
configFile := t.TempDir() + "/temporal.toml"
h.Options.EnvLookup = EnvLookupMap{"TEMPORAL_CONFIG_FILE": configFile}

res := h.Execute("config", "set", "--prop", "cli.update_check.enabled", "--value", "true")
h.NoError(res.Err)

res = h.Execute("config", "get", "--prop", "cli.update_check.enabled", "-o", "json")
h.NoError(res.Err)
h.JSONEq(`{"property":"cli.update_check.enabled","value":true}`, res.Stdout.String())

res = h.Execute("config", "set", "--prop", "namespace", "--value", "testing")
h.NoError(res.Err)
var config map[string]any
_, err := toml.DecodeFile(configFile, &config)
h.NoError(err)
cli := config["cli"].(map[string]any)
updateCheck := cli["update_check"].(map[string]any)
h.Equal(true, updateCheck["enabled"])

res = h.Execute("config", "delete", "--prop", "cli.update_check.enabled")
h.NoError(res.Err)
res = h.Execute("config", "get", "--prop", "cli.update_check.enabled", "-o", "json")
h.NoError(res.Err)
h.JSONEq(`{"property":"cli.update_check.enabled","value":false}`, res.Stdout.String())
}

func TestConfig_Get_NoTLSWhenUnconfigured(t *testing.T) {
// Regression test for #1077: `config get` (table output) must not display
// "tls true" for a profile that has no TLS configuration.
Expand Down
4 changes: 2 additions & 2 deletions internal/temporalcli/commands.gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -1142,9 +1142,9 @@ func NewTemporalConfigCommand(cctx *CommandContext, parent *TemporalCommand) *Te
s.Command.Use = "config"
s.Command.Short = "Manage config files (EXPERIMENTAL)"
if hasHighlighting {
s.Command.Long = "Config files are TOML files that contain profiles, with each profile\ncontaining configuration for connecting to Temporal.\n\n\x1b[1mtemporal config set \\\n --prop address \\\n --value us-west-2.aws.api.temporal.io:7233\x1b[0m\n\nThe default config file path is \x1b[1m$CONFIG_PATH/temporalio/temporal.toml\x1b[0m where\n\x1b[1m$CONFIG_PATH\x1b[0m is defined as \x1b[1m$HOME/.config\x1b[0m on Unix,\n\x1b[1m$HOME/Library/Application Support\x1b[0m on macOS, and \x1b[1m%AppData%\x1b[0m on Windows.\nThis can be overridden with the \x1b[1mTEMPORAL_CONFIG_FILE\x1b[0m environment\nvariable or \x1b[1m--config-file\x1b[0m.\n\nThe default profile is \x1b[1mdefault\x1b[0m. This can be overridden with the\n\x1b[1mTEMPORAL_PROFILE\x1b[0m environment variable or \x1b[1m--profile\x1b[0m."
s.Command.Long = "Config files are TOML files that contain profiles, with each profile\ncontaining configuration for connecting to Temporal.\n\n\x1b[1mtemporal config set \\\n --prop address \\\n --value us-west-2.aws.api.temporal.io:7233\x1b[0m\n\nThe default config file path is \x1b[1m$CONFIG_PATH/temporalio/temporal.toml\x1b[0m where\n\x1b[1m$CONFIG_PATH\x1b[0m is defined as \x1b[1m$HOME/.config\x1b[0m on Unix,\n\x1b[1m$HOME/Library/Application Support\x1b[0m on macOS, and \x1b[1m%AppData%\x1b[0m on Windows.\nThis can be overridden with the \x1b[1mTEMPORAL_CONFIG_FILE\x1b[0m environment\nvariable or \x1b[1m--config-file\x1b[0m.\n\nThe default profile is \x1b[1mdefault\x1b[0m. This can be overridden with the\n\x1b[1mTEMPORAL_PROFILE\x1b[0m environment variable or \x1b[1m--profile\x1b[0m.\n\nTo opt in to periodic checks for new Temporal CLI releases, set the\nglobal CLI property:\n\n\x1b[1mtemporal config set \\\n --prop cli.update_check.enabled \\\n --value true\x1b[0m"
} else {
s.Command.Long = "Config files are TOML files that contain profiles, with each profile\ncontaining configuration for connecting to Temporal.\n\n```\ntemporal config set \\\n --prop address \\\n --value us-west-2.aws.api.temporal.io:7233\n```\n\nThe default config file path is `$CONFIG_PATH/temporalio/temporal.toml` where\n`$CONFIG_PATH` is defined as `$HOME/.config` on Unix,\n`$HOME/Library/Application Support` on macOS, and `%AppData%` on Windows.\nThis can be overridden with the `TEMPORAL_CONFIG_FILE` environment\nvariable or `--config-file`.\n\nThe default profile is `default`. This can be overridden with the\n`TEMPORAL_PROFILE` environment variable or `--profile`."
s.Command.Long = "Config files are TOML files that contain profiles, with each profile\ncontaining configuration for connecting to Temporal.\n\n```\ntemporal config set \\\n --prop address \\\n --value us-west-2.aws.api.temporal.io:7233\n```\n\nThe default config file path is `$CONFIG_PATH/temporalio/temporal.toml` where\n`$CONFIG_PATH` is defined as `$HOME/.config` on Unix,\n`$HOME/Library/Application Support` on macOS, and `%AppData%` on Windows.\nThis can be overridden with the `TEMPORAL_CONFIG_FILE` environment\nvariable or `--config-file`.\n\nThe default profile is `default`. This can be overridden with the\n`TEMPORAL_PROFILE` environment variable or `--profile`.\n\nTo opt in to periodic checks for new Temporal CLI releases, set the\nglobal CLI property:\n\n```\ntemporal config set \\\n --prop cli.update_check.enabled \\\n --value true\n```"
}
s.Command.Args = cobra.NoArgs
s.Command.AddCommand(&NewTemporalConfigDeleteCommand(cctx, &s).Command)
Expand Down
4 changes: 4 additions & 0 deletions internal/temporalcli/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,10 @@ func Execute(ctx context.Context, options CommandOptions) {
defer cancel()

if err == nil {
// Update checks are config-controlled, best effort, and intentionally run
// outside Cobra hooks so they also apply to the built-in --version path.
runUpdateCheck(cctx)

cmd := NewTemporalCommand(cctx)
cmd.Command.SetArgs(cctx.Options.Args)
cmd.Command.SetOut(cctx.Options.Stdout)
Expand Down
9 changes: 9 additions & 0 deletions internal/temporalcli/commands.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,15 @@ commands:

The default profile is `default`. This can be overridden with the
`TEMPORAL_PROFILE` environment variable or `--profile`.

To opt in to periodic checks for new Temporal CLI releases, set the
global CLI property:

```
temporal config set \
--prop cli.update_check.enabled \
--value true
```
docs:
description-header: >-
Temporal CLI 'config' commands allow the getting, setting, deleting, and
Expand Down
Loading
Loading