Skip to content

Commit 398142b

Browse files
Improve JSON output for pg update command (#482)
Improves JSON output for `render ea pg update` to include the updated postgres details and before/after for all changed fields. Removes the full API shape for Project and Environment that were previously riding along for all Postgres commands GROW-2588 GitOrigin-RevId: 8f4cb481ae51801ed1e3afea0ee08143563dc418
1 parent 5d3a13e commit 398142b

7 files changed

Lines changed: 66 additions & 58 deletions

File tree

cmd/pgupdate.go

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66

77
"github.com/spf13/cobra"
88

9-
"github.com/render-oss/cli/pkg/client"
109
"github.com/render-oss/cli/pkg/command"
1110
"github.com/render-oss/cli/pkg/dependencies"
1211
"github.com/render-oss/cli/pkg/postgres"
@@ -102,22 +101,17 @@ mutually exclusive.`,
102101
input.ProjectIDOrName = types.OptionalNonZeroString(input.ProjectIDOrName)
103102
input.EnvironmentIDOrName = types.OptionalNonZeroString(input.EnvironmentIDOrName)
104103

105-
// Captured by both closures so the text formatter can render a diff
106-
// while JSON/YAML output stays simple — just the new state, matching
107-
// pg create's output shape.
108-
var before *client.PostgresDetail
109-
110104
_, err := command.NonInteractive(cmd,
111-
func() (*client.PostgresDetail, error) {
105+
func() (*postgres.PostgresUpdateOut, error) {
112106
result, err := deps.PostgresService().Update(cmd.Context(), input)
113107
if err != nil {
114108
return nil, err
115109
}
116-
before = result.Before
117-
return result.After, nil
110+
out := postgres.NewPostgresUpdateOut(result.Before, result.After)
111+
return &out, nil
118112
},
119-
func(after *client.PostgresDetail) string {
120-
return pgUpdateSuccessMessage(before, after)
113+
func(out *postgres.PostgresUpdateOut) string {
114+
return pgUpdateSuccessMessage(out)
121115
},
122116
)
123117
return err
@@ -126,9 +120,9 @@ mutually exclusive.`,
126120
return cmd
127121
}
128122

129-
func pgUpdateSuccessMessage(before, after *client.PostgresDetail) string {
130-
details := "Full details:\n " + strings.ReplaceAll(text.PostgresAPIDetail(after), "\n", "\n ")
131-
diff := text.PostgresUpdateDiff(before, after)
123+
func pgUpdateSuccessMessage(out *postgres.PostgresUpdateOut) string {
124+
details := "Full details:\n " + strings.ReplaceAll(text.PostgresDetail(&out.Data), "\n", "\n ")
125+
diff := text.PostgresUpdateDiff(out.Diff)
132126
if diff == "" {
133127
return fmt.Sprintf("No changes applied to Postgres database\n\n%s\n", details)
134128
}

cmd/pgupdate_test.go

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package cmd
22

33
import (
4-
"encoding/json"
54
"strings"
65
"testing"
76

@@ -11,6 +10,7 @@ import (
1110
renderapi "github.com/render-oss/cli/internal/fakes/renderapi"
1211
"github.com/render-oss/cli/internal/testassert"
1312
"github.com/render-oss/cli/internal/testids"
13+
"github.com/render-oss/cli/internal/testrequire"
1414
"github.com/render-oss/cli/pkg/client"
1515
pgclient "github.com/render-oss/cli/pkg/client/postgres"
1616
"github.com/render-oss/cli/pkg/pointers"
@@ -164,22 +164,27 @@ func TestPGUpdate_NameCollision_Errors(t *testing.T) {
164164
assert.Contains(t, err.Error(), "Multiple Postgres databases")
165165
}
166166

167-
func TestPGUpdate_JSONOutput_ReturnsNewState(t *testing.T) {
167+
func TestPGUpdate_JSONOutput_ReturnsDataAndDiff(t *testing.T) {
168168
harness := newPGUpdateHarness(t)
169-
pg := seedPG(harness.server, "json-db")
169+
project := harness.server.CreateProject(
170+
renderapi.ProjectAttrs{Name: "My Project", OwnerId: pgActiveWorkspaceID},
171+
renderapi.EnvAttrs{Name: "production"},
172+
)
173+
pg := seedPGInEnv(harness.server, "json-db", project.Env("production").Id)
170174

171175
result, err := harness.execute(pg.Id, "--name", "json-renamed", "--output", "json")
172176
require.NoError(t, err)
173177

174-
var body struct {
175-
ID string `json:"id"`
176-
Name string `json:"name"`
177-
}
178-
require.NoError(t, json.Unmarshal([]byte(result.Stdout), &body))
179-
assert.Equal(t, pg.Id, body.ID)
180-
assert.Equal(t, "json-renamed", body.Name)
181-
// JSON returns just the new state, not a before/after wrapper.
182-
assert.NotContains(t, result.Stdout, "Changes:")
178+
body := unmarshalPGJSONOutput(t, result.Stdout)
179+
data := testrequire.SubMap(t, body, "data")
180+
diff := testrequire.SubMap(t, body, "diff")
181+
nameDiff := testrequire.SubMap(t, diff, "name")
182+
assert.Equal(t, pg.Id, data["id"])
183+
assert.Equal(t, "json-renamed", data["name"])
184+
assert.Equal(t, project.Project.Id, data["projectId"])
185+
assert.Equal(t, project.Env("production").Id, data["environmentId"])
186+
assert.Equal(t, "json-db", nameDiff["before"])
187+
assert.Equal(t, "json-renamed", nameDiff["after"])
183188
}
184189

185190
func TestPGUpdate_UnknownID_Errors(t *testing.T) {

pkg/postgres/output.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,11 @@ func NewPostgresUpdateOut(before *client.PostgresDetail, after *ResolvedPostgres
132132
if before == nil {
133133
return out
134134
}
135-
out.Diff = NewPostgresUpdateDiff(before, &out.Data)
135+
out.Diff = newPostgresUpdateDiff(before, &out.Data)
136136
return out
137137
}
138138

139-
func NewPostgresUpdateDiff(before *client.PostgresDetail, after *PostgresOut) PostgresUpdateDiff {
139+
func newPostgresUpdateDiff(before *client.PostgresDetail, after *PostgresOut) PostgresUpdateDiff {
140140
var diff PostgresUpdateDiff
141141
if before == nil || after == nil {
142142
return diff

pkg/postgres/service.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,14 @@ func (s *Service) Update(ctx context.Context, input pgtypes.UpdatePostgresInput)
186186
return nil, err
187187
}
188188

189-
return &UpdateResult{Before: before, After: after}, nil
189+
return &UpdateResult{
190+
Before: before,
191+
After: &ResolvedPostgres{
192+
Postgres: after,
193+
Project: resolvedBefore.Project,
194+
Environment: resolvedBefore.Environment,
195+
},
196+
}, nil
190197
}
191198

192199
func (s *Service) hydratePostgresModel(ctx context.Context, postgres *client.Postgres, projects []*client.Project) (*Model, error) {

pkg/postgres/update.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
// more information than just the new state.
1313
type UpdateResult struct {
1414
Before *client.PostgresDetail `json:"before"`
15-
After *client.PostgresDetail `json:"after"`
15+
After *ResolvedPostgres `json:"after"`
1616
}
1717

1818
// BuildUpdateRequest converts an UpdatePostgresInput into the API PATCH body.

pkg/text/postgres.go

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@ package text
33
import (
44
"fmt"
55
"maps"
6-
"reflect"
76
"slices"
87
"strings"
98

109
"github.com/jedib0t/go-pretty/table"
1110

12-
"github.com/render-oss/cli/internal/ipallowlist"
1311
"github.com/render-oss/cli/pkg/client"
1412
"github.com/render-oss/cli/pkg/postgres"
1513
)
@@ -128,38 +126,37 @@ func diskSizeLabel(gb *int) string {
128126
return fmt.Sprintf("%d GB", *gb)
129127
}
130128

131-
// PostgresUpdateDiff renders the user-visible changes between before and after
132-
// snapshots of a Postgres instance, showing only the fields that actually
133-
// changed. Returns an empty string when nothing changed (the cmd layer can
134-
// then surface a "no changes" message).
129+
// PostgresUpdateDiff renders the user-visible changes from the public update
130+
// diff contract. Returns an empty string when nothing changed (the cmd layer
131+
// can then surface a "no changes" message).
135132
//
136133
// Label column is padded to 20 characters so the arrows align:
137134
//
138135
// " High availability: disabled → enabled"
139-
func PostgresUpdateDiff(before, after *client.PostgresDetail) string {
136+
func PostgresUpdateDiff(diff postgres.PostgresUpdateDiff) string {
140137
var lines []string
141138

142-
if before.Name != after.Name {
143-
lines = append(lines, fmt.Sprintf(" %-20s%s → %s", "Name:", before.Name, after.Name))
139+
if diff.Name != nil {
140+
lines = append(lines, fmt.Sprintf(" %-20s%s → %s", "Name:", diff.Name.Before, diff.Name.After))
144141
}
145-
if before.Plan != after.Plan {
146-
lines = append(lines, fmt.Sprintf(" %-20s%s → %s", "Plan:", string(before.Plan), string(after.Plan)))
142+
if diff.Plan != nil {
143+
lines = append(lines, fmt.Sprintf(" %-20s%s → %s", "Plan:", string(diff.Plan.Before), string(diff.Plan.After)))
147144
}
148-
if diskSizeLabel(before.DiskSizeGB) != diskSizeLabel(after.DiskSizeGB) {
149-
lines = append(lines, fmt.Sprintf(" %-20s%s → %s", "Disk size:", diskSizeLabel(before.DiskSizeGB), diskSizeLabel(after.DiskSizeGB)))
145+
if diff.DiskSizeGB != nil {
146+
lines = append(lines, fmt.Sprintf(" %-20s%s → %s", "Disk size:", diskSizeLabel(diff.DiskSizeGB.Before), diskSizeLabel(diff.DiskSizeGB.After)))
150147
}
151-
if before.DiskAutoscalingEnabled != after.DiskAutoscalingEnabled {
152-
lines = append(lines, fmt.Sprintf(" %-20s%s → %s", "Disk autoscaling:", boolLabel(before.DiskAutoscalingEnabled), boolLabel(after.DiskAutoscalingEnabled)))
148+
if diff.DiskAutoscalingEnabled != nil {
149+
lines = append(lines, fmt.Sprintf(" %-20s%s → %s", "Disk autoscaling:", boolLabel(diff.DiskAutoscalingEnabled.Before), boolLabel(diff.DiskAutoscalingEnabled.After)))
153150
}
154-
if before.HighAvailabilityEnabled != after.HighAvailabilityEnabled {
155-
lines = append(lines, fmt.Sprintf(" %-20s%s → %s", "High availability:", boolLabel(before.HighAvailabilityEnabled), boolLabel(after.HighAvailabilityEnabled)))
151+
if diff.HighAvailabilityEnabled != nil {
152+
lines = append(lines, fmt.Sprintf(" %-20s%s → %s", "High availability:", boolLabel(diff.HighAvailabilityEnabled.Before), boolLabel(diff.HighAvailabilityEnabled.After)))
156153
}
157-
if !ipallowlist.Equal(before.IpAllowList, after.IpAllowList) {
158-
lines = append(lines, fmt.Sprintf(" %-20s%s → %s", "IP allow-list:", ipAllowListLabel(before.IpAllowList), ipAllowListLabel(after.IpAllowList)))
154+
if diff.IPAllowList != nil {
155+
lines = append(lines, fmt.Sprintf(" %-20s%s → %s", "IP allow-list:", ipAllowListLabel(diff.IPAllowList.Before), ipAllowListLabel(diff.IPAllowList.After)))
159156
}
160157
// ParameterOverrides is a map; rather than a noisy per-key diff we flag that
161158
// it changed. The full new state is shown in the PostgresDetail block below.
162-
if !reflect.DeepEqual(before.ParameterOverrides, after.ParameterOverrides) {
159+
if diff.ParameterOverrides != nil {
163160
lines = append(lines, fmt.Sprintf(" %-20s %s", "Parameter overrides:", "updated"))
164161
}
165162

pkg/text/postgres_test.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ func basicPostgres() *client.PostgresDetail {
2828
}
2929
}
3030

31+
func postgresUpdateDiff(before, after *client.PostgresDetail) postgres.PostgresUpdateDiff {
32+
out := postgres.NewPostgresUpdateOut(before, &postgres.ResolvedPostgres{Postgres: after})
33+
return out.Diff
34+
}
35+
3136
func TestPostgresDetail_BasicFields(t *testing.T) {
3237
out := text.PostgresAPIDetail(basicPostgres())
3338

@@ -137,15 +142,15 @@ func TestPostgresTable(t *testing.T) {
137142
func TestPostgresUpdateDiff(t *testing.T) {
138143
t.Run("returns empty string when nothing changed", func(t *testing.T) {
139144
pg := basicPostgres()
140-
assert.Empty(t, text.PostgresUpdateDiff(pg, pg))
145+
assert.Empty(t, text.PostgresUpdateDiff(postgresUpdateDiff(pg, pg)))
141146
})
142147

143148
t.Run("single changed field: name", func(t *testing.T) {
144149
before := basicPostgres()
145150
after := basicPostgres()
146151
after.Name = "new-name"
147152

148-
out := text.PostgresUpdateDiff(before, after)
153+
out := text.PostgresUpdateDiff(postgresUpdateDiff(before, after))
149154
testassert.ContainsInOrder(t, out, "Name:", "my-pg → new-name")
150155
})
151156

@@ -154,7 +159,7 @@ func TestPostgresUpdateDiff(t *testing.T) {
154159
after := basicPostgres()
155160
after.Plan = pgclient.Basic256mb
156161

157-
out := text.PostgresUpdateDiff(before, after)
162+
out := text.PostgresUpdateDiff(postgresUpdateDiff(before, after))
158163
testassert.ContainsInOrder(t, out, "Plan:", "free → basic_256mb")
159164
})
160165

@@ -163,7 +168,7 @@ func TestPostgresUpdateDiff(t *testing.T) {
163168
after := basicPostgres()
164169
after.DiskSizeGB = pointers.From(100)
165170

166-
out := text.PostgresUpdateDiff(before, after)
171+
out := text.PostgresUpdateDiff(postgresUpdateDiff(before, after))
167172
testassert.ContainsInOrder(t, out, "Disk size:", "(unset) → 100 GB")
168173
})
169174

@@ -174,7 +179,7 @@ func TestPostgresUpdateDiff(t *testing.T) {
174179
after.DiskAutoscalingEnabled = true
175180
after.HighAvailabilityEnabled = false
176181

177-
out := text.PostgresUpdateDiff(before, after)
182+
out := text.PostgresUpdateDiff(postgresUpdateDiff(before, after))
178183
// Disk autoscaling renders before high availability, so assert the full
179184
// layout in order: a field that turned on, then one that turned off.
180185
testassert.ContainsInOrder(t, out,
@@ -189,7 +194,7 @@ func TestPostgresUpdateDiff(t *testing.T) {
189194
{CidrBlock: "10.0.0.0/8", Description: "internal"},
190195
}
191196

192-
out := text.PostgresUpdateDiff(before, after)
197+
out := text.PostgresUpdateDiff(postgresUpdateDiff(before, after))
193198
testassert.ContainsInOrder(t, out, "IP allow-list:", "(empty) → 1 entry")
194199
})
195200

@@ -198,7 +203,7 @@ func TestPostgresUpdateDiff(t *testing.T) {
198203
after := basicPostgres()
199204
after.ParameterOverrides = &client.PostgresParameterOverrides{"max_connections": "200"}
200205

201-
out := text.PostgresUpdateDiff(before, after)
206+
out := text.PostgresUpdateDiff(postgresUpdateDiff(before, after))
202207
testassert.ContainsInOrder(t, out, "Parameter overrides:", "updated")
203208
})
204209

@@ -208,7 +213,7 @@ func TestPostgresUpdateDiff(t *testing.T) {
208213
after.Name = "renamed"
209214
after.HighAvailabilityEnabled = true
210215

211-
out := text.PostgresUpdateDiff(before, after)
216+
out := text.PostgresUpdateDiff(postgresUpdateDiff(before, after))
212217
testassert.ContainsInOrder(t, out, "Name:", "High availability:")
213218

214219
// Only changed fields appear; untouched fields are omitted.

0 commit comments

Comments
 (0)