Skip to content

Commit fdc925f

Browse files
authored
fix(observability): add nil pointer checks for cmd outputs (#617)
relates to STACKITCLI-106
1 parent 22b1f74 commit fdc925f

File tree

16 files changed

+505
-31
lines changed

16 files changed

+505
-31
lines changed

internal/cmd/observability/credentials/create/create.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func NewCmd(p *print.Printer) *cobra.Command {
7979
return fmt.Errorf("create credentials for Observability instance: %w", err)
8080
}
8181

82-
return outputResult(p, model, instanceLabel, resp)
82+
return outputResult(p, model.OutputFormat, instanceLabel, resp)
8383
},
8484
}
8585
configureFlags(cmd)
@@ -110,8 +110,12 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *observabili
110110
return req
111111
}
112112

113-
func outputResult(p *print.Printer, model *inputModel, instanceLabel string, resp *observability.CreateCredentialsResponse) error {
114-
switch model.OutputFormat {
113+
func outputResult(p *print.Printer, outputFormat, instanceLabel string, resp *observability.CreateCredentialsResponse) error {
114+
if resp == nil {
115+
return fmt.Errorf("response is nil")
116+
}
117+
118+
switch outputFormat {
115119
case print.JSONOutputFormat:
116120
details, err := json.MarshalIndent(resp, "", " ")
117121
if err != nil {
@@ -130,13 +134,14 @@ func outputResult(p *print.Printer, model *inputModel, instanceLabel string, res
130134
return nil
131135
default:
132136
p.Outputf("Created credentials for instance %q.\n\n", instanceLabel)
133-
// The username field cannot be set by the user, so we only display it if it's not returned empty
134-
username := *resp.Credentials.Username
135-
if username != "" {
136-
p.Outputf("Username: %s\n", username)
137-
}
138137

139138
if resp.Credentials != nil {
139+
// The username field cannot be set by the user, so we only display it if it's not returned empty
140+
username := *resp.Credentials.Username
141+
if username != "" {
142+
p.Outputf("Username: %s\n", username)
143+
}
144+
140145
p.Outputf("Password: %s\n", utils.PtrString(resp.Credentials.Password))
141146
}
142147
return nil

internal/cmd/observability/credentials/create/create_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"testing"
66

77
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
8+
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
9+
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
810
"github.com/stackitcloud/stackit-sdk-go/services/observability"
911

1012
"github.com/google/go-cmp/cmp"
@@ -188,3 +190,47 @@ func TestBuildRequest(t *testing.T) {
188190
})
189191
}
190192
}
193+
194+
func TestOutputResult(t *testing.T) {
195+
type args struct {
196+
outputFormat string
197+
instanceLabel string
198+
resp *observability.CreateCredentialsResponse
199+
}
200+
tests := []struct {
201+
name string
202+
args args
203+
wantErr bool
204+
}{
205+
{
206+
name: "empty",
207+
args: args{},
208+
wantErr: true,
209+
},
210+
{
211+
name: "set empty response",
212+
args: args{
213+
resp: &observability.CreateCredentialsResponse{},
214+
},
215+
wantErr: false,
216+
},
217+
{
218+
name: "set response with credentials",
219+
args: args{
220+
resp: &observability.CreateCredentialsResponse{
221+
Credentials: observability.NewCredentials(utils.Ptr("dummy-pw"), utils.Ptr("dummy-user")),
222+
},
223+
},
224+
wantErr: false,
225+
},
226+
}
227+
p := print.NewPrinter()
228+
p.Cmd = NewCmd(p)
229+
for _, tt := range tests {
230+
t.Run(tt.name, func(t *testing.T) {
231+
if err := outputResult(p, tt.args.outputFormat, tt.args.instanceLabel, tt.args.resp); (err != nil) != tt.wantErr {
232+
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
233+
}
234+
})
235+
}
236+
}

internal/cmd/observability/credentials/list/list_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"testing"
66

77
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
8+
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
89
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
910

1011
"github.com/google/go-cmp/cmp"
@@ -205,3 +206,44 @@ func TestBuildRequest(t *testing.T) {
205206
})
206207
}
207208
}
209+
210+
func TestOutputResult(t *testing.T) {
211+
type args struct {
212+
outputFormat string
213+
credentials []observability.ServiceKeysList
214+
}
215+
tests := []struct {
216+
name string
217+
args args
218+
wantErr bool
219+
}{
220+
{
221+
name: "empty",
222+
args: args{},
223+
wantErr: false,
224+
},
225+
{
226+
name: "set empty credentials slice",
227+
args: args{
228+
credentials: []observability.ServiceKeysList{},
229+
},
230+
wantErr: false,
231+
},
232+
{
233+
name: "set empty credential in credentials slice",
234+
args: args{
235+
credentials: []observability.ServiceKeysList{{}},
236+
},
237+
wantErr: false,
238+
},
239+
}
240+
p := print.NewPrinter()
241+
p.Cmd = NewCmd(p)
242+
for _, tt := range tests {
243+
t.Run(tt.name, func(t *testing.T) {
244+
if err := outputResult(p, tt.args.outputFormat, tt.args.credentials); (err != nil) != tt.wantErr {
245+
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
246+
}
247+
})
248+
}
249+
}

internal/cmd/observability/grafana/describe/describe.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func NewCmd(p *print.Printer) *cobra.Command {
7777
return fmt.Errorf("get instance: %w", err)
7878
}
7979

80-
return outputResult(p, model, grafanaConfigsResp, instanceResp)
80+
return outputResult(p, model.OutputFormat, model.ShowPassword, grafanaConfigsResp, instanceResp)
8181
},
8282
}
8383
configureFlags(cmd)
@@ -124,8 +124,14 @@ func buildGetInstanceRequest(ctx context.Context, model *inputModel, apiClient *
124124
return req
125125
}
126126

127-
func outputResult(p *print.Printer, inputModel *inputModel, grafanaConfigs *observability.GrafanaConfigs, instance *observability.GetInstanceResponse) error {
128-
switch inputModel.OutputFormat {
127+
func outputResult(p *print.Printer, outputFormat string, showPassword bool, grafanaConfigs *observability.GrafanaConfigs, instance *observability.GetInstanceResponse) error {
128+
if instance == nil || instance.Instance == nil {
129+
return fmt.Errorf("instance or instance content is nil")
130+
} else if grafanaConfigs == nil {
131+
return fmt.Errorf("grafanaConfigs is nil")
132+
}
133+
134+
switch outputFormat {
129135
case print.JSONOutputFormat:
130136
details, err := json.MarshalIndent(grafanaConfigs, "", " ")
131137
if err != nil {
@@ -143,8 +149,8 @@ func outputResult(p *print.Printer, inputModel *inputModel, grafanaConfigs *obse
143149

144150
return nil
145151
default:
146-
initialAdminPassword := *instance.Instance.GrafanaAdminPassword
147-
if !inputModel.ShowPassword {
152+
initialAdminPassword := utils.PtrString(instance.Instance.GrafanaAdminPassword)
153+
if !showPassword {
148154
initialAdminPassword = "<hidden>"
149155
}
150156

internal/cmd/observability/grafana/describe/describe_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,3 +276,58 @@ func TestBuildGetInstanceRequest(t *testing.T) {
276276
})
277277
}
278278
}
279+
280+
func TestOutputResult(t *testing.T) {
281+
type args struct {
282+
outputFormat string
283+
showPassword bool
284+
grafanaConfig *observability.GrafanaConfigs
285+
instance *observability.GetInstanceResponse
286+
}
287+
tests := []struct {
288+
name string
289+
args args
290+
wantErr bool
291+
}{
292+
{
293+
name: "empty",
294+
args: args{},
295+
wantErr: true,
296+
},
297+
{
298+
name: "set grafana configs but no instance",
299+
args: args{
300+
grafanaConfig: &observability.GrafanaConfigs{},
301+
},
302+
wantErr: true,
303+
},
304+
{
305+
name: "set instance but no grafana config",
306+
args: args{
307+
instance: &observability.GetInstanceResponse{
308+
Instance: &observability.InstanceSensitiveData{},
309+
},
310+
},
311+
wantErr: true,
312+
},
313+
{
314+
name: "set instance and grafana configs",
315+
args: args{
316+
grafanaConfig: &observability.GrafanaConfigs{},
317+
instance: &observability.GetInstanceResponse{
318+
Instance: &observability.InstanceSensitiveData{},
319+
},
320+
},
321+
wantErr: false,
322+
},
323+
}
324+
p := print.NewPrinter()
325+
p.Cmd = NewCmd(p)
326+
for _, tt := range tests {
327+
t.Run(tt.name, func(t *testing.T) {
328+
if err := outputResult(p, tt.args.outputFormat, tt.args.showPassword, tt.args.grafanaConfig, tt.args.instance); (err != nil) != tt.wantErr {
329+
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
330+
}
331+
})
332+
}
333+
}

internal/cmd/observability/instance/create/create.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ func NewCmd(p *print.Printer) *cobra.Command {
105105
s.Stop()
106106
}
107107

108-
return outputResult(p, model, projectLabel, resp)
108+
return outputResult(p, model.OutputFormat, model.Async, projectLabel, resp)
109109
},
110110
}
111111
configureFlags(cmd)
@@ -200,8 +200,12 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient observabilit
200200
return req, nil
201201
}
202202

203-
func outputResult(p *print.Printer, model *inputModel, projectLabel string, resp *observability.CreateInstanceResponse) error {
204-
switch model.OutputFormat {
203+
func outputResult(p *print.Printer, outputFormat string, async bool, projectLabel string, resp *observability.CreateInstanceResponse) error {
204+
if resp == nil {
205+
return fmt.Errorf("resp is empty")
206+
}
207+
208+
switch outputFormat {
205209
case print.JSONOutputFormat:
206210
details, err := json.MarshalIndent(resp, "", " ")
207211
if err != nil {
@@ -220,7 +224,7 @@ func outputResult(p *print.Printer, model *inputModel, projectLabel string, resp
220224
return nil
221225
default:
222226
operationState := "Created"
223-
if model.Async {
227+
if async {
224228
operationState = "Triggered creation of"
225229
}
226230
p.Outputf("%s instance for project %q. Instance ID: %s\n", operationState, projectLabel, utils.PtrString(resp.InstanceId))

internal/cmd/observability/instance/create/create_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,3 +334,39 @@ func TestBuildRequest(t *testing.T) {
334334
})
335335
}
336336
}
337+
338+
func TestOutputResult(t *testing.T) {
339+
type args struct {
340+
outputFormat string
341+
async bool
342+
projectLabel string
343+
resp *observability.CreateInstanceResponse
344+
}
345+
tests := []struct {
346+
name string
347+
args args
348+
wantErr bool
349+
}{
350+
{
351+
name: "empty",
352+
args: args{},
353+
wantErr: true,
354+
},
355+
{
356+
name: "empty response",
357+
args: args{
358+
resp: &observability.CreateInstanceResponse{},
359+
},
360+
wantErr: false,
361+
},
362+
}
363+
p := print.NewPrinter()
364+
p.Cmd = NewCmd(p)
365+
for _, tt := range tests {
366+
t.Run(tt.name, func(t *testing.T) {
367+
if err := outputResult(p, tt.args.outputFormat, tt.args.async, tt.args.projectLabel, tt.args.resp); (err != nil) != tt.wantErr {
368+
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
369+
}
370+
})
371+
}
372+
}

internal/cmd/observability/instance/describe/describe.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *observabili
9898
}
9999

100100
func outputResult(p *print.Printer, outputFormat string, instance *observability.GetInstanceResponse) error {
101+
if instance == nil {
102+
return fmt.Errorf("instance is nil")
103+
}
104+
101105
switch outputFormat {
102106
case print.JSONOutputFormat:
103107
details, err := json.MarshalIndent(instance, "", " ")

internal/cmd/observability/instance/describe/describe_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,37 @@ func TestBuildRequest(t *testing.T) {
216216
})
217217
}
218218
}
219+
220+
func TestOutputResult(t *testing.T) {
221+
type args struct {
222+
outputFormat string
223+
instance *observability.GetInstanceResponse
224+
}
225+
tests := []struct {
226+
name string
227+
args args
228+
wantErr bool
229+
}{
230+
{
231+
name: "empty",
232+
args: args{},
233+
wantErr: true,
234+
},
235+
{
236+
name: "empty instance",
237+
args: args{
238+
instance: &observability.GetInstanceResponse{},
239+
},
240+
wantErr: false,
241+
},
242+
}
243+
p := print.NewPrinter()
244+
p.Cmd = NewCmd(p)
245+
for _, tt := range tests {
246+
t.Run(tt.name, func(t *testing.T) {
247+
if err := outputResult(p, tt.args.outputFormat, tt.args.instance); (err != nil) != tt.wantErr {
248+
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
249+
}
250+
})
251+
}
252+
}

0 commit comments

Comments
 (0)