Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions internal/applicationsnapshot/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,15 +183,27 @@ func DetermineInputSpec(ctx context.Context, input Input) (*app.SnapshotSpec, *E
}

func readSnapshotSource(input []byte) (app.SnapshotSpec, error) {
var file app.SnapshotSpec
err := yaml.Unmarshal(input, &file)
if err != nil {
// Define a temporary struct to capture the wrapped spec
var wrapper struct {
Spec *app.SnapshotSpec `yaml:"spec"`
}

// Attempt to unmarshal into the wrapper to check for cluster record format
if err := yaml.Unmarshal(input, &wrapper); err == nil && wrapper.Spec != nil {
// If successful and spec exists, return it directly
log.Debugf("Read application snapshot from cluster record format")
return *wrapper.Spec, nil
}

// Fallback: unmarshal directly into SnapshotSpec for backward compatibility
var spec app.SnapshotSpec
if err := yaml.Unmarshal(input, &spec); err != nil {
log.Debugf("Problem parsing application snapshot from file %s", input)
return app.SnapshotSpec{}, fmt.Errorf("unable to parse Snapshot specification from %s: %w", input, err)
}

log.Debugf("Read application snapshot from file %s", input)
return file, nil
return spec, nil
}

// For an image index, remove the original component and replace it with an expanded component with all its image manifests
Expand Down
91 changes: 91 additions & 0 deletions internal/applicationsnapshot/input_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,97 @@ func TestReadSnapshotFile(t *testing.T) {
expected := fmt.Errorf("unable to parse Snapshot specification from %s: %w", spec, wrapped)
assert.Error(t, err, expected)
})

t.Run("Snapshot with .spec wrapper (cluster record format)", func(t *testing.T) {
snapshotSpec := app.SnapshotSpec{
Components: []app.SnapshotComponent{
{
Name: "Named",
ContainerImage: "registry.io/repository/image:tag",
},
},
}
fs := afero.NewMemMapFs()
// Simulate a cluster record format with .spec wrapper
clusterRecord := `{
"apiVersion": "appstudio.redhat.com/v1alpha1",
"kind": "Snapshot",
"metadata": {
"name": "vsa-demo-app-x9xln",
"namespace": "user-ns2"
},
"spec": {
"components": [
{
"name": "Named",
"containerImage": "registry.io/repository/image:tag"
}
]
}
}`

err := afero.WriteFile(fs, "/cluster-record.json", []byte(clusterRecord), 0644)
if err != nil {
t.Fatalf("Setup failure: could not write file: %v", err)
}

content, err := afero.ReadFile(fs, "/cluster-record.json")
assert.NoError(t, err)
got, err := readSnapshotSource(content)
assert.NoError(t, err)
assert.Equal(t, snapshotSpec, got)
})

t.Run("Snapshot with .spec wrapper in YAML format", func(t *testing.T) {
snapshotSpec := app.SnapshotSpec{
Components: []app.SnapshotComponent{
{
Name: "Named",
ContainerImage: "registry.io/repository/image:tag",
},
},
}
// Simulate a cluster record format with .spec wrapper in YAML
clusterRecord := `apiVersion: appstudio.redhat.com/v1alpha1
kind: Snapshot
metadata:
name: vsa-demo-app-x9xln
namespace: user-ns2
spec:
components:
- name: Named
containerImage: registry.io/repository/image:tag`

content := []byte(clusterRecord)
got, err := readSnapshotSource(content)
assert.NoError(t, err)
assert.Equal(t, snapshotSpec, got)
})

t.Run("Snapshot without .spec wrapper (backward compatibility)", func(t *testing.T) {
snapshotSpec := app.SnapshotSpec{
Components: []app.SnapshotComponent{
{
Name: "Named",
ContainerImage: "registry.io/repository/image:tag",
},
},
}
// Direct SnapshotSpec without wrapper (existing behavior)
directSpec := `{
"components": [
{
"name": "Named",
"containerImage": "registry.io/repository/image:tag"
}
]
}`

content := []byte(directSpec)
got, err := readSnapshotSource(content)
assert.NoError(t, err)
assert.Equal(t, snapshotSpec, got)
})
}

func TestExpandImageIndex(t *testing.T) {
Expand Down
Loading