Repository: osbuild/osbuild-composer
Pull request #3251 (2023-01-30 04:07:59)

build(deps): bump ludeeus/action-shellcheck from 6d3f514f44620b9d4488e380339edc0d9bbe2fba to 00cae500b08a931fb5698e11e79bfbd38e612a38
Bumps [ludeeus/action-shellcheck](https://github.com/ludeeus/action-shellcheck) from 6d3f514f44620b9d4488e380339edc0d9bbe2fba to 00cae500b08a931fb5698e11e79bfbd38e612a38.
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/ludeeus/action-shellcheck/commit/00cae500b08a931fb5698e11e79bfbd38e612a38"><code>00cae50</code></a> Remove non-working problem matchers. (<a href="https://github-redirect.dependabot.com/ludeeus/action-shellcheck/issues/88">#88</a>)</li>
<li><a href="https://github.com/ludeeus/action-shellcheck/commit/84dc45371e29bd549416032458f8de9d0e326f4a"><code>84dc453</code></a> Remove problem matchers before exit (<a href="https://github-redirect.dependabot.com/ludeeus/action-shellcheck/issues/86">#86</a>)</li>
<li><a href="https://github.com/ludeeus/action-shellcheck/commit/e07831dc6f4e690ebbc6d87e878b483863bfc66e"><code>e07831d</code></a> Set 0.9.0 as example version (<a href="https://github-redirect.dependabot.com/ludeeus/action-shellcheck/issues/85">#85</a>)</li>
<li><a href="https://github.com/ludeeus/action-shellcheck/commit/c4b2ed0185e759b941efabd37a2eb23b8ce64c18"><code>c4b2ed0</code></a> Add basic devcontainer (<a href="https://github-redirect.dependabot.com/ludeeus/action-shellcheck/issues/84">#84</a>)</li>
<li><a href="https://github.com/ludeeus/action-shellcheck/commit/e5c9260d1115f89914b306831f831def43d2dc2d"><code>e5c9260</code></a> README: suggest use of line folding (<a href="https://github-redirect.dependabot.com/ludeeus/action-shellcheck/issues/82">#82</a>)</li>
<li>See full diff in <a href="https://github.com/ludeeus/action-shellcheck/compare/6d3f514f44620b9d4488e380339edc0d9bbe2fba...00cae500b08a931fb5698e11e79bfbd38e612a38">compare view</a></li>
</ul>
</details>
<br />


You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/internal/manifest/coi_iso_tree.go b/internal/manifest/coi_iso_tree.go
index 7ba3d5ae8..0ef5e0b23 100644
--- a/internal/manifest/coi_iso_tree.go
+++ b/internal/manifest/coi_iso_tree.go
@@ -64,7 +64,7 @@ func (p *CoreOSISOTree) serialize() osbuild.Pipeline {
 				},
 			},
 		},
-		osbuild.NewFilesInputs(osbuild.NewFilesInputReferencesPipeline(p.payloadPipeline.Name(), p.payloadPipeline.Filename)),
+		osbuild.NewXzStageInputs(osbuild.NewFilesInputPipelineObjectRef(p.payloadPipeline.Name(), p.payloadPipeline.Filename, nil)),
 	))
 
 	if p.coiPipeline.Ignition != nil {
diff --git a/internal/manifest/xz.go b/internal/manifest/xz.go
index e02f47b1d..2c9bea16e 100644
--- a/internal/manifest/xz.go
+++ b/internal/manifest/xz.go
@@ -33,7 +33,7 @@ func (p *XZ) serialize() osbuild.Pipeline {
 
 	pipeline.AddStage(osbuild.NewXzStage(
 		osbuild.NewXzStageOptions(p.Filename),
-		osbuild.NewFilesInputs(osbuild.NewFilesInputReferencesPipeline(p.imgPipeline.Name(), p.imgPipeline.Export().Filename())),
+		osbuild.NewXzStageInputs(osbuild.NewFilesInputPipelineObjectRef(p.imgPipeline.Name(), p.imgPipeline.Export().Filename(), nil)),
 	))
 
 	return pipeline
diff --git a/internal/osbuild/fdo_stage.go b/internal/osbuild/fdo_stage.go
index 42cd77183..e5fa8d09a 100644
--- a/internal/osbuild/fdo_stage.go
+++ b/internal/osbuild/fdo_stage.go
@@ -5,33 +5,18 @@ import (
 	"fmt"
 )
 
-type FDOStageReferences []string
-
-func (FDOStageReferences) isReferences() {}
-
-type FDOStageInput struct {
-	inputCommon
-	References FDOStageReferences `json:"references"`
-}
-
-func (FDOStageInput) isStageInput() {}
-
 type FDOStageInputs struct {
-	RootCerts *FDOStageInput `json:"rootcerts"`
+	RootCerts *FilesInput `json:"rootcerts"`
 }
 
 func (FDOStageInputs) isStageInputs() {}
 
 // NewFDOStageForCert creates FDOStage
 func NewFDOStageForRootCerts(rootCertsData string) *Stage {
-
 	dataBytes := []byte(rootCertsData)
-	rootCertsInputHash := fmt.Sprintf("sha256:%x", sha256.Sum256(dataBytes))
-
-	input := new(FDOStageInput)
-	input.Type = "org.osbuild.files"
-	input.Origin = "org.osbuild.source"
-	input.References = FDOStageReferences{rootCertsInputHash}
+	input := NewFilesInput(NewFilesInputSourcePlainRef([]string{
+		fmt.Sprintf("%x", sha256.Sum256(dataBytes)),
+	}))
 
 	return &Stage{
 		Type:   "org.osbuild.fdo",
diff --git a/internal/osbuild/files_input.go b/internal/osbuild/files_input.go
index 360b76b65..64f685bf4 100644
--- a/internal/osbuild/files_input.go
+++ b/internal/osbuild/files_input.go
@@ -5,41 +5,24 @@ import (
 	"fmt"
 )
 
-// Inputs for individual files
-
-type FilesInputs struct {
-	File *FilesInput `json:"file"`
-}
-
-func (FilesInputs) isStageInputs() {}
-
-func NewFilesInputs(references FilesInputReferences) *FilesInputs {
-	return &FilesInputs{
-		File: NewFilesInput(references),
-	}
-}
-
-// IMPLEMENTED INTERFACES OF STAGES ACCEPTING THIS INPUTS TYPE
-
-// inputs accepted by the XZ stage
-func (FilesInputs) isXzStageInputs() {}
-
 // SPECIFIC INPUT STRUCTURE
 
 type FilesInput struct {
 	inputCommon
-	References FilesInputReferences `json:"references"`
+	References FilesInputRef `json:"references"`
 }
 
 const InputTypeFiles string = "org.osbuild.files"
 
-func NewFilesInput(references FilesInputReferences) *FilesInput {
+func NewFilesInput(references FilesInputRef) *FilesInput {
 	input := new(FilesInput)
 	input.Type = InputTypeFiles
 
 	switch t := references.(type) {
-	case *FilesInputReferencesPipeline:
+	case *FilesInputPipelineArrayRef, *FilesInputPipelineObjectRef:
 		input.Origin = InputOriginPipeline
+	case *FilesInputSourcePlainRef, *FilesInputSourceArrayRef, *FilesInputSourceObjectRef:
+		input.Origin = InputOriginSource
 	default:
 		panic(fmt.Sprintf("unknown FilesInputReferences type: %v", t))
 	}
@@ -60,46 +43,236 @@ func (f *FilesInput) UnmarshalJSON(data []byte) error {
 		return err
 	}
 
-	var ref FilesInputReferences
 	switch rawFilesInput.Origin {
 	case InputOriginPipeline:
-		ref = &FilesInputReferencesPipeline{}
+		possibleRefs := []FilesInputRef{
+			&FilesInputPipelineArrayRef{},
+			&FilesInputPipelineObjectRef{},
+		}
+		var err error
+		for _, ref := range possibleRefs {
+			if err = json.Unmarshal(rawFilesInput.References, ref); err == nil {
+				f.References = ref
+				break
+			}
+		}
+		if err != nil {
+			return fmt.Errorf("FilesInput: failed to unmarshal pipeline references into any supported type")
+		}
+	case InputOriginSource:
+		possibleRefs := []FilesInputRef{
+			&FilesInputSourcePlainRef{},
+			&FilesInputSourceArrayRef{},
+			&FilesInputSourceObjectRef{},
+		}
+		var err error
+		for _, ref := range possibleRefs {
+			if err = json.Unmarshal(rawFilesInput.References, ref); err == nil {
+				f.References = ref
+				break
+			}
+		}
+		if err != nil {
+			return fmt.Errorf("FilesInput: failed to unmarshal source references into any supported type")
+		}
 	default:
 		return fmt.Errorf("FilesInput: unknown input origin: %s", rawFilesInput.Origin)
 	}
 
-	if err := json.Unmarshal(rawFilesInput.References, ref); err != nil {
-		return err
-	}
-
 	f.Type = rawFilesInput.Type
 	f.Origin = rawFilesInput.Origin
-	f.References = ref
 
 	return nil
 }
 
 // SUPPORTED FILE INPUT REFERENCES
 
-type FilesInputReferences interface {
-	isFilesInputReferences()
+// FilesInputRef is an interface that is implemented by all types that can be
+// used as a reference in the files input.
+type FilesInputRef interface {
+	isFilesInputRef()
 }
 
+// Type to represent stage-specific metadata that can be passed via the files
+// input to the stage.
 // The expected JSON structure is:
-// `"name:<pipeline_name>": {"file": "<filename>"}`
-type FilesInputReferencesPipeline map[string]FileReference
+//
+//	`{
+//		"<metadata.str1>": <anything_but_object_with_additional_properties>,
+//		"<metadata.str2>": <anything_but_object_with_additional_properties>,
+//		...
+//	}`
+type FilesInputRefMetadata interface {
+	isFilesInputRefMetadata()
+}
 
-func (*FilesInputReferencesPipeline) isFilesInputReferences() {}
+// Pipeline Object Reference
+// The expected JSON structure is:
+//
+//	`{
+//		"name:<pipeline_name>": {
+//			"metadata": {
+//				"<metadata.str1>": <anything_but_object_with_additional_properties>,
+//				"<metadata.str2>": <anything_but_object_with_additional_properties>,
+//				...
+//			},
+//			"file": "<filename>"
+//		},
+//		...
+//	}`
+type FilesInputPipelineObjectRef map[string]FilesInputPipelineOptions
+
+func (*FilesInputPipelineObjectRef) isFilesInputRef() {}
+
+type FilesInputPipelineOptions struct {
+	// File to access with in a pipeline
+	File string `json:"file,omitempty"`
+	// Additional metadata to forward to the stage
+	Metadata FilesInputRefMetadata `json:"metadata,omitempty"`
+}
+
+func NewFilesInputPipelineObjectRef(pipeline, filename string, metadata FilesInputRefMetadata) FilesInputRef {
+	// The files input schema allows for multiple pipelines to be specified, but we don't use it.
+	ref := &FilesInputPipelineObjectRef{
+		fmt.Sprintf("name:%s", pipeline): {
+			File:     filename,
+			Metadata: metadata,
+		},
+	}
+	return ref
+}
+
+// Pipeline Array Reference
+// The expected JSON structure is:
+//
+//	`[
+//		{
+//			"id": "name:<pipeline_name>",
+//			"options": {
+//				"metadata": {
+//					"<metadata.str1>": <anything_but_object_with_additional_properties>,
+//					"<metadata.str2>": <anything_but_object_with_additional_properties>,
+//					...
+//				},
+//				"file": "<filename>"
+//			}
+//		},
+//		...
+//	]`
+type FilesInputPipelineArrayRef []FilesInputPipelineArrayRefEntry
+
+func (*FilesInputPipelineArrayRef) isFilesInputRef() {}
 
-type FileReference struct {
-	File string `json:"file"`
+type FilesInputPipelineArrayRefEntry struct {
+	ID      string                    `json:"id"`
+	Options FilesInputPipelineOptions `json:"options,omitempty"`
 }
 
-func NewFilesInputReferencesPipeline(pipeline, filename string) FilesInputReferences {
-	ref := &FilesInputReferencesPipeline{
-		fmt.Sprintf("name:%s", pipeline): {File: filename},
+func NewFilesInputPipelineArrayRef(pipeline, filename string, metadata FilesInputRefMetadata) FilesInputRef {
+	// The files input schema allows for multiple pipelines to be specified, but we don't use it.
+	ref := &FilesInputPipelineArrayRef{
+		{
+			ID: fmt.Sprintf("name:%s", pipeline),
+			Options: FilesInputPipelineOptions{
+				File:     filename,
+				Metadata: metadata,
+			},
+		},
 	}
 	return ref
 }
 
-// TODO: define FilesInputReferences for "sources"
+// Source Plain Reference
+// The expected JSON structure is:
+//
+//	`[
+//		"sha256:<sha256sum>",
+//		...
+//	]`
+type FilesInputSourcePlainRef []string
+
+func (*FilesInputSourcePlainRef) isFilesInputRef() {}
+
+// NewFilesInputSourcePlainRef creates a FilesInputSourcePlainRef from a list of sha256sums.
+// The slice items are the SHA256 checksums of files as a hexadecimal string without any prefix (e.g. "sha256:").
+func NewFilesInputSourcePlainRef(sha256Sums []string) FilesInputRef {
+	refs := FilesInputSourcePlainRef{}
+	for _, sha256Sum := range sha256Sums {
+		refs = append(refs, fmt.Sprintf("sha256:%s", sha256Sum))
+	}
+	return &refs
+}
+
+// Source Array Reference
+// The expected JSON structure is:
+//
+//	`[
+//		{
+//			"id": "sha256:<sha256sum>",
+//			"options": {
+//				"metadata": {
+//					"<metadata.str1>": <anything_but_object_with_additional_properties>,
+//					"<metadata.str2>": <anything_but_object_with_additional_properties>,
+//					...
+//				}
+//			}
+//		},
+//		...
+//	]`
+type FilesInputSourceArrayRef []FilesInputSourceArrayRefEntry
+
+func (*FilesInputSourceArrayRef) isFilesInputRef() {}
+
+type FilesInputSourceOptions struct {
+	// Additional metadata to forward to the stage
+	Metadata FilesInputRefMetadata `json:"metadata,omitempty"`
+}
+
+type FilesInputSourceArrayRefEntry struct {
+	ID      string                   `json:"id"`
+	Options *FilesInputSourceOptions `json:"options,omitempty"`
+}
+
+// NewFilesInputSourceArrayRefEntry creates a FilesInputSourceArrayRefEntry from a sha256sum and metadata.
+// The sha256sum is the SHA256 checksum of the file as a hexadecimal string without any prefix (e.g. "sha256:").
+func NewFilesInputSourceArrayRefEntry(sha256Sum string, metadata FilesInputRefMetadata) FilesInputSourceArrayRefEntry {
+	ref := FilesInputSourceArrayRefEntry{
+		ID: fmt.Sprintf("sha256:%s", sha256Sum),
+	}
+	if metadata != nil {
+		ref.Options = &FilesInputSourceOptions{Metadata: metadata}
+	}
+	return ref
+}
+
+func NewFilesInputSourceArrayRef(entries []FilesInputSourceArrayRefEntry) FilesInputRef {
+	ref := FilesInputSourceArrayRef(entries)
+	return &ref
+}
+
+// Source Object Reference
+// The expected JSON structure is:
+//
+//	`{
+//		"sha256:<sha256sum>": {
+//			"metadata": {
+//				"<metadata.str1>": <anything_but_object_with_additional_properties>,
+//				"<metadata.str2>": <anything_but_object_with_additional_properties>,
+//				...
+//			}
+//		},
+//		...
+//	}`
+type FilesInputSourceObjectRef map[string]FilesInputSourceOptions
+
+func (*FilesInputSourceObjectRef) isFilesInputRef() {}
+
+// NewFilesInputSourceObjectRef creates a FilesInputSourceObjectRef from a map of sha256sums to metadata
+// The key is the SHA256 checksum of the file as a hexadecimal string without any prefix (e.g. "sha256:").
+func NewFilesInputSourceObjectRef(entries map[string]FilesInputRefMetadata) FilesInputRef {
+	refs := FilesInputSourceObjectRef{}
+	for sha256Sum, metadata := range entries {
+		refs[fmt.Sprintf("sha256:%s", sha256Sum)] = FilesInputSourceOptions{Metadata: metadata}
+	}
+	return &refs
+}
diff --git a/internal/osbuild/ignition_stage.go b/internal/osbuild/ignition_stage.go
index 32af0b678..e21c689d3 100644
--- a/internal/osbuild/ignition_stage.go
+++ b/internal/osbuild/ignition_stage.go
@@ -18,24 +18,14 @@ func NewIgnitionStage(options *IgnitionStageOptions) *Stage {
 }
 
 type IgnitionStageInputInline struct {
-	InlineFile IgnitionStageInput `json:"inlinefile"`
+	InlineFile *FilesInput `json:"inlinefile"`
 }
 
 func (IgnitionStageInputInline) isStageInputs() {}
 
-type IgnitionStageInput struct {
-	inputCommon
-	References IgnitionStageReferences `json:"references"`
-}
-
-type IgnitionStageReferences []string
-
-func (IgnitionStageReferences) isReferences() {}
-
 func NewIgnitionInlineInput(embeddedData string) Inputs {
-	inputs := new(IgnitionStageInputInline)
-	inputs.InlineFile.Type = "org.osbuild.files"
-	inputs.InlineFile.Origin = "org.osbuild.source"
-	inputs.InlineFile.References = IgnitionStageReferences{fmt.Sprintf("sha256:%x", sha256.Sum256([]byte(embeddedData)))}
-	return inputs
+	input := NewFilesInput(NewFilesInputSourcePlainRef([]string{
+		fmt.Sprintf("%x", sha256.Sum256([]byte(embeddedData))),
+	}))
+	return &IgnitionStageInputInline{InlineFile: input}
 }
diff --git a/internal/osbuild/qemu_stage.go b/internal/osbuild/qemu_stage.go
index dd4e43b82..b33b48059 100644
--- a/internal/osbuild/qemu_stage.go
+++ b/internal/osbuild/qemu_stage.go
@@ -163,29 +163,11 @@ func (o VHDXOptions) formatType() QEMUFormat {
 }
 
 type QEMUStageInputs struct {
-	Image *QEMUStageInput `json:"image"`
+	Image *FilesInput `json:"image"`
 }
 
 func (QEMUStageInputs) isStageInputs() {}
 
-type QEMUStageInput struct {
-	inputCommon
-	References QEMUStageReferences `json:"references"`
-}
-
-func (QEMUStageInput) isStageInput() {}
-
-type QEMUStageReferences map[string]QEMUFile
-
-func (QEMUStageReferences) isReferences() {}
-
-type QEMUFile struct {
-	Metadata FileMetadata `json:"metadata,omitempty"`
-	File     string       `json:"file,omitempty"`
-}
-
-type FileMetadata map[string]interface{}
-
 // NewQEMUStage creates a new QEMU Stage object.
 func NewQEMUStage(options *QEMUStageOptions, inputs *QEMUStageInputs) *Stage {
 	return &Stage{
@@ -271,16 +253,7 @@ func (options QEMUStageOptions) MarshalJSON() ([]byte, error) {
 	return json.Marshal(qemuStageOptions(options))
 }
 
-func NewQemuStagePipelineFilesInputs(stage, file string) *QEMUStageInputs {
-	stageKey := "name:" + stage
-	ref := map[string]QEMUFile{
-		stageKey: {
-			File: file,
-		},
-	}
-	input := new(QEMUStageInput)
-	input.Type = "org.osbuild.files"
-	input.Origin = "org.osbuild.pipeline"
-	input.References = ref
+func NewQemuStagePipelineFilesInputs(pipeline, file string) *QEMUStageInputs {
+	input := NewFilesInput(NewFilesInputPipelineObjectRef(pipeline, file, nil))
 	return &QEMUStageInputs{Image: input}
 }
diff --git a/internal/osbuild/rpm_stage.go b/internal/osbuild/rpm_stage.go
index 91d5453cb..7fbe138cd 100644
--- a/internal/osbuild/rpm_stage.go
+++ b/internal/osbuild/rpm_stage.go
@@ -1,6 +1,8 @@
 package osbuild
 
 import (
+	"strings"
+
 	"github.com/osbuild/osbuild-composer/internal/rpmmd"
 )
 
@@ -42,24 +44,12 @@ func (RPMStageOptions) isStageOptions() {}
 // RPMStageInputs defines a collection of packages to be installed by the RPM
 // stage.
 type RPMStageInputs struct {
-
 	// Packages to install
-	Packages *RPMStageInput `json:"packages"`
+	Packages *FilesInput `json:"packages"`
 }
 
 func (RPMStageInputs) isStageInputs() {}
 
-// RPMStageInput defines a single input source.
-type RPMStageInput struct {
-	inputCommon
-
-	// Collection of references. Each reference defines a package to be
-	// installed, with optional metadata.
-	References RPMStageSourceArrayRefs `json:"references"`
-}
-
-func (RPMStageInput) isStageInput() {}
-
 // RPM package reference metadata
 type RPMStageReferenceMetadata struct {
 	// This option defaults to `false`, therefore it does not need to be
@@ -67,56 +57,7 @@ type RPMStageReferenceMetadata struct {
 	CheckGPG bool `json:"rpm.check_gpg,omitempty"`
 }
 
-// RPMStageSourceOptions holds the metadata/options for a single RPM package to be
-// installed.
-type RPMStageSourceOptions struct {
-	Metadata *RPMStageReferenceMetadata `json:"metadata,omitempty"`
-}
-
-// RPMStageReferences: References to RPM packages defined in JSON as:
-//
-// "sha256:<...>": {
-//    "metadata": {
-//	    "rpm.check_gpg": <boolean>
-//    }
-// },
-// "sha256:<...>": {
-//    "metadata": {
-//	    "rpm.check_gpg": <boolean>
-//    }
-// }
-// ...
-type RPMStageReferences map[string]*RPMStageSourceOptions
-
-func (RPMStageReferences) isReferences() {}
-
-// RPMStageSourceArrayRefs: References to RPM packages defined in JSON as an
-// array of objects (preserves item order):
-// [
-//   {
-//     "id": "sha256:<...>": {
-//       "options": {
-//         "rpm.check_gpg": <boolean>
-//       }
-//     }
-//   },
-//   {
-//     "id": "sha256:<...>": {
-//       "options": {
-//         "rpm.check_gpg": <boolean>
-//       }
-//     }
-//   },
-//   ...
-// ]
-type RPMStageSourceArrayRefs []*RPMStageSourceArrayRef
-
-func (RPMStageSourceArrayRefs) isReferences() {}
-
-type RPMStageSourceArrayRef struct {
-	ID      string                 `json:"id"`
-	Options *RPMStageSourceOptions `json:"options,omitempty"`
-}
+func (*RPMStageReferenceMetadata) isFilesInputRefMetadata() {}
 
 // NewRPMStage creates a new RPM stage.
 func NewRPMStage(options *RPMStageOptions, inputs *RPMStageInputs) *Stage {
@@ -180,28 +121,23 @@ func RPMPackageMetadataToSignature(pkg RPMPackageMetadata) *string {
 }
 
 func NewRpmStageSourceFilesInputs(specs []rpmmd.PackageSpec) *RPMStageInputs {
-	stageInput := new(RPMStageInput)
-	stageInput.Type = "org.osbuild.files"
-	stageInput.Origin = "org.osbuild.source"
-	stageInput.References = pkgRefs(specs)
-	return &RPMStageInputs{Packages: stageInput}
+	input := NewFilesInput(pkgRefs(specs))
+	return &RPMStageInputs{Packages: input}
 }
 
-func pkgRefs(specs []rpmmd.PackageSpec) RPMStageSourceArrayRefs {
-	refs := make(RPMStageSourceArrayRefs, len(specs))
+func pkgRefs(specs []rpmmd.PackageSpec) FilesInputRef {
+	refs := make([]FilesInputSourceArrayRefEntry, len(specs))
 	for idx, pkg := range specs {
-		refs[idx] = &RPMStageSourceArrayRef{
-			ID: pkg.Checksum,
-		}
+		pkgSum := strings.TrimPrefix(pkg.Checksum, "sha256:")
+		var pkgMetadata FilesInputRefMetadata
 		if pkg.CheckGPG {
-			refs[idx].Options = &RPMStageSourceOptions{
-				Metadata: &RPMStageReferenceMetadata{
-					CheckGPG: pkg.CheckGPG,
-				},
+			pkgMetadata = &RPMStageReferenceMetadata{
+				CheckGPG: pkg.CheckGPG,
 			}
 		}
+		refs[idx] = NewFilesInputSourceArrayRefEntry(pkgSum, pkgMetadata)
 	}
-	return refs
+	return NewFilesInputSourceArrayRef(refs)
 }
 
 func NewRPMStageOptions(repos []rpmmd.RepoConfig) *RPMStageOptions {
diff --git a/internal/osbuild/xz_stage.go b/internal/osbuild/xz_stage.go
index 9c8ccbedb..bd9c6d991 100644
--- a/internal/osbuild/xz_stage.go
+++ b/internal/osbuild/xz_stage.go
@@ -13,15 +13,23 @@ func NewXzStageOptions(filename string) *XzStageOptions {
 	}
 }
 
-type XzStageInputs interface {
-	isXzStageInputs()
+type XzStageInputs struct {
+	File *FilesInput `json:"file"`
+}
+
+func (*XzStageInputs) isStageInputs() {}
+
+func NewXzStageInputs(references FilesInputRef) *XzStageInputs {
+	return &XzStageInputs{
+		File: NewFilesInput(references),
+	}
 }
 
 // Compresses a file into a xz archive.
-func NewXzStage(options *XzStageOptions, inputs XzStageInputs) *Stage {
+func NewXzStage(options *XzStageOptions, inputs *XzStageInputs) *Stage {
 	var stageInputs Inputs
 	if inputs != nil {
-		stageInputs = inputs.(Inputs)
+		stageInputs = inputs
 	}
 
 	return &Stage{


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 7489d9c10..62d44bab2 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -167,7 +167,7 @@ jobs:
       with:
         ref: ${{ github.event.pull_request.head.sha }}
     - name: Run ShellCheck
-      uses: ludeeus/action-shellcheck@6d3f514f44620b9d4488e380339edc0d9bbe2fba
+      uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38
       with:
         ignore: vendor # We don't want to fix the code in vendored dependencies
       env:
diff --git a/internal/osbuild/fdo_stage_test.go b/internal/osbuild/fdo_stage_test.go
index c882c4f80..204ece2c4 100644
--- a/internal/osbuild/fdo_stage_test.go
+++ b/internal/osbuild/fdo_stage_test.go
@@ -23,9 +23,10 @@ func TestNewFDOStageForRootCerts(t *testing.T) {
 
 		inputs := stage.Inputs.(*FDOStageInputs)
 		certs := inputs.RootCerts
+		refs := certs.References.(*FilesInputSourcePlainRef)
 
-		assert.Len(certs.References, 1)
-		assert.Equal(certs.References[0], tt.hash)
+		assert.Len(*refs, 1)
+		assert.Equal((*refs)[0], tt.hash)
 
 	}
 }
diff --git a/internal/osbuild/files_input_test.go b/internal/osbuild/files_input_test.go
index 6ec1ec499..72f84c8aa 100644
--- a/internal/osbuild/files_input_test.go
+++ b/internal/osbuild/files_input_test.go
@@ -1,101 +1,93 @@
 package osbuild
 
 import (
-	"bytes"
 	"encoding/json"
-	"fmt"
-	"reflect"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
 )
 
-func TestNewFilesInputs(t *testing.T) {
-	inputFilename := "image.raw"
-	pipeline := "os"
-
-	expectedInput := &FilesInputs{
-		File: &FilesInput{
-			inputCommon: inputCommon{
-				Type:   InputTypeFiles,
-				Origin: InputOriginPipeline,
-			},
-			References: &FilesInputReferencesPipeline{
-				fmt.Sprintf("name:%s", pipeline): FileReference{File: inputFilename},
-			},
+func TestFilesInput_UnmarshalJSON(t *testing.T) {
+	testCases := []struct {
+		name    string
+		ref     FilesInputRef
+		rawJson []byte
+	}{
+		{
+			name:    "pipeline-object-ref",
+			ref:     NewFilesInputPipelineObjectRef("os", "image.raw", nil),
+			rawJson: []byte(`{"type":"org.osbuild.files","origin":"org.osbuild.pipeline","references":{"name:os":{"file":"image.raw"}}}`),
+		},
+		{
+			name:    "pipeline-array-ref",
+			ref:     NewFilesInputPipelineArrayRef("os", "image.raw", nil),
+			rawJson: []byte(`{"type":"org.osbuild.files","origin":"org.osbuild.pipeline","references":[{"id":"name:os","options":{"file":"image.raw"}}]}`),
+		},
+		{
+			name:    "source-plain-ref",
+			ref:     NewFilesInputSourcePlainRef([]string{"1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"}),
+			rawJson: []byte(`{"type":"org.osbuild.files","origin":"org.osbuild.source","references":["sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"]}`),
+		},
+		{
+			name: "source-array-ref",
+			ref: NewFilesInputSourceArrayRef([]FilesInputSourceArrayRefEntry{
+				NewFilesInputSourceArrayRefEntry("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", nil),
+			}),
+			rawJson: []byte(`{"type":"org.osbuild.files","origin":"org.osbuild.source","references":[{"id":"sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"}]}`),
+		},
+		{
+			name: "source-object-ref",
+			ref: NewFilesInputSourceObjectRef(map[string]FilesInputRefMetadata{
+				"1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef": nil,
+			}),
+			rawJson: []byte(`{"type":"org.osbuild.files","origin":"org.osbuild.source","references":{"sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef":{}}}`),
 		},
 	}
 
-	actualInput := NewFilesInputs(NewFilesInputReferencesPipeline(pipeline, inputFilename))
-	assert.Equal(t, expectedInput, actualInput)
-}
+	for _, tt := range testCases {
+		t.Run(tt.name, func(t *testing.T) {
+			var gotInput FilesInput
+			err := json.Unmarshal(tt.rawJson, &gotInput)
+			assert.NoErrorf(t, err, "FilesInput.UnmarshalJSON() error = %v", err)
 
-func TestFilesInput_UnmarshalJSON(t *testing.T) {
-	type fields struct {
-		Type       string
-		Origin     string
-		References FilesInputReferences
-	}
+			input := NewFilesInput(tt.ref)
+			gotBytes, err := json.Marshal(input)
+			assert.NoErrorf(t, err, "FilesInput.MarshalJSON() error = %v", err)
 
-	type args struct {
-		data []byte
+			assert.EqualValuesf(t, tt.rawJson, gotBytes, "Expected JSON `%v`, got JSON `%v`", string(tt.rawJson), string(gotBytes))
+			assert.EqualValuesf(t, input, &gotInput, "Expected input `%v`, got input `%v` [test: %q]", input, &gotInput, tt.name)
+		})
 	}
 
-	tests := []struct {
+	// test invalid cases
+	invalidTestCases := []struct {
 		name    string
-		fields  fields
-		args    args
-		wantErr bool
+		rawJson []byte
 	}{
 		{
-			name: "pipeline-origin",
-			fields: fields{
-				Type:       InputTypeFiles,
-				Origin:     InputOriginPipeline,
-				References: NewFilesInputReferencesPipeline("os", "image.raw"),
-			},
-			args: args{
-				data: []byte(`{"type":"org.osbuild.files","origin":"org.osbuild.pipeline","references":{"name:os":{"file":"image.raw"}}}`),
-			},
+			name:    "invalid-pipeline-ref",
+			rawJson: []byte(`{"type":"org.osbuild.files","origin":"org.osbuild.pipeline","references":1}`),
 		},
 		{
-			name: "unknown-origin",
-			fields: fields{
-				Type:       InputTypeFiles,
-				Origin:     InputOriginSource,
-				References: nil,
-			},
-			wantErr: true,
+			name:    "invalid-source-ref",
+			rawJson: []byte(`{"type":"org.osbuild.files","origin":"org.osbuild.source","references":2}`),
+		},
+		{
+			name:    "invalid-origin",
+			rawJson: []byte(`{"type":"org.osbuild.files","origin":"org.osbuild.invalid","references":{}}`),
+		},
+		{
+			name:    "invalid-input",
+			rawJson: []byte(`[]`),
 		},
 	}
 
-	for idx, tt := range tests {
+	for _, tt := range invalidTestCases {
 		t.Run(tt.name, func(t *testing.T) {
-			input := &FilesInput{
-				inputCommon: inputCommon{
-					Type:   tt.fields.Type,
-					Origin: tt.fields.Origin,
-				},
-				References: tt.fields.References,
-			}
 			var gotInput FilesInput
-			if err := json.Unmarshal(tt.args.data, &gotInput); (err != nil) != tt.wantErr {
-				println("data: ", string(tt.args.data))
-				t.Errorf("FilesInput.UnmarshalJSON() error = %v, wantErr %v [idx: %d]", err, tt.wantErr, idx)
-			}
-			if tt.wantErr {
-				return
-			}
-			gotBytes, err := json.Marshal(input)
-			if err != nil {
-				t.Errorf("Could not marshal FilesInput: %v", err)
-			}
-			if !bytes.Equal(gotBytes, tt.args.data) {
-				t.Errorf("Expected `%v`, got `%v` [idx: %d]", string(tt.args.data), string(gotBytes), idx)
-			}
-			if !reflect.DeepEqual(&gotInput, input) {
-				t.Errorf("got {%v, %v, %v}, expected {%v, %v, %v} [%d]", gotInput.Type, gotInput.Origin, gotInput.References, input.Type, input.Origin, input.References, idx)
-			}
+			err := json.Unmarshal(tt.rawJson, &gotInput)
+			assert.Error(t, err)
 		})
 	}
+
 }
diff --git a/internal/osbuild/qemu_stage_test.go b/internal/osbuild/qemu_stage_test.go
index bbfdb360d..80bd48034 100644
--- a/internal/osbuild/qemu_stage_test.go
+++ b/internal/osbuild/qemu_stage_test.go
@@ -30,14 +30,7 @@ func TestNewQemuStage(t *testing.T) {
 		},
 	}
 
-	input := new(QEMUStageInput)
-	input.Type = "org.osbuild.files"
-	input.Origin = "org.osbuild.pipeline"
-	input.References = map[string]QEMUFile{
-		"name:stage": {
-			File: "img.raw",
-		},
-	}
+	input := NewFilesInput(NewFilesInputPipelineObjectRef("stage", "img.raw", nil))
 	inputs := QEMUStageInputs{Image: input}
 
 	for _, format := range formatOptionsList {
diff --git a/internal/osbuild/xz_stage_test.go b/internal/osbuild/xz_stage_test.go
index 0a5b2a9f7..ff7e94f33 100644
--- a/internal/osbuild/xz_stage_test.go
+++ b/internal/osbuild/xz_stage_test.go
@@ -25,11 +25,11 @@ func TestNewXzStage(t *testing.T) {
 	expectedStage := &Stage{
 		Type:    "org.osbuild.xz",
 		Options: NewXzStageOptions(filename),
-		Inputs:  NewFilesInputs(NewFilesInputReferencesPipeline(pipeline, inputFilename)),
+		Inputs:  NewXzStageInputs(NewFilesInputPipelineObjectRef(pipeline, inputFilename, nil)),
 	}
 
 	actualStage := NewXzStage(NewXzStageOptions(filename),
-		NewFilesInputs(NewFilesInputReferencesPipeline(pipeline, inputFilename)))
+		NewXzStageInputs(NewFilesInputPipelineObjectRef(pipeline, inputFilename, nil)))
 	assert.Equal(t, expectedStage, actualStage)
 }