From 269f2480c269e81775e47234217dc20715ad6bbe Mon Sep 17 00:00:00 2001 From: Uwe Krueger Date: Thu, 26 Dec 2024 16:53:16 +0100 Subject: [PATCH] inputs+artifact adder+hint check --- api/ocm/compdesc/componentdescriptor.go | 2 + .../versions/v2/componentdescriptor.go | 4 +- api/ocm/cpi/repocpi/view_cv.go | 4 +- api/ocm/refhints/hints.go | 122 ++++++++++++++++-- api/ocm/refhints/hints_test.go | 31 +++++ .../ocmcmds/common/addhdlrs/interface.go | 9 ++ .../ocmcmds/common/addhdlrs/rscs/elements.go | 34 ++++- .../ocmcmds/common/addhdlrs/srcs/elements.go | 19 ++- .../ocmcmds/common/inputs/cpi/helper.go | 7 +- .../common/inputs/types/binary/spec.go | 4 +- .../common/inputs/types/binary/type.go | 4 + .../common/inputs/types/directory/spec.go | 6 +- .../common/inputs/types/directory/type.go | 4 + .../ocmcmds/common/inputs/types/file/spec.go | 5 +- .../common/inputs/types/file/support.go | 14 +- .../ocmcmds/common/inputs/types/spiff/spec.go | 10 +- .../ocmcmds/common/inputs/types/spiff/type.go | 4 + .../ocmcmds/common/inputs/types/utf8/spec.go | 4 +- .../ocmcmds/common/inputs/types/utf8/type.go | 4 + .../ocmcmds/common/inputs/types/wget/spec.go | 5 +- cmds/ocm/commands/ocmcmds/common/resources.go | 45 ++++--- .../ocmcmds/resources/add/cmd_test.go | 8 +- .../resources/add/testdata/resources.yaml | 4 + .../resources/add/testdata/resources3.yaml | 20 +++ .../commands/ocmcmds/sources/add/cmd_test.go | 15 +++ .../sources/add/testdata/sources2.yaml | 21 +++ cmds/ocm/common/utils/validate.go | 12 +- 27 files changed, 352 insertions(+), 69 deletions(-) create mode 100644 cmds/ocm/commands/ocmcmds/resources/add/testdata/resources3.yaml create mode 100644 cmds/ocm/commands/ocmcmds/sources/add/testdata/sources2.yaml diff --git a/api/ocm/compdesc/componentdescriptor.go b/api/ocm/compdesc/componentdescriptor.go index 9c02b23713..e9580fee18 100644 --- a/api/ocm/compdesc/componentdescriptor.go +++ b/api/ocm/compdesc/componentdescriptor.go @@ -149,6 +149,8 @@ type ReferenceHintProvider interface { type ReferenceHintSink interface { // SetReferenceHints sets the reference hints specified together with the metadata. SetReferenceHints([]refhints.ReferenceHint) + // AddReferenceHints adds additional hints if their type is not yet available. + AddReferenceHints(hints ...refhints.ReferenceHint) } // ArtifactMetaPointer is a pointer to an artifact meta object. diff --git a/api/ocm/compdesc/versions/v2/componentdescriptor.go b/api/ocm/compdesc/versions/v2/componentdescriptor.go index f979444ab0..f7cd54f97c 100644 --- a/api/ocm/compdesc/versions/v2/componentdescriptor.go +++ b/api/ocm/compdesc/versions/v2/componentdescriptor.go @@ -245,7 +245,7 @@ type SourceMeta struct { Type string `json:"type"` // ReferenceHints describe several types hints used by uploaders // to decide on used element identities. - ReferenceHints []refhints.DefaultReferenceHint `json:"referenceHints,omitempty"` + ReferenceHints refhints.DefaultReferenceHints `json:"referenceHints,omitempty"` } // GetType returns the type of the object. @@ -292,7 +292,7 @@ type Resource struct { // ReferenceHints describe several types hints used by uploaders // to decide on used element identities. - ReferenceHints []refhints.DefaultReferenceHint `json:"referenceHints,omitempty"` + ReferenceHints refhints.DefaultReferenceHints `json:"referenceHints,omitempty"` // Relation describes the relation of the resource to the component. // Can be a local or external resource diff --git a/api/ocm/cpi/repocpi/view_cv.go b/api/ocm/cpi/repocpi/view_cv.go index 070fd9519b..cdc3657436 100644 --- a/api/ocm/cpi/repocpi/view_cv.go +++ b/api/ocm/cpi/repocpi/view_cv.go @@ -245,7 +245,7 @@ func (c *componentVersionAccessView) SetResourceBlob(meta *cpi.ResourceMeta, blo return err } eff := cpi.NewBlobModificationOptions(opts...) - hints = refhints.Join(meta.ReferenceHints, refhints.FilterImplicit(hints)) + hints = refhints.JoinUnique(meta.ReferenceHints, refhints.FilterImplicit(hints)) acc, err := c.AddBlob(blob, meta.Type, hints, global, eff) if err != nil { return fmt.Errorf("unable to add blob (component %s:%s resource %s): %w", c.GetName(), c.GetVersion(), meta.GetName(), err) @@ -271,7 +271,7 @@ func (c *componentVersionAccessView) SetSourceBlob(meta *cpi.SourceMeta, blob cp if err := utils.ValidateObject(blob); err != nil { return err } - hints = refhints.Join(meta.ReferenceHints, refhints.FilterImplicit(hints)) + hints = refhints.JoinUnique(meta.ReferenceHints, refhints.FilterImplicit(hints)) acc, err := c.AddBlob(blob, meta.Type, hints, global) if err != nil { return fmt.Errorf("unable to add blob: (component %s:%s source %s): %w", c.GetName(), c.GetVersion(), meta.GetName(), err) diff --git a/api/ocm/refhints/hints.go b/api/ocm/refhints/hints.go index cfcb2a87ae..869df5cd1b 100644 --- a/api/ocm/refhints/hints.go +++ b/api/ocm/refhints/hints.go @@ -1,6 +1,7 @@ package refhints import ( + "encoding/json" "maps" "slices" "strings" @@ -35,12 +36,6 @@ const ( IMPLICIT_TRUE = "true" ) -func MatchType(typs ...string) matcher.Matcher[ReferenceHint] { - return func(h ReferenceHint) bool { - return slices.Contains(typs, h.GetType()) - } -} - // ReferenceHints is list of hints. // Notaion: a sequence of hint notations separated by a ;. type ReferenceHints []ReferenceHint @@ -50,7 +45,7 @@ func NewHints(f func(ref string, implicit ...bool) ReferenceHint, ref string, im } func (h *ReferenceHints) Add(hints ...ReferenceHint) { - *h = sliceutils.AppendUniqueFunc(*h, runtime.MatchType[ReferenceHint], hints...) + AddUnique(h, hints...) } func (h ReferenceHints) Copy() ReferenceHints { @@ -75,7 +70,7 @@ func (h ReferenceHints) GetReferenceHint(typs ...string) ReferenceHint { if len(typs) == 0 { return nil } - hints := sliceutils.Filter(h, MatchType(typs...)) + hints := Filter(h, MatchType(typs...)) if len(hints) == 0 { return nil } @@ -128,18 +123,55 @@ type ReferenceHint interface { AsDefault() DefaultReferenceHint } -func IsImplicitHint(h ReferenceHint) bool { +func MatchType(typs ...string) matcher.Matcher[ReferenceHint] { + return func(h ReferenceHint) bool { + return slices.Contains(typs, h.GetType()) + } +} + +func Equal(o ReferenceHint) matcher.Matcher[ReferenceHint] { + d := o.Serialize() + return func(h ReferenceHint) bool { + return h.Serialize() == d + } +} + +func IsImplicit(h ReferenceHint) bool { if h == nil { return false } return h.GetProperty(HINT_IMPLICIT) == IMPLICIT_TRUE } -func FilterImplicit(hints []ReferenceHint) ReferenceHints { +func IsExplicit(h ReferenceHint) bool { + if h == nil { + return false + } + return !IsImplicit(h) +} + +func Filter(hints []ReferenceHint, cond matcher.Matcher[ReferenceHint]) ReferenceHints { if len(hints) == 0 { return nil } - return sliceutils.Filter(hints, IsImplicitHint) + return sliceutils.Filter(hints, cond) +} + +func FilterImplicit(hints []ReferenceHint) ReferenceHints { + return Filter(hints, IsImplicit) +} + +func AsImplicit[S ~[]T, T ReferenceHint](hints S) DefaultReferenceHints { + var result DefaultReferenceHints + + for _, h := range hints { + if IsImplicit(h) { + result.Add(h) + } else { + result.Add(h.AsDefault().SetProperty(HINT_IMPLICIT, IMPLICIT_TRUE)) + } + } + return result } // GetReference returns the default reference hint attribute @@ -245,6 +277,57 @@ func (h DefaultReferenceHint) Serialize(implicit ...bool) string { return s } +//////////////////////////////////////////////////////////////////////////////// + +type DefaultReferenceHints []DefaultReferenceHint + +func (h *DefaultReferenceHints) Add(hints ...ReferenceHint) { + AddUnique(h, sliceutils.Transform(hints, AsDefault)...) +} + +func (h DefaultReferenceHints) Copy() ReferenceHints { + var result ReferenceHints + + for _, v := range h { + result = append(result, v.Copy()) + } + return result +} + +// Serialize provides a string representation. The implicit +// attribute is only serialized, if it is called with true. +func (h DefaultReferenceHints) Serialize(implicit ...bool) string { + return HintsToString(sliceutils.Convert[ReferenceHint](h), implicit...) +} + +var _ json.Marshaler = DefaultReferenceHints{} + +func (h DefaultReferenceHints) MarshalJSON() ([]byte, error) { + return json.Marshal(([]DefaultReferenceHint)(h)) +} + +var _ json.Unmarshaler = &DefaultReferenceHints{} + +// UnmarshalJSON excepts the serialized form or the list form. +func (h *DefaultReferenceHints) UnmarshalJSON(data []byte) error { + var in []DefaultReferenceHint + + err := json.Unmarshal(data, &in) + if err == nil { + *h = DefaultReferenceHints(in) + return nil + } + var s string + err = json.Unmarshal(data, &s) + if err != nil { + return err + } + *h = sliceutils.Transform(ParseHints(s), AsDefault) + return nil +} + +//////////////////////////////////////////////////////////////////////////////// + func escapeHintValue(v string) string { if !strings.ContainsAny(v, "\",;") { return v @@ -413,10 +496,23 @@ func ParseHints(v string, implicit ...bool) ReferenceHints { return hints } -func Join(hints ...[]ReferenceHint) ReferenceHints { +// JoinUnique joins multiple hint lists, where the first occurrence of a +// hint type takes precedence. +func JoinUnique(hints ...[]ReferenceHint) ReferenceHints { var result []ReferenceHint for _, h := range hints { - result = sliceutils.AppendUniqueFunc(result, runtime.MatchType[ReferenceHint], h...) + AddUnique(&result, h...) } return result } + +// AddUnique adds hints to hint list, whode type is not yet present in the list. +func AddUnique[S ~[]T, T ReferenceHint](hints *S, add ...T) { + *hints = sliceutils.AppendUniqueFunc(*hints, runtime.MatchType[T], add...) +} + +// AsDefault transforms a generic hint into a default hint. +// It can be used by sliceutils.Transform. +func AsDefault(h ReferenceHint) DefaultReferenceHint { + return h.AsDefault() +} diff --git a/api/ocm/refhints/hints_test.go b/api/ocm/refhints/hints_test.go index e5435b6ed9..d02d66a845 100644 --- a/api/ocm/refhints/hints_test.go +++ b/api/ocm/refhints/hints_test.go @@ -1,8 +1,10 @@ package refhints_test import ( + "encoding/json" "strings" + . "github.com/mandelsoft/goutils/testutils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -133,6 +135,35 @@ var _ = Describe("Hints Test Environment", func() { CheckHintImplicit("typ", "test", "typ::implicit=true,reference=test") }) }) + + Context("marshal unmarshal", func() { + It("works", func() { + var hints refhints.DefaultReferenceHints + + hints.Add(refhints.New("test", "label")) + + data := Must(json.Marshal(hints)) + Expect(string(data)).To(Equal(`[{"reference":"label","type":"test"}]`)) + + var result refhints.DefaultReferenceHints + + MustBeSuccessful(json.Unmarshal(data, &result)) + Expect(result).To(DeepEqual(hints)) + }) + + It("unmarshalls string", func() { + var hints refhints.DefaultReferenceHints + + hints.Add(refhints.New("test", "label")) + + data := hints.Serialize() + + var result refhints.DefaultReferenceHints + + MustBeSuccessful(json.Unmarshal([]byte(`"`+data+`"`), &result)) + Expect(result).To(DeepEqual(hints)) + }) + }) }) func CheckHint(s string, h ...refhints.ReferenceHint) { diff --git a/cmds/ocm/commands/ocmcmds/common/addhdlrs/interface.go b/cmds/ocm/commands/ocmcmds/common/addhdlrs/interface.go index 6faf055c2f..d1679ed6bb 100644 --- a/cmds/ocm/commands/ocmcmds/common/addhdlrs/interface.go +++ b/cmds/ocm/commands/ocmcmds/common/addhdlrs/interface.go @@ -8,6 +8,7 @@ import ( "github.com/mandelsoft/goutils/sliceutils" clictx "ocm.software/ocm/api/cli" + "ocm.software/ocm/api/ocm/compdesc" metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" "ocm.software/ocm/api/ocm/cpi" "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" @@ -106,6 +107,14 @@ type ElementSpec interface { Validate(ctx clictx.Context, input *ResourceInput) error } +// ArtifactElementSpec is the interface for elements +// representing artifacts (like Resources a d Source). +type ArtifactElementSpec interface { + ElementSpec + compdesc.ReferenceHintSink + compdesc.ReferenceHintProvider +} + // Element is the abstraction over model elements handled by // the add handler, for example, resources, sources, references or complete // component versions. diff --git a/cmds/ocm/commands/ocmcmds/common/addhdlrs/rscs/elements.go b/cmds/ocm/commands/ocmcmds/common/addhdlrs/rscs/elements.go index 03fb688739..b1e5555309 100644 --- a/cmds/ocm/commands/ocmcmds/common/addhdlrs/rscs/elements.go +++ b/cmds/ocm/commands/ocmcmds/common/addhdlrs/rscs/elements.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/sliceutils" "k8s.io/apimachinery/pkg/util/validation/field" clictx "ocm.software/ocm/api/cli" @@ -11,6 +12,7 @@ import ( "ocm.software/ocm/api/ocm/compdesc" metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" compdescv2 "ocm.software/ocm/api/ocm/compdesc/versions/v2" + "ocm.software/ocm/api/ocm/refhints" "ocm.software/ocm/api/utils/runtime" "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs" @@ -97,9 +99,10 @@ func (h *ResourceSpecHandler) Set(v ocm.ComponentVersionAccess, r addhdlrs.Eleme ExtraIdentity: spec.ExtraIdentity, Labels: spec.Labels, }, - Type: spec.Type, - Relation: spec.Relation, - SourceRefs: compdescv2.ConvertSourcerefsTo(spec.SourceRefs), + Type: spec.Type, + Relation: spec.Relation, + SourceRefs: compdescv2.ConvertSourcerefsTo(spec.SourceRefs), + ReferenceHints: spec.GetReferenceHints(), } opts := h.getModOpts() if spec.Options.SkipDigestGeneration { @@ -130,6 +133,8 @@ type ResourceSpec struct { // component.sources. SourceRefs []compdescv2.SourceRef `json:"srcRefs"` + ReferenceHints refhints.DefaultReferenceHints `json:"referenceHints,omitempty"` + addhdlrs.ResourceInput `json:",inline"` // Options describes additional process related options @@ -137,6 +142,8 @@ type ResourceSpec struct { Options ResourceOptions `json:"options,omitempty"` } +var _ addhdlrs.ArtifactElementSpec = (*ResourceSpec)(nil) + // ResourceOptions describes additional process related options // which reflect the handling of the resource without describing it directly. // Typical examples are any options that require specific changes in handling of the resource @@ -176,13 +183,26 @@ func (r *ResourceSpec) Validate(ctx clictx.Context, input *addhdlrs.ResourceInpu r.Version = ComponentVersionTag } rsc := compdescv2.Resource{ - ElementMeta: r.ElementMeta, - Type: r.Type, - Relation: r.Relation, - SourceRefs: r.SourceRefs, + ElementMeta: r.ElementMeta, + Type: r.Type, + Relation: r.Relation, + SourceRefs: r.SourceRefs, + ReferenceHints: r.ReferenceHints, } if err := compdescv2.ValidateResource(fldPath, rsc, false); err != nil { allErrs = append(allErrs, err...) } return allErrs.ToAggregate() } + +func (r *ResourceSpec) GetReferenceHints() refhints.ReferenceHints { + return refhints.ReferenceHints(sliceutils.Convert[refhints.ReferenceHint](r.ReferenceHints)) +} + +func (r *ResourceSpec) SetReferenceHints(hints []refhints.ReferenceHint) { + r.ReferenceHints = sliceutils.Transform(hints, refhints.AsDefault) +} + +func (r *ResourceSpec) AddReferenceHints(hints ...refhints.ReferenceHint) { + refhints.AddUnique(&r.ReferenceHints, sliceutils.Transform(hints, refhints.AsDefault)...) +} diff --git a/cmds/ocm/commands/ocmcmds/common/addhdlrs/srcs/elements.go b/cmds/ocm/commands/ocmcmds/common/addhdlrs/srcs/elements.go index 9d8dced1dc..7cc2bd7cf3 100644 --- a/cmds/ocm/commands/ocmcmds/common/addhdlrs/srcs/elements.go +++ b/cmds/ocm/commands/ocmcmds/common/addhdlrs/srcs/elements.go @@ -3,6 +3,7 @@ package srcs import ( "fmt" + "github.com/mandelsoft/goutils/sliceutils" "k8s.io/apimachinery/pkg/util/validation/field" clictx "ocm.software/ocm/api/cli" @@ -10,6 +11,7 @@ import ( "ocm.software/ocm/api/ocm/compdesc" metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" compdescv2 "ocm.software/ocm/api/ocm/compdesc/versions/v2" + "ocm.software/ocm/api/ocm/refhints" "ocm.software/ocm/api/utils/runtime" "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs" @@ -68,7 +70,8 @@ func (h *ResourceSpecHandler) Set(v ocm.ComponentVersionAccess, r addhdlrs.Eleme ExtraIdentity: spec.ExtraIdentity, Labels: spec.Labels, }, - Type: spec.Type, + Type: spec.Type, + ReferenceHints: spec.GetReferenceHints(), } return v.SetSource(meta, acc, h.GetTargetOpts()...) } @@ -81,7 +84,7 @@ type ResourceSpec struct { addhdlrs.ResourceInput `json:",inline"` } -var _ addhdlrs.ElementSpec = (*ResourceSpec)(nil) +var _ addhdlrs.ArtifactElementSpec = (*ResourceSpec)(nil) func (r *ResourceSpec) GetRawIdentity() metav1.Identity { return r.ElementMeta.GetRawIdentity() @@ -117,3 +120,15 @@ func (r *ResourceSpec) Validate(ctx clictx.Context, input *addhdlrs.ResourceInpu } return allErrs.ToAggregate() } + +func (r *ResourceSpec) GetReferenceHints() refhints.ReferenceHints { + return refhints.ReferenceHints(sliceutils.Convert[refhints.ReferenceHint](r.ReferenceHints)) +} + +func (r *ResourceSpec) SetReferenceHints(hints []refhints.ReferenceHint) { + r.ReferenceHints = sliceutils.Transform(hints, refhints.AsDefault) +} + +func (r *ResourceSpec) AddReferenceHints(hints ...refhints.ReferenceHint) { + refhints.AddUnique(&r.ReferenceHints, sliceutils.Transform(hints, refhints.AsDefault)...) +} diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/cpi/helper.go b/cmds/ocm/commands/ocmcmds/common/inputs/cpi/helper.go index 8efa40bec8..9cd0682bd3 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/cpi/helper.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/cpi/helper.go @@ -7,6 +7,7 @@ import ( "os" "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/sliceutils" "github.com/mandelsoft/vfs/pkg/vfs" "k8s.io/apimachinery/pkg/util/validation/field" @@ -77,12 +78,12 @@ func (s *ProcessSpec) SetMediaTypeIfNotDefined(mediaType string) { s.MediaType = mediaType } -func (s *ProcessSpec) ProcessBlob(ctx inputs.Context, acc blobaccess.DataAccess, fs vfs.FileSystem) (blobaccess.BlobAccess, []refhints.ReferenceHint, error) { +func (s *ProcessSpec) ProcessBlob(ctx inputs.Context, acc blobaccess.DataAccess, fs vfs.FileSystem, hints ...refhints.DefaultReferenceHint) (blobaccess.BlobAccess, []refhints.ReferenceHint, error) { if !s.Compress() { if s.MediaType == "" { s.MediaType = mime.MIME_OCTET } - return blobaccess.ForDataAccess(blobaccess.BLOB_UNKNOWN_DIGEST, blobaccess.BLOB_UNKNOWN_SIZE, s.MediaType, acc), nil, nil + return blobaccess.ForDataAccess(blobaccess.BLOB_UNKNOWN_DIGEST, blobaccess.BLOB_UNKNOWN_SIZE, s.MediaType, acc), sliceutils.Convert[refhints.ReferenceHint](hints), nil } reader, err := acc.Reader() @@ -106,7 +107,7 @@ func (s *ProcessSpec) ProcessBlob(ctx inputs.Context, acc blobaccess.DataAccess, return nil, nil, errors.Wrapf(err, "unable to close gzip writer") } - return temp.AsBlob(s.MediaType), nil, nil + return temp.AsBlob(s.MediaType), sliceutils.Convert[refhints.ReferenceHint](hints), nil } func AddProcessSpecOptionTypes(set flagsets.ConfigOptionTypeSetHandler) { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/binary/spec.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/binary/spec.go index 10320c2cb0..6d3ab669d1 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/binary/spec.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/binary/spec.go @@ -14,6 +14,8 @@ type Spec struct { inputs.InputSpecBase `json:",inline"` cpi.ProcessSpec `json:",inline"` + ReferenceHints refhints.DefaultReferenceHints `json:"referenceHints,omitempty"` + // Data is plain inline data as byte array Data runtime.Binary `json:"data,omitempty"` // json rejects to unmarshal some !string into []byte } @@ -37,5 +39,5 @@ func (s *Spec) Validate(fldPath *field.Path, ctx inputs.Context, inputFilePath s } func (s *Spec) GetBlob(ctx inputs.Context, info inputs.InputResourceInfo) (blobaccess.BlobAccess, []refhints.ReferenceHint, error) { - return s.ProcessBlob(ctx, blobaccess.DataAccessForData([]byte(s.Data)), ctx.FileSystem()) + return s.ProcessBlob(ctx, blobaccess.DataAccessForData([]byte(s.Data)), ctx.FileSystem(), refhints.AsImplicit(s.ReferenceHints)...) } diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/binary/type.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/binary/type.go index abf4431cd7..b784addace 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/binary/type.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/binary/type.go @@ -18,4 +18,8 @@ specification supports the following fields: - **data** *[]byte* The binary data to provide. + +- **referenceHints** *[]map[string]string* + + This OPTIONAL property describes a list of implicit reference hints. ` + cpi.ProcessSpecUsage diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/directory/spec.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/directory/spec.go index a2b3964930..7f6b4f0df1 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/directory/spec.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/directory/spec.go @@ -3,6 +3,7 @@ package directory import ( "fmt" + "github.com/mandelsoft/goutils/sliceutils" "k8s.io/apimachinery/pkg/util/validation/field" "ocm.software/ocm/api/ocm/refhints" @@ -15,6 +16,9 @@ import ( type Spec struct { cpi.MediaFileSpec `json:",inline"` + + ReferenceHints refhints.DefaultReferenceHints `json:"referenceHints,omitempty"` + // PreserveDir defines that the directory specified in the Path field should be included in the blob. // Only supported for Type dir. PreserveDir *bool `json:"preserveDir,omitempty"` @@ -74,5 +78,5 @@ func (s *Spec) GetBlob(ctx inputs.Context, info inputs.InputResourceInfo) (bloba dirtree.WithFollowSymlinks(utils.AsBool(s.FollowSymlinks)), dirtree.WithPreserveDir(utils.AsBool(s.PreserveDir)), ) - return access, nil, err + return access, sliceutils.Convert[refhints.ReferenceHint](refhints.AsImplicit(s.ReferenceHints)), err } diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/directory/type.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/directory/type.go index 9250ee9571..2573b299c3 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/directory/type.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/directory/type.go @@ -35,6 +35,10 @@ This blob type specification supports the following fields: The default media type is ` + mime.MIME_TAR + ` and ` + mime.MIME_GZIP + ` if compression is enabled. +- **referenceHints** *[]map[string]string* + + This OPTIONAL property describes a list of implicit reference hints. + - **compress** *bool* This OPTIONAL property describes whether the file content should be stored diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/file/spec.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/file/spec.go index 32234c8645..7df206e8c4 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/file/spec.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/file/spec.go @@ -11,6 +11,7 @@ import ( type Spec struct { cpi.MediaFileSpec `json:",inline"` + ReferenceHints refhints.DefaultReferenceHints `json:"referenceHints,omitempty"` } var _ inputs.InputSpec = (*Spec)(nil) @@ -22,9 +23,9 @@ func New(path, mediatype string, compress bool) *Spec { } func (s *Spec) Validate(fldPath *field.Path, ctx inputs.Context, inputFilePath string) field.ErrorList { - return (&FileProcessSpec{s.MediaFileSpec, nil}).Validate(fldPath, ctx, inputFilePath) + return (&FileProcessSpec{s.MediaFileSpec, s.ReferenceHints, nil}).Validate(fldPath, ctx, inputFilePath) } func (s *Spec) GetBlob(ctx inputs.Context, info inputs.InputResourceInfo) (blobaccess.BlobAccess, []refhints.ReferenceHint, error) { - return (&FileProcessSpec{s.MediaFileSpec, nil}).GetBlob(ctx, info) + return (&FileProcessSpec{s.MediaFileSpec, refhints.AsImplicit(s.ReferenceHints), nil}).GetBlob(ctx, info) } diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/file/support.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/file/support.go index bab7f88b97..ab9f96c025 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/file/support.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/file/support.go @@ -7,6 +7,7 @@ import ( "io" "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/sliceutils" "k8s.io/apimachinery/pkg/util/validation/field" "ocm.software/ocm/api/ocm/refhints" @@ -18,7 +19,8 @@ import ( type FileProcessSpec struct { cpi.MediaFileSpec - Transformer func(ctx inputs.Context, inputDir string, data []byte) ([]byte, error) + ReferenceHints []refhints.DefaultReferenceHint `json:"referenceHints,omitempty"` + Transformer func(ctx inputs.Context, inputDir string, data []byte) ([]byte, error) } func (s *FileProcessSpec) Validate(fldPath *field.Path, ctx inputs.Context, inputFilePath string) field.ErrorList { @@ -72,9 +74,9 @@ func (s *FileProcessSpec) GetBlob(ctx inputs.Context, info inputs.InputResourceI } if data == nil { inputBlob.Close() - return blobaccess.ForFile(s.MediaType, inputPath, fs), nil, nil + return blobaccess.ForFile(s.MediaType, inputPath, fs), sliceutils.Convert[refhints.ReferenceHint](s.ReferenceHints), nil } - return blobaccess.ForData(s.MediaType, data), nil, nil + return blobaccess.ForData(s.MediaType, data), sliceutils.Convert[refhints.ReferenceHint](s.ReferenceHints), nil } temp, err := blobaccess.NewTempFile("", "compressed*.gzip", fs) @@ -92,7 +94,7 @@ func (s *FileProcessSpec) GetBlob(ctx inputs.Context, info inputs.InputResourceI return nil, nil, fmt.Errorf("unable to close gzip writer: %w", err) } - return temp.AsBlob(s.MediaType), nil, nil + return temp.AsBlob(s.MediaType), sliceutils.Convert[refhints.ReferenceHint](s.ReferenceHints), nil } func Usage(head string) string { @@ -106,5 +108,9 @@ This blob type specification supports the following fields: This REQUIRED property describes the path to the file relative to the resource file location. + +- **referenceHints** *[]map[string]string* + + This OPTIONAL property describes a list of implicit reference hints ` + cpi.ProcessSpecUsage } diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff/spec.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff/spec.go index 7f850030ce..264373c0f1 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff/spec.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff/spec.go @@ -9,7 +9,7 @@ import ( "github.com/mandelsoft/vfs/pkg/cwdfs" "k8s.io/apimachinery/pkg/util/validation/field" - metav1 "ocm.software/ocm/api/ocm/refhints" + "ocm.software/ocm/api/ocm/refhints" "ocm.software/ocm/api/utils/blobaccess" "ocm.software/ocm/api/utils/runtime" "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" @@ -23,6 +23,8 @@ type Spec struct { Values json.RawMessage `json:"values,omitempty"` // Libraries specifies a list of spiff libraries to include in template processing Libraries []string `json:"libraries,omitempty"` + // ReferenceHints are optional implicit reference hints. + ReferenceHints refhints.DefaultReferenceHints `json:"referenceHints,omitempty"` } var _ inputs.InputSpec = (*Spec)(nil) @@ -46,7 +48,7 @@ func New(path, mediatype string, compress bool, values interface{}, libs ...stri } func (s *Spec) Validate(fldPath *field.Path, ctx inputs.Context, inputFilePath string) field.ErrorList { - allErrs := (&file.FileProcessSpec{s.MediaFileSpec, nil}).Validate(fldPath, ctx, inputFilePath) + allErrs := (&file.FileProcessSpec{s.MediaFileSpec, refhints.AsImplicit(s.ReferenceHints), nil}).Validate(fldPath, ctx, inputFilePath) for i, v := range s.Libraries { pathField := fldPath.Index(i) fileInfo, filePath, err := inputs.FileInfo(ctx, v, inputFilePath) @@ -59,8 +61,8 @@ func (s *Spec) Validate(fldPath *field.Path, ctx inputs.Context, inputFilePath s return allErrs } -func (s *Spec) GetBlob(ctx inputs.Context, info inputs.InputResourceInfo) (blobaccess.BlobAccess, []metav1.ReferenceHint, error) { - return (&file.FileProcessSpec{s.MediaFileSpec, s.process}).GetBlob(ctx, info) +func (s *Spec) GetBlob(ctx inputs.Context, info inputs.InputResourceInfo) (blobaccess.BlobAccess, []refhints.ReferenceHint, error) { + return (&file.FileProcessSpec{s.MediaFileSpec, refhints.AsImplicit(s.ReferenceHints), s.process}).GetBlob(ctx, info) } func (s *Spec) process(ctx inputs.Context, inputFilePath string, data []byte) ([]byte, error) { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff/type.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff/type.go index fd3af2b31f..862152118c 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff/type.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff/type.go @@ -23,6 +23,10 @@ func usage() string { This OPTIONAL property describes a list of spiff libraries to include in template processing. +- **referenceHints** *[]map[string]string* + + This OPTIONAL property describes a list of implicit reference hints. + The variable settings from the command line are available as binding, also. They are provided under the node values. ` diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8/spec.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8/spec.go index 12badcf79b..0e1c982e58 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8/spec.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8/spec.go @@ -16,6 +16,8 @@ type Spec struct { inputs.InputSpecBase `json:",inline"` cpi.ProcessSpec `json:",inline"` + ReferenceHints refhints.DefaultReferenceHints `json:"referenceHints,omitempty"` + // Text is an utf8 string Text string `json:"text,omitempty"` Json json.RawMessage `json:"json,omitempty"` @@ -122,7 +124,7 @@ func (s *Spec) GetBlob(ctx inputs.Context, info inputs.InputResourceInfo) (bloba if err != nil { return nil, nil, err } - return s.ProcessBlob(ctx, blobaccess.DataAccessForData(data), ctx.FileSystem()) + return s.ProcessBlob(ctx, blobaccess.DataAccessForData(data), ctx.FileSystem(), refhints.AsImplicit(s.ReferenceHints)...) } func Prepare(raw []byte) (interface{}, error) { diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8/type.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8/type.go index ab2936c68a..06464bea16 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8/type.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8/type.go @@ -30,4 +30,8 @@ specification supports the following fields: - **yaml** *AML/JSON or JSON/YAML string interpreted as YAML* The content emitted as YAML. + +- **referenceHints** *[]map[string]string* + + This OPTIONAL property describes a list of implicit reference hints. ` + cpi.ProcessSpecUsage diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/wget/spec.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/wget/spec.go index aeeef66d71..f881f3e00f 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/wget/spec.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/wget/spec.go @@ -3,6 +3,7 @@ package wget import ( "bytes" + "github.com/mandelsoft/goutils/sliceutils" "k8s.io/apimachinery/pkg/util/validation/field" "ocm.software/ocm/api/ocm/refhints" @@ -26,6 +27,8 @@ type Spec struct { Body string `json:"body"` // NoRedirect allows to disable redirects NoRedirect bool `json:"noRedirect"` + + ReferenceHints refhints.DefaultReferenceHints `json:"referenceHints,omitempty"` } var _ inputs.InputSpec = (*Spec)(nil) @@ -65,5 +68,5 @@ func (s *Spec) GetBlob(ctx inputs.Context, info inputs.InputResourceInfo) (bloba wget.WithBody(bytes.NewReader([]byte(s.Body))), wget.WithNoRedirect(s.NoRedirect), ) - return access, nil, err + return access, sliceutils.Convert[refhints.ReferenceHint](refhints.AsImplicit(s.ReferenceHints)), err } diff --git a/cmds/ocm/commands/ocmcmds/common/resources.go b/cmds/ocm/commands/ocmcmds/common/resources.go index 9fb7b578a6..65453ec98c 100644 --- a/cmds/ocm/commands/ocmcmds/common/resources.go +++ b/cmds/ocm/commands/ocmcmds/common/resources.go @@ -3,6 +3,7 @@ package common import ( "encoding/json" "fmt" + "slices" _ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types" @@ -18,14 +19,14 @@ import ( "ocm.software/ocm/api/ocm" "ocm.software/ocm/api/ocm/compdesc" v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" - "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + "ocm.software/ocm/api/ocm/cpi" "ocm.software/ocm/api/ocm/extensions/repositories/comparch" + "ocm.software/ocm/api/ocm/refhints" utils2 "ocm.software/ocm/api/utils" "ocm.software/ocm/api/utils/accessio" "ocm.software/ocm/api/utils/accessobj" "ocm.software/ocm/api/utils/cobrautils/flagsets" "ocm.software/ocm/api/utils/logging" - "ocm.software/ocm/api/utils/mime" common "ocm.software/ocm/api/utils/misc" "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/addhdlrs" "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" @@ -56,17 +57,15 @@ func checkHint(v ocm.ComponentVersionAccess, typ string, elem addhdlrs.Element, if err != nil { return err } - local, ok := spec.(*localblob.AccessSpec) - if !ok { - return nil - } - if local.ReferenceName == "" { + hints := refhints.JoinUnique(elem.Spec().(addhdlrs.ArtifactElementSpec).GetReferenceHints(), cpi.ReferenceHint(spec, v)) + if len(hints) == 0 { return nil } elemid := elem.Spec().GetRawIdentity() if elemid[v1.SystemIdentityVersion] == ComponentVersionTag { elemid[v1.SystemIdentityVersion] = v.GetVersion() } + accessor := artacc(v.GetDescriptor()) for i := 0; i < accessor.Len(); i++ { a := accessor.GetArtifact(i) @@ -77,16 +76,20 @@ func checkHint(v ocm.ComponentVersionAccess, typ string, elem addhdlrs.Element, if elemid.Equals(a.GetMeta().GetRawIdentity()) { continue } - olocal, ok := other.(*localblob.AccessSpec) - if !ok { - continue - } - if olocal.ReferenceName != local.ReferenceName { - continue - } - if mime.BaseType(local.MediaType) == mime.BaseType(olocal.MediaType) { - return fmt.Errorf("reference name (hint) %q with base media type %s already used for %s %s:%s", - local.ReferenceName, mime.BaseType(local.MediaType), typ, a.GetMeta().GetName(), a.GetMeta().GetVersion()) + r := accessor.Get(i) + ohints := refhints.JoinUnique(r.(compdesc.ReferenceHintProvider).GetReferenceHints(), cpi.ReferenceHint(other, v)) + + for _, h := range hints { + if slices.ContainsFunc(ohints, refhints.Equal(h)) { + /* + if mime.BaseType(local.MediaType) == mime.BaseType(olocal.MediaType) { + return fmt.Errorf("reference name (hint) %q with base media type %s already used for %s %s:%s", + local.ReferenceName, mime.BaseType(local.MediaType), typ, a.GetMeta().GetName(), a.GetMeta().GetVersion()) + } + */ + return fmt.Errorf("reference name (hint) %q already used for %s %s:%s", + h.Serialize(), typ, a.GetMeta().GetName(), a.GetMeta().GetVersion()) + } } } return nil @@ -486,14 +489,18 @@ func ProcessElements(ictx inputs.Context, cv ocm.ComponentVersionAccess, elems [ ElementName: elem.Spec().GetName(), InputFilePath: general.OptionalDefaulted(elem.Source().Origin(), elem.Input().SourceFile), } - blob, hint, berr := elem.Input().Input.GetBlob(ictx, info) + blob, hints, berr := elem.Input().Input.GetBlob(ictx, info) if berr != nil { return errors.Wrapf(berr, "cannot get %s blob for %q(%s)", h.Key(), elem.Spec().GetName(), elem.Source()) } if iv := elem.Input().Input.GetInputVersion(ictx); iv != "" && !IsVersionSet(elem.Spec().GetVersion()) { elem.Spec().SetVersion(iv) } - acc, err = cv.AddBlob(blob, elem.Type(), hint, nil) + + expl := refhints.Filter(hints, refhints.IsExplicit) + elem.Spec().(addhdlrs.ArtifactElementSpec).AddReferenceHints(expl...) + + acc, err = cv.AddBlob(blob, elem.Type(), hints, nil) blob.Close() if err == nil { err = CheckHint(cv, elem, acc) diff --git a/cmds/ocm/commands/ocmcmds/resources/add/cmd_test.go b/cmds/ocm/commands/ocmcmds/resources/add/cmd_test.go index a6add2d4bb..a53babd766 100644 --- a/cmds/ocm/commands/ocmcmds/resources/add/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/resources/add/cmd_test.go @@ -124,6 +124,12 @@ var _ = Describe("Add resources", func() { Expect(len(cd.Resources)).To(Equal(1)) CheckTextResource(env, cd, "testdata") + + Expect(cd.Resources[0].ReferenceHints.Serialize(true)).To(Equal(`test::laber`)) + }) + + It("rejects duplicate expl and impl hint", func() { + ExpectError(env.Execute("add", "resources", "--file", ARCH, "/testdata/resources3.yaml")).To(MatchError(`cannot add resource "testdata2"(/testdata/resources3.yaml[2][1]): reference name (hint) "test::laber" already used for resource testdata:v1`)) }) It("adds simple text blob with explicit version info", func() { @@ -285,7 +291,7 @@ var _ = Describe("Add resources", func() { It("rejects duplicate hints", func() { Expect(env.Execute("add", "resources", "--skip-digest-generation", "--file", ARCH, "/testdata/helm.yaml")).To(Succeed()) - ExpectError(env.Execute("add", "resources", "--skip-digest-generation", "--file", ARCH, "/testdata/helm2.yaml")).To(MatchError("cannot add resource \"chart2\"(/testdata/helm2.yaml[1][1]): reference name (hint) \"" + oci2.ReferenceHintType + "::test.de/x/mandelsoft/testchart:0.1.0\" with base media type application/vnd.oci.image.manifest.v1 already used for resource chart:v1")) + ExpectError(env.Execute("add", "resources", "--skip-digest-generation", "--file", ARCH, "/testdata/helm2.yaml")).To(MatchError("cannot add resource \"chart2\"(/testdata/helm2.yaml[1][1]): reference name (hint) \"" + oci2.ReferenceHintType + "::test.de/x/mandelsoft/testchart:0.1.0\" already used for resource chart:v1")) }) Context("resource by options", func() { diff --git a/cmds/ocm/commands/ocmcmds/resources/add/testdata/resources.yaml b/cmds/ocm/commands/ocmcmds/resources/add/testdata/resources.yaml index b4c3000b4a..65df34152a 100644 --- a/cmds/ocm/commands/ocmcmds/resources/add/testdata/resources.yaml +++ b/cmds/ocm/commands/ocmcmds/resources/add/testdata/resources.yaml @@ -1,6 +1,10 @@ --- name: testdata type: plainText +referenceHints: + - type: test + reference: laber + input: type: file path: testcontent diff --git a/cmds/ocm/commands/ocmcmds/resources/add/testdata/resources3.yaml b/cmds/ocm/commands/ocmcmds/resources/add/testdata/resources3.yaml new file mode 100644 index 0000000000..4d41d7b0f7 --- /dev/null +++ b/cmds/ocm/commands/ocmcmds/resources/add/testdata/resources3.yaml @@ -0,0 +1,20 @@ +--- +name: testdata +type: plainText +referenceHints: + - type: test + reference: laber + +input: + type: file + path: testcontent + mediaType: text/plain +--- +name: testdata2 +type: plainText + +input: + type: file + path: testcontent + mediaType: text/plain + referenceHints: test::laber \ No newline at end of file diff --git a/cmds/ocm/commands/ocmcmds/sources/add/cmd_test.go b/cmds/ocm/commands/ocmcmds/sources/add/cmd_test.go index d0a8d8ccfb..aa540557c4 100644 --- a/cmds/ocm/commands/ocmcmds/sources/add/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/sources/add/cmd_test.go @@ -8,6 +8,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "ocm.software/ocm/api/utils/runtime" . "ocm.software/ocm/cmds/ocm/testhelper" "ocm.software/ocm/api/ocm/compdesc" @@ -160,5 +161,19 @@ mediaType: text/plain CheckTextSource(env, cd, "testdata") }) + + It("handles hints", func() { + Expect(env.Execute("add", "sources", "--file", ARCH, "/testdata/sources2.yaml")).To(Succeed()) + data, err := env.ReadFile(env.Join(ARCH, comparch.ComponentDescriptorFileName)) + Expect(err).To(Succeed()) + cd, err := compdesc.Decode(data) + Expect(err).To(Succeed()) + Expect(len(cd.Sources)).To(Equal(2)) + + Expect(cd.Sources[0].ReferenceHints.Serialize(true)).To(Equal("test::laber")) + Expect(cd.Sources[1].Access.(*runtime.UnstructuredVersionedTypedObject).Object["referenceName"]).To(Equal("dummy::blubber")) + CheckTextSource(env, cd, "testdata") + CheckArchiveSource(env, cd, "myothersrc") + }) }) }) diff --git a/cmds/ocm/commands/ocmcmds/sources/add/testdata/sources2.yaml b/cmds/ocm/commands/ocmcmds/sources/add/testdata/sources2.yaml new file mode 100644 index 0000000000..27f01a4723 --- /dev/null +++ b/cmds/ocm/commands/ocmcmds/sources/add/testdata/sources2.yaml @@ -0,0 +1,21 @@ +--- +sources: +- name: testdata + type: git + referenceHints: test::laber + input: + type: file + path: testcontent + mediaType: text/plain +- name: 'myothersrc' + type: 'git' + input: + type: "dir" + path: . + compress: true # defaults to false + excludeFiles: + - "*.yaml" + - "*.tmpl" + preserveDir: false # optional, defaulted to false; if true, the top level folder "my/path" is included + referenceHints: + dummy::blubber \ No newline at end of file diff --git a/cmds/ocm/common/utils/validate.go b/cmds/ocm/common/utils/validate.go index a56bf9e7c4..68bfc4d674 100644 --- a/cmds/ocm/common/utils/validate.go +++ b/cmds/ocm/common/utils/validate.go @@ -47,15 +47,15 @@ func CheckForUnknown(fldPath *field.Path, orig, accepted interface{}) field.Erro case map[string]interface{}: if o, ok := orig.(map[string]interface{}); ok { allErrs = append(allErrs, CheckForUnknownFields(fldPath, o, a)...) - } else { - allErrs = append(allErrs, field.Forbidden(fldPath, "map expected")) - } + } /*else { // accept different parsing by explicit unmarshaller + allErrs = append(allErrs, field.Forbidden(fldPath, "map expected")) + }*/ case []interface{}: if o, ok := orig.([]interface{}); ok { allErrs = append(allErrs, CheckForUnknownElements(fldPath, o, a)...) - } else { - allErrs = append(allErrs, field.Forbidden(fldPath, "list expected")) - } + } /*else { // accept different parsing by explicit unmarshaller + allErrs = append(allErrs, field.Forbidden(fldPath, "list expected")) + }*/ default: } return allErrs