Make sure checksums used in the file input helper functions contain only 1 colon delimiter and it is not at the start of the string. Adjusted tests to work with new restriction.
313 lines
8.6 KiB
Go
313 lines
8.6 KiB
Go
package osbuild
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/osbuild/osbuild-composer/internal/container"
|
|
)
|
|
|
|
// SPECIFIC INPUT STRUCTURE
|
|
|
|
type FilesInput struct {
|
|
inputCommon
|
|
References FilesInputRef `json:"references"`
|
|
}
|
|
|
|
const InputTypeFiles string = "org.osbuild.files"
|
|
|
|
func NewFilesInput(references FilesInputRef) *FilesInput {
|
|
input := new(FilesInput)
|
|
input.Type = InputTypeFiles
|
|
|
|
switch t := references.(type) {
|
|
case *FilesInputPipelineArrayRef, *FilesInputPipelineObjectRef:
|
|
input.Origin = InputOriginPipeline
|
|
case *FilesInputSourcePlainRef, *FilesInputSourceArrayRef, *FilesInputSourceObjectRef:
|
|
input.Origin = InputOriginSource
|
|
default:
|
|
panic(fmt.Sprintf("unknown FilesInputReferences type: %v", t))
|
|
}
|
|
|
|
input.References = references
|
|
|
|
return input
|
|
}
|
|
|
|
type rawFilesInput struct {
|
|
inputCommon
|
|
References json.RawMessage `json:"references"`
|
|
}
|
|
|
|
func (f *FilesInput) UnmarshalJSON(data []byte) error {
|
|
var rawFilesInput rawFilesInput
|
|
if err := json.Unmarshal(data, &rawFilesInput); err != nil {
|
|
return err
|
|
}
|
|
|
|
switch rawFilesInput.Origin {
|
|
case InputOriginPipeline:
|
|
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)
|
|
}
|
|
|
|
f.Type = rawFilesInput.Type
|
|
f.Origin = rawFilesInput.Origin
|
|
|
|
return nil
|
|
}
|
|
|
|
// SUPPORTED FILE INPUT REFERENCES
|
|
|
|
// 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:
|
|
//
|
|
// `{
|
|
// "<metadata.str1>": <anything_but_object_with_additional_properties>,
|
|
// "<metadata.str2>": <anything_but_object_with_additional_properties>,
|
|
// ...
|
|
// }`
|
|
type FilesInputRefMetadata interface {
|
|
isFilesInputRefMetadata()
|
|
}
|
|
|
|
// 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 FilesInputPipelineArrayRefEntry struct {
|
|
ID string `json:"id"`
|
|
Options FilesInputPipelineOptions `json:"options,omitempty"`
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// Source Plain Reference
|
|
// The expected JSON structure is:
|
|
//
|
|
// `[
|
|
// "sha256:<sha256sum>",
|
|
// ...
|
|
// ]`
|
|
type FilesInputSourcePlainRef []string
|
|
|
|
func (*FilesInputSourcePlainRef) isFilesInputRef() {}
|
|
|
|
// NewFilesInputSourcePlainRef creates a FilesInputSourcePlainRef from a list
|
|
// of checksums. The checksums must be prefixed by the name of the corresponding
|
|
// hashing algorithm followed by a colon (e.g. sha256:, sha1:, etc).
|
|
func NewFilesInputSourcePlainRef(checksums []string) FilesInputRef {
|
|
for _, cs := range checksums {
|
|
validateChecksum(cs)
|
|
}
|
|
refs := FilesInputSourcePlainRef(checksums)
|
|
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 checksum and metadata. The checksum must be prefixed by the name of
|
|
// the corresponding hashing algorithm followed by a colon (e.g. sha256:,
|
|
// sha1:, etc).
|
|
func NewFilesInputSourceArrayRefEntry(checksum string, metadata FilesInputRefMetadata) FilesInputSourceArrayRefEntry {
|
|
validateChecksum(checksum)
|
|
ref := FilesInputSourceArrayRefEntry{
|
|
ID: checksum,
|
|
}
|
|
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 checksums to metadata. The checksums must be prefixed by the name of the
|
|
// corresponding hashing algorithm followed by a colon (e.g. sha256:, sha1:,
|
|
// etc).
|
|
func NewFilesInputSourceObjectRef(entries map[string]FilesInputRefMetadata) FilesInputRef {
|
|
refs := FilesInputSourceObjectRef{}
|
|
for checksum, metadata := range entries {
|
|
validateChecksum(checksum)
|
|
refs[checksum] = FilesInputSourceOptions{Metadata: metadata}
|
|
}
|
|
return &refs
|
|
}
|
|
|
|
// NewFilesInputForManifestLists creates a FilesInput for container manifest
|
|
// lists. If there are no list digests in the container specs, it returns nil.
|
|
func NewFilesInputForManifestLists(containers []container.Spec) *FilesInput {
|
|
refs := make([]string, 0, len(containers))
|
|
for _, c := range containers {
|
|
if c.ListDigest != "" {
|
|
refs = append(refs, c.ListDigest)
|
|
}
|
|
}
|
|
if len(refs) == 0 {
|
|
return nil
|
|
}
|
|
filesRef := FilesInputSourcePlainRef(refs)
|
|
return NewFilesInput(&filesRef)
|
|
}
|
|
|
|
// validateChecksum panics if the given string does not contain an algorithm
|
|
// prefix. It checks this by making sure that the : delimiter exists and is not
|
|
// at the start of the string.
|
|
func validateChecksum(checksum string) {
|
|
if strings.IndexRune(checksum, ':') == 0 || strings.Count(checksum, ":") != 1 {
|
|
panic(fmt.Sprintf("invalid checksum format; must contain algorithm prefix: %s", checksum))
|
|
}
|
|
}
|