diff --git a/internal/applicationsnapshot/input.go b/internal/applicationsnapshot/input.go index c4f99aca6..16e83f970 100644 --- a/internal/applicationsnapshot/input.go +++ b/internal/applicationsnapshot/input.go @@ -183,15 +183,29 @@ 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 so we + // can read snapshot data correctly from a cluster record + 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 + } + + // If we didn't find a snapshot under the .spec top level key then + // assume we're looking at the bare snapshot data + 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 diff --git a/internal/applicationsnapshot/input_test.go b/internal/applicationsnapshot/input_test.go index 7a143a715..6bf8598fd 100644 --- a/internal/applicationsnapshot/input_test.go +++ b/internal/applicationsnapshot/input_test.go @@ -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) {