Skip to content

Commit 061c85a

Browse files
sravyanimmalab-lnimmalaCopilot
authored
Fixes: ARO-25422 handle nil document returned from CosmosDB in _putOrPatchOpenShiftCluster (#4830)
* fix: handle nil document returned from CosmosDB in Create and Update * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix:handle nil document returned from CosmosDB in create and update * Both Create and Update now follow the same pattern for handling CosmosDB's nil return case * add defensive nil checkfrontend for nil doc * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix(ARO-25422): fix Content-Type check in CosmosDB client causing silent nil,nil returns * reverting the unintended changes * bump go-cosmosdb dependency to pick up Content-Type fix * Replace error message to include partition key --------- Co-authored-by: b-lnimmala <b-lnimmala@microsoft.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent eda8ff1 commit 061c85a

8 files changed

Lines changed: 302 additions & 10 deletions

File tree

.bingo/Variables.mk

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,11 @@ $(FIPS_DETECT): $(BINGO_DIR)/fips-detect.mod
5353
@echo "(re)installing $(GOBIN)/fips-detect-v0.0.0-20230309083406-7157dae5bafd"
5454
@cd $(BINGO_DIR) && GOWORK=off GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) GOARM=$(GOHOSTARM) $(GO) build -mod=mod -modfile=fips-detect.mod -o=$(GOBIN)/fips-detect-v0.0.0-20230309083406-7157dae5bafd "github.com/acardace/fips-detect"
5555

56-
GENCOSMOSDB := $(GOBIN)/gencosmosdb-v0.0.0-20260318032027-4ed41099e61c
56+
GENCOSMOSDB := $(GOBIN)/gencosmosdb-v0.0.0-20260603170134-b6a927df2721
5757
$(GENCOSMOSDB): $(BINGO_DIR)/gencosmosdb.mod
5858
@# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies.
59-
@echo "(re)installing $(GOBIN)/gencosmosdb-v0.0.0-20260318032027-4ed41099e61c"
60-
@cd $(BINGO_DIR) && GOWORK=off GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) GOARM=$(GOHOSTARM) $(GO) build -mod=mod -modfile=gencosmosdb.mod -o=$(GOBIN)/gencosmosdb-v0.0.0-20260318032027-4ed41099e61c "github.com/bennerv/go-cosmosdb/cmd/gencosmosdb"
59+
@echo "(re)installing $(GOBIN)/gencosmosdb-v0.0.0-20260603170134-b6a927df2721"
60+
@cd $(BINGO_DIR) && GOWORK=off GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) GOARM=$(GOHOSTARM) $(GO) build -mod=mod -modfile=gencosmosdb.mod -o=$(GOBIN)/gencosmosdb-v0.0.0-20260603170134-b6a927df2721 "github.com/bennerv/go-cosmosdb/cmd/gencosmosdb"
6161

6262
GO_BINDATA := $(GOBIN)/go-bindata-v3.1.2+incompatible
6363
$(GO_BINDATA): $(BINGO_DIR)/go-bindata.mod

.bingo/gencosmosdb.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT
22

33
go 1.25.3
44

5-
require github.com/bennerv/go-cosmosdb v0.0.0-20260318032027-4ed41099e61c // cmd/gencosmosdb
5+
require github.com/bennerv/go-cosmosdb v0.0.0-20260603170134-b6a927df2721 // cmd/gencosmosdb

.bingo/gencosmosdb.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ github.com/bennerv/go-cosmosdb v0.0.0-20260313173900-2523aa951bd1 h1:FKiA++jcX4S
66
github.com/bennerv/go-cosmosdb v0.0.0-20260313173900-2523aa951bd1/go.mod h1:4bSvv83quaEsAsIBxUGczXmMep28F+/pi7mbSoZkBxs=
77
github.com/bennerv/go-cosmosdb v0.0.0-20260318032027-4ed41099e61c h1:qsHNAeWS0eKexZ5tgu9D8V6hVPOYlHWVBOzaa6IQHLs=
88
github.com/bennerv/go-cosmosdb v0.0.0-20260318032027-4ed41099e61c/go.mod h1:4bSvv83quaEsAsIBxUGczXmMep28F+/pi7mbSoZkBxs=
9+
github.com/bennerv/go-cosmosdb v0.0.0-20260603170134-b6a927df2721 h1:ZpERebog5YjwG4wDBBF97190/upAkiZHsrNZMewDvVc=
10+
github.com/bennerv/go-cosmosdb v0.0.0-20260603170134-b6a927df2721/go.mod h1:4bSvv83quaEsAsIBxUGczXmMep28F+/pi7mbSoZkBxs=

.bingo/variables.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ ENUMER="${GOBIN}/enumer-v1.5.11"
1818

1919
FIPS_DETECT="${GOBIN}/fips-detect-v0.0.0-20230309083406-7157dae5bafd"
2020

21-
GENCOSMOSDB="${GOBIN}/gencosmosdb-v0.0.0-20260318032027-4ed41099e61c"
21+
GENCOSMOSDB="${GOBIN}/gencosmosdb-v0.0.0-20260603170134-b6a927df2721"
2222

2323
GO_BINDATA="${GOBIN}/go-bindata-v3.1.2+incompatible"
2424

pkg/database/cosmosdb/zz_generated_cosmosdb.go

Lines changed: 9 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/database/cosmosdb/zz_generated_cosmosdb_test.go

Lines changed: 141 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/database/openshiftclusters.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,16 @@ func (c *openShiftClusters) Create(ctx context.Context, doc *api.OpenShiftCluste
9393
return nil, err
9494
}
9595

96-
doc, err = c.c.Create(ctx, doc.PartitionKey, doc, nil)
96+
key := doc.Key
97+
partitionKey := doc.PartitionKey
98+
doc, err = c.c.Create(ctx, partitionKey, doc, nil)
99+
100+
if doc == nil && err == nil {
101+
return nil, &cosmosdb.Error{
102+
StatusCode: http.StatusInternalServerError,
103+
Message: fmt.Sprintf("creating OpenShift cluster: CosmosDB returned nil document with no error for key %q and partition key %q", key, partitionKey),
104+
}
105+
}
97106

98107
if err, ok := err.(*cosmosdb.Error); ok && err.StatusCode == http.StatusConflict {
99108
err.StatusCode = http.StatusPreconditionFailed
@@ -214,7 +223,15 @@ func (c *openShiftClusters) update(ctx context.Context, doc *api.OpenShiftCluste
214223
return nil, fmt.Errorf("key %q is not lower case", doc.Key)
215224
}
216225

217-
return c.c.Replace(ctx, doc.PartitionKey, doc, options)
226+
updatedDoc, err := c.c.Replace(ctx, doc.PartitionKey, doc, options)
227+
if updatedDoc == nil && err == nil {
228+
return nil, &cosmosdb.Error{
229+
StatusCode: http.StatusInternalServerError,
230+
Message: fmt.Sprintf("OpenShiftClusters Replace returned nil document with nil error for key %q and partition key %q", doc.Key, doc.PartitionKey),
231+
}
232+
}
233+
234+
return updatedDoc, err
218235
}
219236

220237
func (c *openShiftClusters) Delete(ctx context.Context, doc *api.OpenShiftClusterDocument) error {
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package database
2+
3+
// Copyright (c) Microsoft Corporation.
4+
// Licensed under the Apache License 2.0.
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"net/http"
10+
"testing"
11+
12+
"github.com/Azure/ARO-RP/pkg/api"
13+
"github.com/Azure/ARO-RP/pkg/database/cosmosdb"
14+
"github.com/Azure/ARO-RP/pkg/util/uuid"
15+
)
16+
17+
// mockNilReturningClient is a minimal mock that returns (nil, nil) from Create and Replace
18+
// to test the regression where CosmosDB could return (nil, nil) and we didn't handle it
19+
type mockNilReturningClient struct {
20+
cosmosdb.OpenShiftClusterDocumentClient
21+
returnNilNil bool
22+
}
23+
24+
func (m *mockNilReturningClient) Create(ctx context.Context, partitionKey string, doc *api.OpenShiftClusterDocument, options *cosmosdb.Options) (*api.OpenShiftClusterDocument, error) {
25+
if m.returnNilNil {
26+
return nil, nil
27+
}
28+
return doc, nil
29+
}
30+
31+
func (m *mockNilReturningClient) Replace(ctx context.Context, partitionKey string, doc *api.OpenShiftClusterDocument, options *cosmosdb.Options) (*api.OpenShiftClusterDocument, error) {
32+
if m.returnNilNil {
33+
return nil, nil
34+
}
35+
return doc, nil
36+
}
37+
38+
// TestCreateReturnsErrorWhenCosmosDBReturnsNilNil verifies that when the CosmosDB client
39+
// returns (nil, nil) from Create, the wrapper returns a proper error instead of (nil, nil).
40+
// This is a regression test for a bug where CosmosDB could return (nil, nil) and we would
41+
// propagate it to callers, leading to nil pointer dereferences.
42+
func TestCreateReturnsErrorWhenCosmosDBReturnsNilNil(t *testing.T) {
43+
ctx := context.Background()
44+
45+
mockClient := &mockNilReturningClient{returnNilNil: true}
46+
db := NewOpenShiftClustersWithProvidedClient(mockClient, nil, "test-uuid", uuid.DefaultGenerator)
47+
48+
doc := &api.OpenShiftClusterDocument{
49+
Key: "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resourcegroup/providers/microsoft.redhatopenshift/openshiftclusters/cluster",
50+
PartitionKey: "00000000-0000-0000-0000-000000000000",
51+
OpenShiftCluster: &api.OpenShiftCluster{
52+
ID: "test-id",
53+
},
54+
}
55+
56+
result, err := db.Create(ctx, doc)
57+
58+
// Verify we get an error instead of (nil, nil)
59+
if err == nil {
60+
t.Fatal("expected error when CosmosDB returns (nil, nil), got nil")
61+
}
62+
63+
if result != nil {
64+
t.Fatalf("expected nil result when error occurs, got %v", result)
65+
}
66+
67+
cosmosErr, ok := err.(*cosmosdb.Error)
68+
if !ok {
69+
t.Fatalf("expected *cosmosdb.Error, got %T", err)
70+
}
71+
72+
if cosmosErr.StatusCode != http.StatusInternalServerError {
73+
t.Errorf("expected status code 500, got %d", cosmosErr.StatusCode)
74+
}
75+
76+
// Verify the error message contains descriptive text
77+
expectedMsg := fmt.Sprintf("creating OpenShift cluster: CosmosDB returned nil document with no error for key %q and partition key %q", doc.Key, doc.PartitionKey)
78+
if cosmosErr.Message != expectedMsg {
79+
t.Errorf("expected error message %q, got %q", expectedMsg, cosmosErr.Message)
80+
}
81+
}
82+
83+
// TestUpdateReturnsErrorWhenCosmosDBReturnsNilNil verifies that when the CosmosDB client
84+
// returns (nil, nil) from Replace, the wrapper returns a proper error instead of (nil, nil).
85+
// This is a regression test for a bug where CosmosDB could return (nil, nil) and we would
86+
// propagate it to callers, leading to nil pointer dereferences.
87+
func TestUpdateReturnsErrorWhenCosmosDBReturnsNilNil(t *testing.T) {
88+
ctx := context.Background()
89+
90+
mockClient := &mockNilReturningClient{returnNilNil: true}
91+
db := NewOpenShiftClustersWithProvidedClient(mockClient, nil, "test-uuid", uuid.DefaultGenerator)
92+
93+
doc := &api.OpenShiftClusterDocument{
94+
Key: "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resourcegroup/providers/microsoft.redhatopenshift/openshiftclusters/cluster",
95+
PartitionKey: "00000000-0000-0000-0000-000000000000",
96+
OpenShiftCluster: &api.OpenShiftCluster{
97+
ID: "test-id",
98+
},
99+
}
100+
101+
result, err := db.Update(ctx, doc)
102+
103+
// Verify we get an error instead of (nil, nil)
104+
if err == nil {
105+
t.Fatal("expected error when CosmosDB returns (nil, nil), got nil")
106+
}
107+
108+
if result != nil {
109+
t.Fatalf("expected nil result when error occurs, got %v", result)
110+
}
111+
112+
cosmosErr, ok := err.(*cosmosdb.Error)
113+
if !ok {
114+
t.Fatalf("expected *cosmosdb.Error, got %T", err)
115+
}
116+
117+
if cosmosErr.StatusCode != http.StatusInternalServerError {
118+
t.Errorf("expected status code 500, got %d", cosmosErr.StatusCode)
119+
}
120+
121+
// Verify the error message contains descriptive text
122+
expectedMsg := fmt.Sprintf("OpenShiftClusters Replace returned nil document with nil error for key %q and partition key %q", doc.Key, doc.PartitionKey)
123+
if cosmosErr.Message != expectedMsg {
124+
t.Errorf("expected error message %q, got %q", expectedMsg, cosmosErr.Message)
125+
}
126+
}

0 commit comments

Comments
 (0)