Skip to content

Commit 94f6f9c

Browse files
committed
Extract .spec from cluster record format for snapshot input
* Modify readSnapshotSource to automatically extract the .spec field when the input is a Kubernetes cluster record, matching the behavior for policy.yaml files. This allows direct use of kubectl output without manual jq extraction. * The change maintains backward compatibility with direct SnapshotSpec input and works with both JSON and YAML formats. resolves: EC-1534
1 parent 5c6d15a commit 94f6f9c

File tree

2 files changed

+114
-0
lines changed

2 files changed

+114
-0
lines changed

internal/applicationsnapshot/input.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,28 @@ func DetermineInputSpec(ctx context.Context, input Input) (*app.SnapshotSpec, *E
183183
}
184184

185185
func readSnapshotSource(input []byte) (app.SnapshotSpec, error) {
186+
var v map[string]interface{}
187+
188+
// Since JSON is a subset of YAML, yaml.Unmarshal can be used directly.
189+
if err := yaml.Unmarshal(input, &v); err != nil {
190+
log.Debugf("Problem parsing application snapshot from file %s", input)
191+
return app.SnapshotSpec{}, fmt.Errorf("unable to parse Snapshot specification from %s: %w", input, err)
192+
}
193+
194+
// Extract the "spec" key from YAML, if present, to use as the snapshot.
195+
if spec, ok := v["spec"]; ok {
196+
v, ok = spec.(map[string]interface{})
197+
if !ok {
198+
return app.SnapshotSpec{}, fmt.Errorf("spec is not a valid map structure")
199+
}
200+
// Marshal the spec back to bytes for unmarshaling into SnapshotSpec
201+
specBytes, err := yaml.Marshal(v)
202+
if err != nil {
203+
return app.SnapshotSpec{}, fmt.Errorf("unable to marshal spec: %w", err)
204+
}
205+
input = specBytes
206+
}
207+
186208
var file app.SnapshotSpec
187209
err := yaml.Unmarshal(input, &file)
188210
if err != nil {

internal/applicationsnapshot/input_test.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,98 @@ func TestReadSnapshotFile(t *testing.T) {
233233
expected := fmt.Errorf("unable to parse Snapshot specification from %s: %w", spec, wrapped)
234234
assert.Error(t, err, expected)
235235
})
236+
237+
t.Run("Snapshot with .spec wrapper (cluster record format)", func(t *testing.T) {
238+
snapshotSpec := app.SnapshotSpec{
239+
Components: []app.SnapshotComponent{
240+
{
241+
Name: "Named",
242+
ContainerImage: "registry.io/repository/image:tag",
243+
},
244+
},
245+
}
246+
fs := afero.NewMemMapFs()
247+
// Simulate a cluster record format with .spec wrapper
248+
clusterRecord := `{
249+
"apiVersion": "appstudio.redhat.com/v1alpha1",
250+
"kind": "Snapshot",
251+
"metadata": {
252+
"name": "vsa-demo-app-x9xln",
253+
"namespace": "user-ns2"
254+
},
255+
"spec": {
256+
"components": [
257+
{
258+
"name": "Named",
259+
"containerImage": "registry.io/repository/image:tag"
260+
}
261+
]
262+
}
263+
}`
264+
265+
err := afero.WriteFile(fs, "/cluster-record.json", []byte(clusterRecord), 0644)
266+
if err != nil {
267+
t.Fatalf("Setup failure: could not write file: %v", err)
268+
}
269+
270+
content, err := afero.ReadFile(fs, "/cluster-record.json")
271+
assert.NoError(t, err)
272+
got, err := readSnapshotSource(content)
273+
assert.NoError(t, err)
274+
assert.Equal(t, snapshotSpec, got)
275+
})
276+
277+
t.Run("Snapshot with .spec wrapper in YAML format", func(t *testing.T) {
278+
snapshotSpec := app.SnapshotSpec{
279+
Components: []app.SnapshotComponent{
280+
{
281+
Name: "Named",
282+
ContainerImage: "registry.io/repository/image:tag",
283+
},
284+
},
285+
}
286+
// Simulate a cluster record format with .spec wrapper in YAML
287+
clusterRecord := `apiVersion: appstudio.redhat.com/v1alpha1
288+
kind: Snapshot
289+
metadata:
290+
name: vsa-demo-app-x9xln
291+
namespace: user-ns2
292+
spec:
293+
components:
294+
- name: Named
295+
containerImage: registry.io/repository/image:tag
296+
`
297+
298+
content := []byte(clusterRecord)
299+
got, err := readSnapshotSource(content)
300+
assert.NoError(t, err)
301+
assert.Equal(t, snapshotSpec, got)
302+
})
303+
304+
t.Run("Snapshot without .spec wrapper (backward compatibility)", func(t *testing.T) {
305+
snapshotSpec := app.SnapshotSpec{
306+
Components: []app.SnapshotComponent{
307+
{
308+
Name: "Named",
309+
ContainerImage: "registry.io/repository/image:tag",
310+
},
311+
},
312+
}
313+
// Direct SnapshotSpec without wrapper (existing behavior)
314+
directSpec := `{
315+
"components": [
316+
{
317+
"name": "Named",
318+
"containerImage": "registry.io/repository/image:tag"
319+
}
320+
]
321+
}`
322+
323+
content := []byte(directSpec)
324+
got, err := readSnapshotSource(content)
325+
assert.NoError(t, err)
326+
assert.Equal(t, snapshotSpec, got)
327+
})
236328
}
237329

238330
func TestExpandImageIndex(t *testing.T) {

0 commit comments

Comments
 (0)