Skip to content
Open
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
6 changes: 3 additions & 3 deletions internal/temporalcli/commands.gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -2152,12 +2152,12 @@ func NewTemporalOperatorNamespaceUpdateCommand(cctx *CommandContext, parent *Tem
s.Command.Use = "update [flags]"
s.Command.Short = "Update a Namespace"
if hasHighlighting {
s.Command.Long = "Update a Namespace using properties you specify.\n\n\x1b[1mtemporal operator namespace update [options]\x1b[0m\n\nAssign a Namespace's active Cluster (Service):\n\n\x1b[1mtemporal operator namespace update \\\n --namespace YourNamespaceName \\\n --active-cluster NewActiveCluster\x1b[0m\n\nPromote a Namespace for multi-region data replication:\n\n\x1b[1mtemporal operator namespace update \\\n --namespace YourNamespaceName \\\n --promote-global\x1b[0m\n\nYou may update archives that were previously enabled or disabled. Note: URI\nvalues for archival states can't be changed once enabled.\n\n\x1b[1mtemporal operator namespace update \\\n --namespace YourNamespaceName \\\n --history-archival-state enabled \\\n --visibility-archival-state disabled\x1b[0m"
s.Command.Long = "Update a Namespace using properties you specify.\n\n\x1b[1mtemporal operator namespace update [options]\x1b[0m\n\nAssign a Namespace's active Cluster (Service):\n\n\x1b[1mtemporal operator namespace update \\\n --namespace YourNamespaceName \\\n --active-cluster NewActiveCluster\x1b[0m\n\nActive Cluster changes cannot be combined with other Namespace updates.\nUpdate Namespace configuration, including the replication Cluster list,\nbefore changing the active Cluster in a separate command.\n\nPromote a Namespace for multi-region data replication:\n\n\x1b[1mtemporal operator namespace update \\\n --namespace YourNamespaceName \\\n --promote-global\x1b[0m\n\nYou may update archives that were previously enabled or disabled. Note: URI\nvalues for archival states can't be changed once enabled.\n\n\x1b[1mtemporal operator namespace update \\\n --namespace YourNamespaceName \\\n --history-archival-state enabled \\\n --visibility-archival-state disabled\x1b[0m"
} else {
s.Command.Long = "Update a Namespace using properties you specify.\n\n```\ntemporal operator namespace update [options]\n```\n\nAssign a Namespace's active Cluster (Service):\n\n```\ntemporal operator namespace update \\\n --namespace YourNamespaceName \\\n --active-cluster NewActiveCluster\n```\n\nPromote a Namespace for multi-region data replication:\n\n```\ntemporal operator namespace update \\\n --namespace YourNamespaceName \\\n --promote-global\n```\n\nYou may update archives that were previously enabled or disabled. Note: URI\nvalues for archival states can't be changed once enabled.\n\n```\ntemporal operator namespace update \\\n --namespace YourNamespaceName \\\n --history-archival-state enabled \\\n --visibility-archival-state disabled\n```"
s.Command.Long = "Update a Namespace using properties you specify.\n\n```\ntemporal operator namespace update [options]\n```\n\nAssign a Namespace's active Cluster (Service):\n\n```\ntemporal operator namespace update \\\n --namespace YourNamespaceName \\\n --active-cluster NewActiveCluster\n```\n\nActive Cluster changes cannot be combined with other Namespace updates.\nUpdate Namespace configuration, including the replication Cluster list,\nbefore changing the active Cluster in a separate command.\n\nPromote a Namespace for multi-region data replication:\n\n```\ntemporal operator namespace update \\\n --namespace YourNamespaceName \\\n --promote-global\n```\n\nYou may update archives that were previously enabled or disabled. Note: URI\nvalues for archival states can't be changed once enabled.\n\n```\ntemporal operator namespace update \\\n --namespace YourNamespaceName \\\n --history-archival-state enabled \\\n --visibility-archival-state disabled\n```"
}
s.Command.Args = cobra.MaximumNArgs(1)
s.Command.Flags().StringVar(&s.ActiveCluster, "active-cluster", "", "Active Cluster (Service) name.")
s.Command.Flags().StringVar(&s.ActiveCluster, "active-cluster", "", "Active Cluster (Service) name. Cannot be combined with options that modify Namespace configuration.")
s.Command.Flags().StringArrayVar(&s.Cluster, "cluster", nil, "Cluster (Service) names.")
s.Command.Flags().StringArrayVar(&s.Data, "data", nil, "Namespace data as `KEY=VALUE` pairs. Keys must be identifiers, and values must be JSON values. For example: `YourKey={\"your\": \"value\"}` Can be passed multiple times.")
s.Command.Flags().StringVar(&s.Description, "description", "", "Namespace description.")
Expand Down
47 changes: 42 additions & 5 deletions internal/temporalcli/commands.operator_namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package temporalcli

import (
"fmt"
"strings"

"github.com/fatih/color"
"github.com/temporalio/cli/internal/printer"
Expand Down Expand Up @@ -193,6 +194,10 @@ func (c *TemporalOperatorNamespaceListCommand) run(cctx *CommandContext, args []
}

func (c *TemporalOperatorNamespaceUpdateCommand) run(cctx *CommandContext, args []string) error {
if err := c.validateFlags(); err != nil {
return err
}

cl, err := dialClient(cctx, &c.Parent.Parent.ClientOptions)
if err != nil {
return err
Expand All @@ -206,10 +211,6 @@ func (c *TemporalOperatorNamespaceUpdateCommand) run(cctx *CommandContext, args

var updateRequest *workflowservice.UpdateNamespaceRequest

if c.PromoteGlobal && len(c.ActiveCluster) > 0 {
return fmt.Errorf("both --promote-global and --active-cluster flags cannot be set together")
}

if c.PromoteGlobal {
cctx.Printer.Printlnf("Will promote local namespace to global namespace for:%s, other flag will be omitted. "+
"If it is already global namespace, this will be no-op.\n", nsName)
Expand All @@ -218,7 +219,7 @@ func (c *TemporalOperatorNamespaceUpdateCommand) run(cctx *CommandContext, args
PromoteNamespace: true,
}
} else if len(c.ActiveCluster) > 0 {
cctx.Printer.Printlnf("Will set active cluster name to: %s, other flag will be omitted.\n", c.ActiveCluster)
cctx.Printer.Printlnf("Will set active cluster name to: %s.\n", c.ActiveCluster)
replicationConfig := &replication.NamespaceReplicationConfig{
ActiveClusterName: c.ActiveCluster,
}
Expand Down Expand Up @@ -304,6 +305,42 @@ func (c *TemporalOperatorNamespaceUpdateCommand) run(cctx *CommandContext, args
return nil
}

func (c *TemporalOperatorNamespaceUpdateCommand) validateFlags() error {
if len(c.ActiveCluster) == 0 {
return nil
}
if c.PromoteGlobal {
return fmt.Errorf("both --promote-global and --active-cluster flags cannot be set together")
}

incompatibleFlagNames := [...]string{
"cluster",
"data",
"description",
"email",
"history-archival-state",
"history-uri",
"replication-state",
"retention",
"visibility-archival-state",
"visibility-uri",
}
var incompatibleFlags []string
for _, flag := range incompatibleFlagNames {
if c.Command.Flags().Changed(flag) {
incompatibleFlags = append(incompatibleFlags, "--"+flag)
}
}
if len(incompatibleFlags) == 0 {
return nil
}

return fmt.Errorf(
"--active-cluster cannot be combined with %s; update namespace configuration before changing the active cluster",
strings.Join(incompatibleFlags, ", "),
)
}

func printNamespaceDescriptions(cctx *CommandContext, responses ...*workflowservice.DescribeNamespaceResponse) error {
namespaces := make([]map[string]any, len(responses))
for i, resp := range responses {
Expand Down
79 changes: 79 additions & 0 deletions internal/temporalcli/commands.operator_namespace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,71 @@ package temporalcli_test
import (
"fmt"
"os"
"testing"
"time"

"github.com/stretchr/testify/require"
"github.com/temporalio/cli/internal/temporalcli"
"go.temporal.io/api/enums/v1"
"go.temporal.io/api/workflowservice/v1"
)

func TestNamespaceUpdate_ActiveClusterRejectsOtherUpdates(t *testing.T) {
tests := []struct {
name string
args []string
wantError string
}{
{name: "cluster", args: []string{"--cluster", "cluster-a"}, wantError: "--active-cluster cannot be combined with --cluster"},
{name: "data", args: []string{"--data", "key=value"}, wantError: "--active-cluster cannot be combined with --data"},
{name: "description", args: []string{"--description", "description"}, wantError: "--active-cluster cannot be combined with --description"},
{name: "email", args: []string{"--email", "owner@example.com"}, wantError: "--active-cluster cannot be combined with --email"},
{name: "promote global", args: []string{"--promote-global"}, wantError: "both --promote-global and --active-cluster flags cannot be set together"},
{name: "history archival state", args: []string{"--history-archival-state", "enabled"}, wantError: "--active-cluster cannot be combined with --history-archival-state"},
{name: "history URI", args: []string{"--history-uri", "file:///tmp/history"}, wantError: "--active-cluster cannot be combined with --history-uri"},
{name: "replication state", args: []string{"--replication-state", "normal"}, wantError: "--active-cluster cannot be combined with --replication-state"},
{name: "retention", args: []string{"--retention", "24h"}, wantError: "--active-cluster cannot be combined with --retention"},
{name: "visibility archival state", args: []string{"--visibility-archival-state", "enabled"}, wantError: "--active-cluster cannot be combined with --visibility-archival-state"},
{name: "visibility URI", args: []string{"--visibility-uri", "file:///tmp/visibility"}, wantError: "--active-cluster cannot be combined with --visibility-uri"},
}

for _, output := range []string{"text", "json"} {
t.Run(output, func(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
h := NewCommandHarness(t)
args := []string{
"operator", "namespace", "update",
"--address", "127.0.0.1:1",
"--namespace", "test-namespace",
"--active-cluster", "cluster-b",
"--output", output,
}
res := h.Execute(append(args, test.args...)...)

require.ErrorContains(t, res.Err, test.wantError)
require.Empty(t, res.Stdout.String())
})
}
})
}
}

func TestNamespaceUpdate_ActiveClusterReportsAllConflicts(t *testing.T) {
h := NewCommandHarness(t)
res := h.Execute(
"operator", "namespace", "update",
"--address", "127.0.0.1:1",
"--namespace", "test-namespace",
"--active-cluster", "cluster-b",
"--cluster", "cluster-a",
"--description", "description",
)

require.ErrorContains(t, res.Err, "--active-cluster cannot be combined with --cluster, --description")
require.Empty(t, res.Stdout.String())
}

func (s *SharedServerSuite) TestOperator_NamespaceCreateListAndDescribe() {
nsName := "test_namespace"
res := s.Execute(
Expand Down Expand Up @@ -107,6 +165,27 @@ func (s *SharedServerSuite) TestNamespaceUpdate() {
s.Equal("v3", describeResp.NamespaceInfo.Data["k3"])
}

func (s *SharedServerSuite) TestNamespaceUpdate_ActiveClusterAlone() {
nsName := "test-namespace-update-active-cluster"
res := s.Execute(
"operator", "namespace", "create",
"--address", s.Address(),
"--namespace", nsName,
"--global",
"--active-cluster", "active",
"--cluster", "active",
)
require.NoError(s.T(), res.Err)

res = s.Execute(
"operator", "namespace", "update",
"--address", s.Address(),
"--namespace", nsName,
"--active-cluster", "active",
)
require.NoError(s.T(), res.Err)
}

func (s *SharedServerSuite) TestNamespaceUpdate_NamespaceDontExist() {
nsName := "missing-namespace"
res := s.Execute(
Expand Down
8 changes: 7 additions & 1 deletion internal/temporalcli/commands.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2694,6 +2694,10 @@ commands:
--active-cluster NewActiveCluster
```

Active Cluster changes cannot be combined with other Namespace updates.
Update Namespace configuration, including the replication Cluster list,
before changing the active Cluster in a separate command.

Promote a Namespace for multi-region data replication:

```
Expand All @@ -2715,7 +2719,9 @@ commands:
options:
- name: active-cluster
type: string
description: Active Cluster (Service) name.
description: |
Active Cluster (Service) name.
Cannot be combined with options that modify Namespace configuration.
- name: cluster
type: string[]
description: Cluster (Service) names.
Expand Down