Skip to content

Commit 272eac6

Browse files
committed
fix: panic in status
Signed-off-by: Timur Tuktamyshev <[email protected]>
1 parent add4537 commit 272eac6

File tree

2 files changed

+242
-5
lines changed

2 files changed

+242
-5
lines changed

internal/status/objects/releases/releases.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323

2424
"github.com/fatih/color"
2525
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2627
"k8s.io/apimachinery/pkg/runtime/schema"
2728
"k8s.io/client-go/dynamic"
2829

@@ -78,13 +79,22 @@ func getDeckhouseReleases(ctx context.Context, dynamicCl dynamic.Interface) ([]D
7879

7980
// Processing converts raw resource data into a structured format for easier output and analysis.
8081
func deckhouseReleaseProcessing(item map[string]interface{}, name string) (DeckhouseRelease, bool) {
81-
statusMap, ok := item["status"].(map[string]interface{})
82-
if !ok {
82+
if item == nil {
8383
return DeckhouseRelease{}, false
8484
}
85-
phase := statusMap["phase"].(string)
86-
transitionTime := statusMap["transitionTime"].(string)
87-
message := statusMap["message"].(string)
85+
86+
statusValue, exists := item["status"]
87+
if !exists || statusValue == nil {
88+
return DeckhouseRelease{}, false
89+
}
90+
91+
if _, ok := statusValue.(map[string]interface{}); !ok {
92+
return DeckhouseRelease{}, false
93+
}
94+
95+
phase, _, _ := unstructured.NestedString(item, "status", "phase")
96+
transitionTime, _, _ := unstructured.NestedString(item, "status", "transitionTime")
97+
message, _, _ := unstructured.NestedString(item, "status", "message")
8898

8999
return DeckhouseRelease{
90100
Name: name,
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
/*
2+
Copyright 2026 Flant JSC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package deckhousereleases
18+
19+
import (
20+
"testing"
21+
)
22+
23+
func TestDeckhouseReleaseProcessing(t *testing.T) {
24+
tests := []struct {
25+
name string
26+
item map[string]interface{}
27+
want DeckhouseRelease
28+
wantOk bool
29+
}{
30+
{
31+
name: "complete valid release",
32+
item: map[string]interface{}{
33+
"status": map[string]interface{}{
34+
"phase": "Deployed",
35+
"transitionTime": "2025-01-15T10:30:00Z",
36+
"message": "Release deployed successfully",
37+
},
38+
},
39+
want: DeckhouseRelease{
40+
Name: "test-release",
41+
Phase: "Deployed",
42+
TransitionTime: "2025-01-15T10:30:00Z",
43+
Message: "Release deployed successfully",
44+
},
45+
wantOk: true,
46+
},
47+
{
48+
name: "release with empty optional fields",
49+
item: map[string]interface{}{
50+
"status": map[string]interface{}{
51+
"phase": "Pending",
52+
},
53+
},
54+
want: DeckhouseRelease{
55+
Name: "test-release",
56+
Phase: "Pending",
57+
TransitionTime: "",
58+
Message: "",
59+
},
60+
wantOk: true,
61+
},
62+
{
63+
name: "release with partial fields",
64+
item: map[string]interface{}{
65+
"status": map[string]interface{}{
66+
"phase": "Superseded",
67+
"message": "Superseded by newer release",
68+
},
69+
},
70+
want: DeckhouseRelease{
71+
Name: "test-release",
72+
Phase: "Superseded",
73+
TransitionTime: "",
74+
Message: "Superseded by newer release",
75+
},
76+
wantOk: true,
77+
},
78+
{
79+
name: "nil item",
80+
item: nil,
81+
want: DeckhouseRelease{},
82+
wantOk: false,
83+
},
84+
{
85+
name: "missing status field",
86+
item: map[string]interface{}{
87+
"spec": map[string]interface{}{
88+
"version": "1.2.3",
89+
},
90+
},
91+
want: DeckhouseRelease{},
92+
wantOk: false,
93+
},
94+
{
95+
name: "status is nil",
96+
item: map[string]interface{}{
97+
"status": nil,
98+
},
99+
want: DeckhouseRelease{},
100+
wantOk: false,
101+
},
102+
{
103+
name: "status is not a map",
104+
item: map[string]interface{}{
105+
"status": "not-a-map",
106+
},
107+
want: DeckhouseRelease{},
108+
wantOk: false,
109+
},
110+
{
111+
name: "phase is not a string",
112+
item: map[string]interface{}{
113+
"status": map[string]interface{}{
114+
"phase": 123,
115+
},
116+
},
117+
want: DeckhouseRelease{
118+
Name: "test-release",
119+
Phase: "",
120+
TransitionTime: "",
121+
Message: "",
122+
},
123+
wantOk: true,
124+
},
125+
{
126+
name: "transitionTime is not a string",
127+
item: map[string]interface{}{
128+
"status": map[string]interface{}{
129+
"phase": "Deployed",
130+
"transitionTime": 1234567890,
131+
},
132+
},
133+
want: DeckhouseRelease{
134+
Name: "test-release",
135+
Phase: "Deployed",
136+
TransitionTime: "",
137+
Message: "",
138+
},
139+
wantOk: true,
140+
},
141+
{
142+
name: "message is not a string",
143+
item: map[string]interface{}{
144+
"status": map[string]interface{}{
145+
"phase": "Deployed",
146+
"message": []string{"not", "a", "string"},
147+
},
148+
},
149+
want: DeckhouseRelease{
150+
Name: "test-release",
151+
Phase: "Deployed",
152+
TransitionTime: "",
153+
Message: "",
154+
},
155+
wantOk: true,
156+
},
157+
{
158+
name: "phase value is nil",
159+
item: map[string]interface{}{
160+
"status": map[string]interface{}{
161+
"phase": nil,
162+
},
163+
},
164+
want: DeckhouseRelease{
165+
Name: "test-release",
166+
Phase: "",
167+
TransitionTime: "",
168+
Message: "",
169+
},
170+
wantOk: true,
171+
},
172+
{
173+
name: "empty status map",
174+
item: map[string]interface{}{
175+
"status": map[string]interface{}{},
176+
},
177+
want: DeckhouseRelease{
178+
Name: "test-release",
179+
Phase: "",
180+
TransitionTime: "",
181+
Message: "",
182+
},
183+
wantOk: true,
184+
},
185+
{
186+
name: "all fields with different types that should be ignored",
187+
item: map[string]interface{}{
188+
"status": map[string]interface{}{
189+
"phase": map[string]interface{}{"nested": "value"},
190+
"transitionTime": []int{1, 2, 3},
191+
"message": true,
192+
},
193+
},
194+
want: DeckhouseRelease{
195+
Name: "test-release",
196+
Phase: "",
197+
TransitionTime: "",
198+
Message: "",
199+
},
200+
wantOk: true,
201+
},
202+
}
203+
204+
for _, tt := range tests {
205+
t.Run(tt.name, func(t *testing.T) {
206+
got, gotOk := deckhouseReleaseProcessing(tt.item, "test-release")
207+
if gotOk != tt.wantOk {
208+
t.Errorf("deckhouseReleaseProcessing() gotOk = %v, want %v", gotOk, tt.wantOk)
209+
return
210+
}
211+
if gotOk {
212+
if got.Name != tt.want.Name {
213+
t.Errorf("deckhouseReleaseProcessing() Name = %v, want %v", got.Name, tt.want.Name)
214+
}
215+
if got.Phase != tt.want.Phase {
216+
t.Errorf("deckhouseReleaseProcessing() Phase = %v, want %v", got.Phase, tt.want.Phase)
217+
}
218+
if got.TransitionTime != tt.want.TransitionTime {
219+
t.Errorf("deckhouseReleaseProcessing() TransitionTime = %v, want %v", got.TransitionTime, tt.want.TransitionTime)
220+
}
221+
if got.Message != tt.want.Message {
222+
t.Errorf("deckhouseReleaseProcessing() Message = %v, want %v", got.Message, tt.want.Message)
223+
}
224+
}
225+
})
226+
}
227+
}

0 commit comments

Comments
 (0)