Skip to content

Commit d055576

Browse files
Add Key Value output contract for create (#476)
Tightens up JSON / YAML output for `kv create` command. Updates `render ea kv create -o json` to serialize and print the new `KeyValueOut` struct, instead of just printing whatever is returned by the API GROW-2587 GitOrigin-RevId: e554e898f89881362375e9bb0e213691eeabab64
1 parent 75d8ec4 commit d055576

4 files changed

Lines changed: 83 additions & 25 deletions

File tree

cmd/kvcreate.go

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55

66
"github.com/spf13/cobra"
77

8-
"github.com/render-oss/cli/pkg/client"
98
"github.com/render-oss/cli/pkg/command"
109
"github.com/render-oss/cli/pkg/dependencies"
1110
"github.com/render-oss/cli/pkg/keyvalue"
@@ -15,7 +14,7 @@ import (
1514
kvtypes "github.com/render-oss/cli/pkg/types/keyvalue"
1615
)
1716

18-
func newKVCreateCmd(_ *dependencies.Dependencies) *cobra.Command {
17+
func newKVCreateCmd(deps *dependencies.Dependencies) *cobra.Command {
1918
cmd := &cobra.Command{
2019
Use: "create",
2120
Short: "Create a new Key Value store instance",
@@ -75,10 +74,15 @@ Examples:
7574
}
7675

7776
if nonInteractive, err := command.NonInteractive(cmd,
78-
func() (*client.KeyValueDetail, error) {
79-
return keyvalue.Create(cmd.Context(), input)
77+
func() (*keyvalue.KeyValueOut, error) {
78+
resolved, err := deps.KeyValueService().Create(cmd.Context(), input)
79+
if err != nil {
80+
return nil, err
81+
}
82+
out := keyvalue.NewKeyValueOut(resolved)
83+
return &out, nil
8084
},
81-
func(kv *client.KeyValueDetail) string {
85+
func(kv *keyvalue.KeyValueOut) string {
8286
return kvCreateSuccessMessage(kv)
8387
},
8488
); err != nil {
@@ -88,7 +92,7 @@ Examples:
8892
}
8993

9094
if command.GetConfirmFromContext(cmd.Context()) {
91-
return runKVCreateAndPrint(cmd, input)
95+
return runKVCreateAndPrint(cmd, deps, input)
9296
}
9397

9498
kv, err := views.RunKeyValueCreate(cmd, &input)
@@ -99,25 +103,27 @@ Examples:
99103
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "Canceled.")
100104
return nil
101105
}
102-
_, _ = fmt.Fprint(cmd.OutOrStdout(), kvCreateSuccessMessage(kv))
106+
out := keyvalue.NewKeyValueOut(&keyvalue.ResolvedKeyValue{KeyValue: kv})
107+
_, _ = fmt.Fprint(cmd.OutOrStdout(), kvCreateSuccessMessage(&out))
103108
return nil
104109
}
105110

106111
return cmd
107112
}
108113

109-
func runKVCreateAndPrint(cmd *cobra.Command, input kvtypes.KeyValueCreateInput) error {
110-
kv, err := keyvalue.Create(cmd.Context(), input)
114+
func runKVCreateAndPrint(cmd *cobra.Command, deps *dependencies.Dependencies, input kvtypes.KeyValueCreateInput) error {
115+
resolved, err := deps.KeyValueService().Create(cmd.Context(), input)
111116
if err != nil {
112117
return err
113118
}
114-
_, _ = fmt.Fprint(cmd.OutOrStdout(), kvCreateSuccessMessage(kv))
119+
out := keyvalue.NewKeyValueOut(resolved)
120+
_, _ = fmt.Fprint(cmd.OutOrStdout(), kvCreateSuccessMessage(&out))
115121
return nil
116122
}
117123

118-
func kvCreateSuccessMessage(kv *client.KeyValueDetail) string {
124+
func kvCreateSuccessMessage(kv *keyvalue.KeyValueOut) string {
119125
return fmt.Sprintf(
120126
"Created Key Value store\n\n%s\n",
121-
text.KeyValueAPIDetail(kv),
127+
text.KeyValueDetail(kv),
122128
)
123129
}

cmd/kvcreate_test.go

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"encoding/json"
55
"net/http"
66
"testing"
7+
"time"
78

89
renderapi "github.com/render-oss/cli/internal/fakes/renderapi"
910
"github.com/render-oss/cli/internal/testids"
@@ -363,14 +364,46 @@ func TestKVCreate_ConfirmMode_GeneratesName(t *testing.T) {
363364

364365
func TestKVCreate_OutputJSON(t *testing.T) {
365366
server := renderapi.NewServer(t)
367+
project := server.CreateProject(
368+
renderapi.ProjectAttrs{Name: "My Project", OwnerId: kvTestWorkspaceID},
369+
renderapi.EnvAttrs{Name: "production"},
370+
)
371+
env := project.Env("production")
372+
366373
cmdResult, err := executeKVCreate(t, server,
367-
"--name", "my-kv", "--plan", "free",
374+
"--name", "my-kv",
375+
"--plan", "free",
376+
"--project", "My Project",
377+
"--environment", "production",
378+
"--memory-policy", "queue",
379+
"--ip-allow-list", "cidr=203.0.113.5/32,description=office",
368380
"--output", "json",
369381
)
370382
require.NoError(t, err)
371-
var result map[string]interface{}
372-
require.NoError(t, json.Unmarshal([]byte(cmdResult.Stdout), &result), "expected valid JSON, got: %s", cmdResult.Stdout)
373-
assert.Equal(t, "my-kv", result["name"])
383+
384+
var body map[string]any
385+
require.NoError(t, json.Unmarshal([]byte(cmdResult.Stdout), &body), "expected valid JSON, got: %s", cmdResult.Stdout)
386+
require.Len(t, server.KV.Instances, 1)
387+
kv := server.KV.Instances[0]
388+
assert.Equal(t, map[string]any{
389+
"id": kv.Id,
390+
"name": "my-kv",
391+
"plan": "free",
392+
"region": "oregon",
393+
"status": "available",
394+
"createdAt": kv.CreatedAt.Format(time.RFC3339Nano),
395+
"updatedAt": kv.UpdatedAt.Format(time.RFC3339Nano),
396+
"ownerId": kvTestWorkspaceID,
397+
"projectId": project.Project.Id,
398+
"environmentId": env.Id,
399+
"ipAllowList": []any{
400+
map[string]any{
401+
"cidrBlock": "203.0.113.5/32",
402+
"description": "office",
403+
},
404+
},
405+
"maxmemoryPolicy": "noeviction",
406+
}, body)
374407
}
375408

376409
func TestKVCreate_OutputYAML(t *testing.T) {

pkg/keyvalue/create.go

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,22 @@ import (
1313
)
1414

1515
// Create applies defaults, resolves the requested scope (workspace/project/
16-
// environment), and calls the Key Value create endpoint. It is the shared
17-
// entry point for both the non-interactive flag path and the interactive
18-
// wizard's final submit step.
16+
// environment), and calls the Key Value create endpoint.
1917
func Create(ctx context.Context, input kvtypes.KeyValueCreateInput) (*client.KeyValueDetail, error) {
20-
input = kvtypes.NormalizeCreateInput(input)
21-
2218
c, err := client.NewDefaultClient()
2319
if err != nil {
2420
return nil, err
2521
}
26-
resolver := resolve.NewFromClient(c)
22+
svc := NewService(NewRepo(c), nil, nil, resolve.NewFromClient(c))
23+
resolved, err := svc.Create(ctx, input)
24+
if err != nil {
25+
return nil, err
26+
}
27+
return resolved.KeyValue, nil
28+
}
29+
30+
func (s *Service) create(ctx context.Context, input kvtypes.KeyValueCreateInput) (*ResolvedKeyValue, error) {
31+
input = kvtypes.NormalizeCreateInput(input)
2732

2833
if input.Name == "" {
2934
input.Name = petname.Generate(2, "-")
@@ -40,7 +45,7 @@ func Create(ctx context.Context, input kvtypes.KeyValueCreateInput) (*client.Key
4045
input.MaxmemoryPolicy = &p
4146
}
4247

43-
scope, err := resolver.ResolveScope(ctx, resolve.ScopeInput{
48+
scope, err := s.resolver.ResolveScope(ctx, resolve.ScopeInput{
4449
WorkspaceIDOrName: input.WorkspaceIDOrName,
4550
ProjectIDOrName: input.ProjectIDOrName,
4651
EnvironmentIDOrName: input.EnvironmentIDOrName,
@@ -51,7 +56,7 @@ func Create(ctx context.Context, input kvtypes.KeyValueCreateInput) (*client.Key
5156

5257
environmentID := scope.EnvironmentID()
5358
if environmentID == nil && input.ProjectIDOrName != nil && input.EnvironmentIDOrName == nil {
54-
environmentID, err = resolver.ResolveEnvironmentID(ctx, scope.Project, nil, scope.WorkspaceID)
59+
environmentID, err = s.resolver.ResolveEnvironmentID(ctx, scope.Project, nil, scope.WorkspaceID)
5560
if err != nil {
5661
return nil, err
5762
}
@@ -70,7 +75,15 @@ func Create(ctx context.Context, input kvtypes.KeyValueCreateInput) (*client.Key
7075
return nil, err
7176
}
7277

73-
return NewRepo(c).CreateKeyValue(ctx, body)
78+
kv, err := s.repo.CreateKeyValue(ctx, body)
79+
if err != nil {
80+
return nil, err
81+
}
82+
return &ResolvedKeyValue{
83+
KeyValue: kv,
84+
Project: scope.Project,
85+
Environment: scope.Environment,
86+
}, nil
7487
}
7588

7689
var wellKnownPlanValues = []string{

pkg/keyvalue/service.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ func (s *Service) Resolve(ctx context.Context, input ResolveInput) (*ResolvedKey
8888
return s.resolve(ctx, input)
8989
}
9090

91+
// Create applies defaults, resolves the requested scope, creates a Key Value,
92+
// and returns the created resource with resolved project/environment context.
93+
func (s *Service) Create(ctx context.Context, input kvtypes.KeyValueCreateInput) (*ResolvedKeyValue, error) {
94+
return s.create(ctx, input)
95+
}
96+
9197
func (s *Service) GetConnectionInfo(ctx context.Context, id string) (*client.KeyValueConnectionInfo, error) {
9298
return s.repo.GetKeyValueConnectionInfo(ctx, id)
9399
}

0 commit comments

Comments
 (0)