diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index df1e339..7e57b40 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,12 +7,21 @@ on: branches: - main +permissions: + contents: read + jobs: verify-generation: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v5 + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: "go.mod" + check-latest: true + cache: false - name: Add Go bin to PATH run: echo "$HOME/go/bin" >> $GITHUB_PATH - name: Run commands generation command @@ -34,5 +43,11 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v5 + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: "go.mod" + check-latest: true + cache: false - name: Build the cli binary run: make build diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6790412..1e08e67 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,6 +23,13 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: "go.mod" + check-latest: true + cache: false + - name: Setup mise uses: jdx/mise-action@v2 with: diff --git a/temporalcloudcli/cloud.go b/temporalcloudcli/cloud.go index fc3de2c..bcfb8ce 100644 --- a/temporalcloudcli/cloud.go +++ b/temporalcloudcli/cloud.go @@ -51,7 +51,9 @@ func (b *CloudOptionsBuilder) Build(ctx context.Context) (*CloudOptions, error) } } - cloudOpts := &CloudOptions{} + cloudOpts := &CloudOptions{ + CommonOptions: common, + } // Set logger if provided. if b.Logger != nil { diff --git a/temporalcloudcli/commands.gen.go b/temporalcloudcli/commands.gen.go index 8163c17..56fef4f 100644 --- a/temporalcloudcli/commands.gen.go +++ b/temporalcloudcli/commands.gen.go @@ -29,6 +29,16 @@ func (v *ClientOptions) BuildFlags(f *pflag.FlagSet) { _ = f.MarkHidden("server") } +type DiffOptions struct { + VerboseDiff bool + FlagSet *pflag.FlagSet +} + +func (v *DiffOptions) BuildFlags(f *pflag.FlagSet) { + v.FlagSet = f + f.BoolVar(&v.VerboseDiff, "verbose-diff", false, "Show detailed differences between the current and desired namespace configurations when changes are detected.") +} + type CloudCommand struct { Command cobra.Command ClientOptions @@ -153,11 +163,11 @@ type CloudNamespaceApplyCommand struct { Parent *CloudNamespaceCommand Command cobra.Command ClientOptions + DiffOptions Spec string AsyncOperationId string Idempotent bool Async bool - VerboseDiff bool ResourceVersion string } @@ -178,9 +188,9 @@ func NewCloudNamespaceApplyCommand(cctx *CommandContext, parent *CloudNamespaceC s.Command.Flags().StringVar(&s.AsyncOperationId, "async-operation-id", "", "Custom identifier for tracking this async operation. If not provided, a unique ID is generated automatically.") s.Command.Flags().BoolVar(&s.Idempotent, "idempotent", false, "Succeed silently if the namespace already matches the specification. Without this flag, the command errors when no changes are needed.") s.Command.Flags().BoolVar(&s.Async, "async", false, "Return immediately after initiating the operation instead of waiting for completion. Use the returned operation ID to check status later.") - s.Command.Flags().BoolVar(&s.VerboseDiff, "verbose-diff", false, "Show detailed differences between the current and desired namespace configurations when changes are detected.") s.Command.Flags().StringVarP(&s.ResourceVersion, "resource-version", "v", "", "Resource version for optimistic concurrency control. If not provided, the current version is fetched automatically.") s.ClientOptions.BuildFlags(s.Command.Flags()) + s.DiffOptions.BuildFlags(s.Command.Flags()) s.Command.Run = func(c *cobra.Command, args []string) { if err := s.run(cctx, args); err != nil { cctx.Options.Fail(err) @@ -231,6 +241,7 @@ type CloudNamespaceEditCommand struct { Parent *CloudNamespaceCommand Command cobra.Command ClientOptions + DiffOptions Namespace string AsyncOperationId string Idempotent bool @@ -257,6 +268,7 @@ func NewCloudNamespaceEditCommand(cctx *CommandContext, parent *CloudNamespaceCo s.Command.Flags().BoolVar(&s.Async, "async", false, "Return immediately after initiating the operation instead of waiting for completion. Use the returned operation ID to check status later.") s.Command.Flags().StringVarP(&s.ResourceVersion, "resource-version", "v", "", "Resource version for optimistic concurrency control. If not provided, the current version is fetched automatically.") s.ClientOptions.BuildFlags(s.Command.Flags()) + s.DiffOptions.BuildFlags(s.Command.Flags()) s.Command.Run = func(c *cobra.Command, args []string) { if err := s.run(cctx, args); err != nil { cctx.Options.Fail(err) @@ -348,6 +360,7 @@ type CloudNamespaceLifecycleSetCommand struct { Parent *CloudNamespaceLifecycleCommand Command cobra.Command ClientOptions + DiffOptions Namespace string EnableDeleteProtection bool AsyncOperationId string @@ -377,6 +390,7 @@ func NewCloudNamespaceLifecycleSetCommand(cctx *CommandContext, parent *CloudNam s.Command.Flags().BoolVar(&s.Idempotent, "idempotent", false, "Succeed silently if the lifecycle configuration is already set to the specified value. Without this flag, the command errors when no change is needed.") s.Command.Flags().StringVar(&s.ResourceVersion, "resource-version", "", "Resource version for optimistic concurrency control. If not provided, the current version is fetched automatically.") s.ClientOptions.BuildFlags(s.Command.Flags()) + s.DiffOptions.BuildFlags(s.Command.Flags()) s.Command.Run = func(c *cobra.Command, args []string) { if err := s.run(cctx, args); err != nil { cctx.Options.Fail(err) @@ -469,6 +483,7 @@ type CloudNamespaceRetentionSetCommand struct { Parent *CloudNamespaceRetentionCommand Command cobra.Command ClientOptions + DiffOptions Namespace string AsyncOperationId string Async bool @@ -498,6 +513,7 @@ func NewCloudNamespaceRetentionSetCommand(cctx *CommandContext, parent *CloudNam _ = cobra.MarkFlagRequired(s.Command.Flags(), "retention-days") s.Command.Flags().StringVar(&s.ResourceVersion, "resource-version", "", "Resource version for optimistic concurrency control. If not provided, the current version is fetched automatically.") s.ClientOptions.BuildFlags(s.Command.Flags()) + s.DiffOptions.BuildFlags(s.Command.Flags()) s.Command.Run = func(c *cobra.Command, args []string) { if err := s.run(cctx, args); err != nil { cctx.Options.Fail(err) diff --git a/temporalcloudcli/commands.go b/temporalcloudcli/commands.go index 755424d..14bbd03 100644 --- a/temporalcloudcli/commands.go +++ b/temporalcloudcli/commands.go @@ -4,7 +4,6 @@ import ( "bufio" "context" "encoding/json" - "errors" "fmt" "io" "log/slog" @@ -21,17 +20,13 @@ import ( "github.com/spf13/pflag" "github.com/temporalio/cloud-cli/temporalcloudcli/internal/printer" "go.temporal.io/api/common/v1" - commonpb "go.temporal.io/api/common/v1" "go.temporal.io/api/failure/v1" "go.temporal.io/api/temporalproto" "go.temporal.io/cloud-sdk/cloudclient" "go.temporal.io/sdk/contrib/envconfig" - "go.temporal.io/sdk/converter" - "go.temporal.io/sdk/temporal" "golang.org/x/term" "google.golang.org/grpc" "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/timestamppb" ) // Version is the value put as the default command version. This is often @@ -546,15 +541,6 @@ func (c *CloudCommand) preRun(cctx *CommandContext) error { return nil } -func aliasNormalizer(aliases map[string]string) func(f *pflag.FlagSet, name string) pflag.NormalizedName { - return func(f *pflag.FlagSet, name string) pflag.NormalizedName { - if actual := aliases[name]; actual != "" { - name = actual - } - return pflag.NormalizedName(name) - } -} - func newNopLogger() *slog.Logger { return slog.New(discardLogHandler{}) } type discardLogHandler struct{} @@ -564,61 +550,6 @@ func (discardLogHandler) Handle(context.Context, slog.Record) error { return nil func (d discardLogHandler) WithAttrs([]slog.Attr) slog.Handler { return d } func (d discardLogHandler) WithGroup(string) slog.Handler { return d } -func timestampToTime(t *timestamppb.Timestamp) time.Time { - if t == nil { - return time.Time{} - } - return t.AsTime() -} - type nopWriter struct{} func (nopWriter) Write(b []byte) (int, error) { return len(b), nil } - -type structuredError struct { - Message string `json:"message"` - Type string `json:"type,omitempty"` - Details any `json:"details,omitempty"` -} - -func fromApplicationError(err *temporal.ApplicationError) (*structuredError, error) { - var deets any - if err := err.Details(&deets); err != nil && !errors.Is(err, temporal.ErrNoData) { - return nil, err - } - return &structuredError{ - Message: err.Error(), - Type: err.Type(), - Details: deets, - }, nil -} - -func encodeMapToPayloads(in map[string]any) (map[string]*commonpb.Payload, error) { - if len(in) == 0 { - return nil, nil - } - // search attributes always use default dataconverter - dc := converter.GetDefaultDataConverter() - out := make(map[string]*commonpb.Payload, len(in)) - for key, val := range in { - payload, err := dc.ToPayload(val) - if err != nil { - return nil, err - } - out[key] = payload - } - return out, nil -} - -type overrideDisplayTypeFlagValue struct { - pflag.Value - displayType string -} - -func (o *overrideDisplayTypeFlagValue) Type() string { - return o.displayType -} - -func overrideFlagDisplayType(flag *pflag.Flag, displayType string) { - flag.Value = &overrideDisplayTypeFlagValue{Value: flag.Value, displayType: displayType} -} diff --git a/temporalcloudcli/commands.login.go b/temporalcloudcli/commands.login.go index 2450a2c..babedae 100644 --- a/temporalcloudcli/commands.login.go +++ b/temporalcloudcli/commands.login.go @@ -19,6 +19,8 @@ func (c *CloudLoginCommand) run(cctx *CommandContext, _ []string) error { return fmt.Errorf("failed to load profile: %w", err) } if loadClientOauthRes.OAuth != nil && + loadClientOauthRes.OAuth.Token != nil && + loadClientOauthRes.OAuth.ClientConfig != nil && !reflect.DeepEqual(*loadClientOauthRes.OAuth, cliext.OAuthConfig{}) && !reflect.DeepEqual(*loadClientOauthRes.OAuth.ClientConfig, oauth2.Config{}) && !c.Reset { diff --git a/temporalcloudcli/commands.namespace.go b/temporalcloudcli/commands.namespace.go index a9e1ac1..419a5c2 100644 --- a/temporalcloudcli/commands.namespace.go +++ b/temporalcloudcli/commands.namespace.go @@ -4,6 +4,7 @@ import ( "fmt" namespace "go.temporal.io/cloud-sdk/api/namespace/v1" + operation "go.temporal.io/cloud-sdk/api/operation/v1" "github.com/temporalio/cloud-cli/temporalcloudcli/internal/printer" ) @@ -46,7 +47,7 @@ func (c *CloudNamespaceEditCommand) run(cctx *CommandContext, _ []string) error return err } - err = promptApplyResource(cctx, ns.Spec, newSpec, cctx.RootCommand.AutoConfirm) + err = promptApplyResource(cctx, ns.Spec, newSpec, c.VerboseDiff) if err != nil { return err } @@ -57,7 +58,7 @@ func (c *CloudNamespaceEditCommand) run(cctx *CommandContext, _ []string) error resourceVersion = ns.ResourceVersion } - res, err := client.applyNamespace(cctx.Context, applyNamespaceParams{ + asyncOp, err := client.updateNamespace(cctx.Context, updateNamespaceParams{ namespace: c.Namespace, spec: newSpec, asyncOperationID: c.AsyncOperationId, @@ -69,14 +70,14 @@ func (c *CloudNamespaceEditCommand) run(cctx *CommandContext, _ []string) error } // TODO: (gmankes) remove this -- clean up and make shareable - if res.asyncOp == nil { + if asyncOp == nil { // Nothing changed (idempotent case) result := struct { Status string Namespace string }{ Status: "unchanged", - Namespace: newSpec.Name, + Namespace: c.Namespace, } return cctx.Printer.PrintStructured(result, printer.StructuredOptions{}) } @@ -85,13 +86,13 @@ func (c *CloudNamespaceEditCommand) run(cctx *CommandContext, _ []string) error if c.Async { // Return immediately with the async operation return cctx.Printer.PrintStructured(MutationResult{ - AsyncOp: res.asyncOp, - ID: res.Namespace, + AsyncOp: asyncOp, + ID: c.Namespace, }, printer.StructuredOptions{}) } // Poll for completion - return PollAsyncOperation(cctx, cloudClient, res.asyncOp.Id, res.Namespace) + return PollAsyncOperation(cctx, cloudClient, asyncOp.Id, c.Namespace) } func (c *CloudNamespaceApplyCommand) run(cctx *CommandContext, _ []string) error { @@ -126,6 +127,7 @@ func (c *CloudNamespaceApplyCommand) run(cctx *CommandContext, _ []string) error existingResourceVersion := "" var existingSpec *namespace.NamespaceSpec existingNamespaceIdentifier := "" + if found { existingResourceVersion = existing.ResourceVersion existingSpec = existing.Spec @@ -138,51 +140,66 @@ func (c *CloudNamespaceApplyCommand) run(cctx *CommandContext, _ []string) error return err } - // Step 5: Apply the namespace (create or update) + // Step 6: Apply the namespace (create or update) // Use provided resource version, or use fetched version resourceVersion := c.ResourceVersion if resourceVersion == "" { resourceVersion = existingResourceVersion } - params := applyNamespaceParams{ - namespace: existingNamespaceIdentifier, - spec: spec, + var asyncOp *operation.AsyncOperation + var namespaceID string - resourceVersion: resourceVersion, - asyncOperationID: c.AsyncOperationId, // Use the flag value if provided - idempotent: c.Idempotent, // Use the flag value - } - - res, err := client.applyNamespace(cctx.Context, params) - if err != nil { - return fmt.Errorf("failed to apply namespace: %w", err) + if found { + // Update existing namespace + asyncOp, err = client.updateNamespace(cctx.Context, updateNamespaceParams{ + namespace: existingNamespaceIdentifier, + spec: spec, + asyncOperationID: c.AsyncOperationId, + idempotent: c.Idempotent, + resourceVersion: resourceVersion, + }) + if err != nil { + return fmt.Errorf("failed to update namespace: %w", err) + } + namespaceID = existingNamespaceIdentifier + } else { + // Create new namespace + res, err := client.createNamespace(cctx.Context, createNamespaceParams{ + spec: spec, + asyncOperationID: c.AsyncOperationId, + }) + if err != nil { + return fmt.Errorf("failed to create namespace: %w", err) + } + asyncOp = res.asyncOp + namespaceID = res.Namespace } - // Step 5: Handle result - if res.asyncOp == nil { + // Step 7: Handle result + if asyncOp == nil { // Nothing changed (idempotent case) result := struct { Status string Namespace string }{ Status: "unchanged", - Namespace: existingNamespaceIdentifier, + Namespace: namespaceID, } return cctx.Printer.PrintStructured(result, printer.StructuredOptions{}) } - // Step 6: Handle async flag + // Step 8: Handle async flag if c.Async { // Return immediately with the async operation return cctx.Printer.PrintStructured(MutationResult{ - AsyncOp: res.asyncOp, - ID: res.Namespace, + AsyncOp: asyncOp, + ID: namespaceID, }, printer.StructuredOptions{}) } // Step 7: Poll for completion - return PollAsyncOperation(cctx, cloudClient, res.asyncOp.Id, res.Namespace) + return PollAsyncOperation(cctx, cloudClient, asyncOp.Id, namespaceID) } func (c *CloudNamespaceDeleteCommand) run(cctx *CommandContext, _ []string) error { diff --git a/temporalcloudcli/commands.namespace.lifecycle.go b/temporalcloudcli/commands.namespace.lifecycle.go index c5281b6..9e22a48 100644 --- a/temporalcloudcli/commands.namespace.lifecycle.go +++ b/temporalcloudcli/commands.namespace.lifecycle.go @@ -61,7 +61,7 @@ func (c *CloudNamespaceLifecycleSetCommand) run(cctx *CommandContext, _ []string newSpec.Lifecycle.EnableDeleteProtection = c.EnableDeleteProtection // Show diff - err = promptApplyResource(cctx, ns.Spec, newSpec, cctx.RootCommand.AutoConfirm) + err = promptApplyResource(cctx, ns.Spec, newSpec, c.VerboseDiff) if err != nil { return err } @@ -72,7 +72,7 @@ func (c *CloudNamespaceLifecycleSetCommand) run(cctx *CommandContext, _ []string resourceVersion = ns.ResourceVersion } - res, err := client.applyNamespace(cctx.Context, applyNamespaceParams{ + asyncOp, err := client.updateNamespace(cctx.Context, updateNamespaceParams{ namespace: c.Namespace, spec: newSpec, asyncOperationID: c.AsyncOperationId, @@ -83,14 +83,14 @@ func (c *CloudNamespaceLifecycleSetCommand) run(cctx *CommandContext, _ []string return err } - if res.asyncOp == nil { + if asyncOp == nil { // Nothing changed (idempotent case) result := struct { Status string Namespace string }{ Status: "unchanged", - Namespace: newSpec.Name, + Namespace: c.Namespace, } return cctx.Printer.PrintStructured(result, printer.StructuredOptions{}) } @@ -99,11 +99,11 @@ func (c *CloudNamespaceLifecycleSetCommand) run(cctx *CommandContext, _ []string if c.Async { // Return immediately with the async operation return cctx.Printer.PrintStructured(MutationResult{ - AsyncOp: res.asyncOp, - ID: res.Namespace, + AsyncOp: asyncOp, + ID: c.Namespace, }, printer.StructuredOptions{}) } // Poll for completion - return PollAsyncOperation(cctx, cloudClient, res.asyncOp.Id, res.Namespace) + return PollAsyncOperation(cctx, cloudClient, asyncOp.Id, c.Namespace) } diff --git a/temporalcloudcli/commands.namespace.retention.go b/temporalcloudcli/commands.namespace.retention.go index 5dca245..c2b3fce 100644 --- a/temporalcloudcli/commands.namespace.retention.go +++ b/temporalcloudcli/commands.namespace.retention.go @@ -23,7 +23,7 @@ func (c *CloudNamespaceRetentionSetCommand) run(cctx *CommandContext, _ []string newSpec := proto.Clone(ns.Spec).(*namespace.NamespaceSpec) newSpec.RetentionDays = int32(c.RetentionDays) - err = promptApplyResource(cctx, ns.Spec, newSpec, cctx.RootCommand.AutoConfirm) + err = promptApplyResource(cctx, ns.Spec, newSpec, c.VerboseDiff) if err != nil { return err } @@ -34,7 +34,7 @@ func (c *CloudNamespaceRetentionSetCommand) run(cctx *CommandContext, _ []string resourceVersion = ns.ResourceVersion } - res, err := client.applyNamespace(cctx.Context, applyNamespaceParams{ + asyncOp, err := client.updateNamespace(cctx.Context, updateNamespaceParams{ namespace: c.Namespace, spec: newSpec, asyncOperationID: c.AsyncOperationId, @@ -44,14 +44,14 @@ func (c *CloudNamespaceRetentionSetCommand) run(cctx *CommandContext, _ []string if err != nil { return err } - if res.asyncOp == nil { + if asyncOp == nil { // Nothing changed (idempotent case) result := struct { Status string Namespace string }{ Status: "unchanged", - Namespace: newSpec.Name, + Namespace: c.Namespace, } return cctx.Printer.PrintStructured(result, printer.StructuredOptions{}) } @@ -60,13 +60,13 @@ func (c *CloudNamespaceRetentionSetCommand) run(cctx *CommandContext, _ []string if c.Async { // Return immediately with the async operation return cctx.Printer.PrintStructured(MutationResult{ - AsyncOp: res.asyncOp, - ID: res.Namespace, + AsyncOp: asyncOp, + ID: c.Namespace, }, printer.StructuredOptions{}) } // Poll for completion - return PollAsyncOperation(cctx, cloudClient, res.asyncOp.Id, res.Namespace) + return PollAsyncOperation(cctx, cloudClient, asyncOp.Id, c.Namespace) } func (c *CloudNamespaceRetentionGetCommand) run(cctx *CommandContext, _ []string) error { diff --git a/temporalcloudcli/commands.yml b/temporalcloudcli/commands.yml index bc4ae09..9329a53 100644 --- a/temporalcloudcli/commands.yml +++ b/temporalcloudcli/commands.yml @@ -187,6 +187,7 @@ commands: has-init: false option-sets: - client + - diff options: - name: spec type: string @@ -209,11 +210,6 @@ commands: description: | Return immediately after initiating the operation instead of waiting for completion. Use the returned operation ID to check status later. - - name: verbose-diff - type: bool - description: | - Show detailed differences between the current and desired namespace - configurations when changes are detected. - name: resource-version type: string short: v @@ -238,6 +234,7 @@ commands: has-init: false option-sets: - client + - diff options: - name: namespace type: string @@ -382,6 +379,7 @@ commands: has-init: false option-sets: - client + - diff options: - name: namespace type: string @@ -460,6 +458,7 @@ commands: has-init: false option-sets: - client + - diff options: - name: namespace type: string @@ -512,5 +511,12 @@ option-sets: to non-production environments. hidden: true default: saas-api.tmprl-test.cloud:443 + - name: diff + options: + - name: verbose-diff + type: bool + description: | + Show detailed differences between the current and desired namespace + configurations when changes are detected. - name: common external-package: github.com/temporalio/cli/cliext diff --git a/temporalcloudcli/commands_test.go b/temporalcloudcli/commands_test.go index c8d53b0..4e7b3f5 100644 --- a/temporalcloudcli/commands_test.go +++ b/temporalcloudcli/commands_test.go @@ -182,7 +182,7 @@ func (s *SharedServerSuite) SetupSuite() { s.apiKey = os.Getenv("TEMPORAL_API_KEY") s.Suite.Require().NotEmpty(s.apiKey, "Could not load TEMPORAL_API_KEY. Are you running with `mise run test` and have you filled out your .env? See README.md for details.") s.server = os.Getenv("TEMPORAL_CLOUD_SERVER") - s.Suite.Require().NotEmpty(s.apiKey, "Could not load TEMPORAL_CLOUD_SERVER. Are you running with `mise run test` and have you filled out your .env? See README.md for details.") + s.Suite.Require().NotEmpty(s.server, "Could not load TEMPORAL_CLOUD_SERVER. Are you running with `mise run test` and have you filled out your .env? See README.md for details.") } func (s *SharedServerSuite) TearDownSuite() { diff --git a/temporalcloudcli/common.go b/temporalcloudcli/common.go index 9503f02..b35e44c 100644 --- a/temporalcloudcli/common.go +++ b/temporalcloudcli/common.go @@ -3,16 +3,15 @@ package temporalcloudcli import ( "bytes" "cmp" - "encoding/json" "fmt" "os" "os/exec" "strings" "time" - "go.temporal.io/cloud-sdk/cloudclient" cloudservice "go.temporal.io/cloud-sdk/api/cloudservice/v1" operation "go.temporal.io/cloud-sdk/api/operation/v1" + "go.temporal.io/cloud-sdk/cloudclient" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -47,9 +46,7 @@ func isNotFoundErr(e error) bool { // or treats the input as inline JSON. Returns the parsed data as a byte slice. func loadJSONSpec(spec string) ([]byte, error) { // Check if spec starts with '@' indicating file path - if strings.HasPrefix(spec, "@") { - // Remove '@' prefix and read file - filePath := strings.TrimPrefix(spec, "@") + if filePath, ok := strings.CutPrefix(spec, "@"); ok { data, err := os.ReadFile(filePath) if err != nil { return nil, fmt.Errorf("failed to read spec file %q: %w", filePath, err) @@ -61,18 +58,6 @@ func loadJSONSpec(spec string) ([]byte, error) { return []byte(spec), nil } -func runEditorForJSONEdit(existing, valuePtr any) error { - existingBytes, err := json.MarshalIndent(existing, "", " ") - if err != nil { - return fmt.Errorf("unable to convert existing object to json: %v", err) - } - updatedBytes, err := runEditor(existingBytes) - if err != nil { - return err - } - return json.Unmarshal(updatedBytes, valuePtr) -} - func runEditorForJSONEditForProtos(existing, value proto.Message) error { marshaler := protojson.MarshalOptions{ EmitUnpopulated: true, @@ -96,6 +81,11 @@ func runEditor(existing []byte) ([]byte, error) { return nil, fmt.Errorf("unable to create temp file for editing: %v", err) } + defer func() { + // Clean up temp file. + _ = os.Remove(f.Name()) + }() + if _, err := f.Write(existing); err != nil { return nil, fmt.Errorf("unable to write existing data to temp file for editing: %v", err) } diff --git a/temporalcloudcli/namespace.go b/temporalcloudcli/namespace.go index 8d671c0..c9af2be 100644 --- a/temporalcloudcli/namespace.go +++ b/temporalcloudcli/namespace.go @@ -157,65 +157,6 @@ func (c *namespaceClient) deleteNamespace(ctx context.Context, params deleteName return res.AsyncOperation, nil } -type applyNamespaceParams struct { - namespace string - spec *namespace.NamespaceSpec - - resourceVersion string // optional, if empty, will be fetched - asyncOperationID string - idempotent bool -} - -type applyNamespaceResponse struct { - asyncOp *operation.AsyncOperation - Namespace string -} - -func (c *namespaceClient) applyNamespace(ctx context.Context, params applyNamespaceParams) (applyNamespaceResponse, error) { - existing, err := c.getNamespaceByName(ctx, params.spec.Name) - if err != nil && !isNotFoundErr(err) { - return applyNamespaceResponse{}, err - } else if err != nil && isNotFoundErr(err) { - // create the namespace - res, err := c.createNamespace(ctx, createNamespaceParams{ - spec: params.spec, - asyncOperationID: params.asyncOperationID, - }) - if err != nil { - return applyNamespaceResponse{}, err - } - return applyNamespaceResponse{ - asyncOp: res.asyncOp, - Namespace: res.Namespace, - }, nil - } - - // update the namespace - if params.resourceVersion == "" { - params.resourceVersion = existing.ResourceVersion - } - - // update - // Namespace exists, update it using the current resource version - updateParams := updateNamespaceParams{ - namespace: params.namespace, - spec: params.spec, - - asyncOperationID: params.asyncOperationID, - idempotent: params.idempotent, - resourceVersion: params.resourceVersion, - } - - res, err := c.updateNamespace(ctx, updateParams) - if err != nil { - return applyNamespaceResponse{}, err - } - return applyNamespaceResponse{ - asyncOp: res, - Namespace: existing.Namespace, - }, nil -} - func (c *namespaceClient) getNamespaceByName(ctx context.Context, name string) (*namespace.Namespace, error) { namespaces, err := c.listNamespacesWithName(ctx, name, true) if err != nil {