From 782970c77f9da3ab8d73eb7904a50d0b1a9d1aba Mon Sep 17 00:00:00 2001 From: Uwe Krueger Date: Tue, 12 Nov 2024 15:05:46 +0100 Subject: [PATCH] fix implicit version handling of an artifact identity (#1026) #### What this PR does / why we need it The defaulting of the extraIdentity did only work, if there was an identity map already set. So far the version was implicutly added to the extraIdentity of a CV if the rest is not unique. In the future the extraIdentity should be explicitly set to be unique. Therefore, the version was now implicitly added to the extraIdentity. This has two problems: - it was only done, if there was already an identity map set, - it was always done, when serializing a CD To be comparable with older signed component versions, such defaulting may only be done if the content of a component version is changed, otherwise the signature would be brocken. #### Which issue(s) this PR fixes --- api/helper/builder/ocm_identity.go | 16 ++ api/helper/builder/ocm_reference.go | 3 +- api/helper/builder/ocm_resource.go | 4 +- api/oci/ociutils/ref.go | 3 +- api/ocm/add_test.go | 14 +- api/ocm/compdesc/componentdescriptor.go | 17 +- api/ocm/compdesc/default.go | 47 +++++- .../versions/ocm.software/v3alpha1/default.go | 26 --- api/ocm/compdesc/versions/v2/default.go | 26 --- api/ocm/cpi/dummy.go | 8 +- api/ocm/cpi/modopts.go | 22 ++- api/ocm/cpi/repocpi/view_cv.go | 46 ++++-- api/ocm/internal/modopts.go | 145 ++++++++++++----- api/ocm/internal/repository.go | 11 +- api/ocm/modopts.go | 16 +- api/ocm/tools/signing/handler_test.go | 14 +- api/ocm/tools/signing/signing_test.go | 153 ++++++++++++++++++ api/ocm/tools/transfer/transfer.go | 2 +- .../commands/ocmcmds/common/addhdlrs/base.go | 8 +- .../ocmcmds/common/addhdlrs/options.go | 51 ++++-- .../ocmcmds/common/addhdlrs/refs/elements.go | 3 +- .../ocmcmds/common/addhdlrs/rscs/elements.go | 12 +- .../ocmcmds/common/addhdlrs/srcs/elements.go | 4 +- .../commands/ocmcmds/components/hash/cmd.go | 45 ++++-- .../ocmcmds/components/hash/cmd_test.go | 44 +++++ .../ocmcmds/components/hash/options.go | 2 +- docs/reference/ocm_add_componentversions.md | 6 +- docs/reference/ocm_add_references.md | 6 +- docs/reference/ocm_add_resources.md | 6 +- docs/reference/ocm_add_sources.md | 6 +- docs/reference/ocm_hash_componentversions.md | 2 +- 31 files changed, 588 insertions(+), 180 deletions(-) diff --git a/api/helper/builder/ocm_identity.go b/api/helper/builder/ocm_identity.go index f77719a7e3..ec302ee622 100644 --- a/api/helper/builder/ocm_identity.go +++ b/api/helper/builder/ocm_identity.go @@ -1,5 +1,9 @@ package builder +import ( + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" +) + const T_OCMMETA = "element with metadata" //////////////////////////////////////////////////////////////////////////////// @@ -10,6 +14,18 @@ func (b *Builder) ExtraIdentity(name string, value string) { b.ocm_meta.ExtraIdentity.Set(name, value) } +func (b *Builder) ExtraIdentities(extras ...string) { + b.expect(b.ocm_meta, T_OCMMETA) + + id := metav1.NewExtraIdentity(extras...) + if b.ocm_meta.ExtraIdentity == nil { + b.ocm_meta.ExtraIdentity = metav1.Identity{} + } + for k, v := range id { + b.ocm_meta.ExtraIdentity.Set(k, v) + } +} + //////////////////////////////////////////////////////////////////////////////// func (b *Builder) RemoveExtraIdentity(name string) { diff --git a/api/helper/builder/ocm_reference.go b/api/helper/builder/ocm_reference.go index 93fc02f932..8f3d2b92f7 100644 --- a/api/helper/builder/ocm_reference.go +++ b/api/helper/builder/ocm_reference.go @@ -1,6 +1,7 @@ package builder import ( + "ocm.software/ocm/api/ocm" "ocm.software/ocm/api/ocm/compdesc" ) @@ -22,7 +23,7 @@ func (r *ocmReference) Set() { } func (r *ocmReference) Close() error { - return r.ocm_vers.SetReference(&r.meta) + return r.ocm_vers.SetReference(&r.meta, ocm.ModifyElement()) } //////////////////////////////////////////////////////////////////////////////// diff --git a/api/helper/builder/ocm_resource.go b/api/helper/builder/ocm_resource.go index 9b3ffe30dc..fb29c85b65 100644 --- a/api/helper/builder/ocm_resource.go +++ b/api/helper/builder/ocm_resource.go @@ -42,9 +42,9 @@ func (r *ocmResource) Close() error { } switch { case r.access != nil: - return r.Builder.ocm_vers.SetResource(&r.meta, r.access, r.opts.ApplyModificationOptions((ocm.ModifyResource()))) + return r.Builder.ocm_vers.SetResource(&r.meta, r.access, r.opts.ApplyModificationOptions((ocm.ModifyElement()))) case r.blob != nil: - return r.Builder.ocm_vers.SetResourceBlob(&r.meta, r.blob, r.hint, nil, r.opts.ApplyModificationOptions((ocm.ModifyResource()))) + return r.Builder.ocm_vers.SetResourceBlob(&r.meta, r.blob, r.hint, nil, r.opts.ApplyModificationOptions((ocm.ModifyElement()))) } return errors.New("access or blob required") } diff --git a/api/oci/ociutils/ref.go b/api/oci/ociutils/ref.go index 50f7675264..5d05921777 100644 --- a/api/oci/ociutils/ref.go +++ b/api/oci/ociutils/ref.go @@ -89,8 +89,7 @@ func (v *ArtVersion) IsDigested() bool { } func (v *ArtVersion) GetTag() string { - if v != nil && - v.Tag != nil { + if v != nil && v.Tag != nil { return *v.Tag } return "" diff --git a/api/ocm/add_test.go b/api/ocm/add_test.go index 9d27a6c0de..1c33309f7d 100644 --- a/api/ocm/add_test.go +++ b/api/ocm/add_test.go @@ -179,13 +179,13 @@ var _ = Describe("add resources", func() { Context("references", func() { It("adds reference", func() { ref := ocm.NewComponentReference("test", COMPONENT+"/sub", "v1") - MustBeSuccessful(cv.SetReference(ref)) + MustBeSuccessful(cv.SetReference(ref, ocm.ModifyElement())) Expect(len(cv.GetDescriptor().References)).To(Equal(1)) }) It("replaces reference", func() { ref := ocm.NewComponentReference("test", COMPONENT+"/sub", "v1") - MustBeSuccessful(cv.SetReference(ref)) + MustBeSuccessful(cv.SetReference(ref, ocm.ModifyElement())) MustBeSuccessful(cv.SetReference(ref.WithVersion("v1"))) Expect(len(Must(cv.SelectReferences(selectors.Name("test"))))).To(Equal(1)) @@ -193,7 +193,7 @@ var _ = Describe("add resources", func() { It("replaces source (enforced)", func() { ref := ocm.NewComponentReference("test", COMPONENT+"/sub", "v1") - MustBeSuccessful(cv.SetReference(ref)) + MustBeSuccessful(cv.SetReference(ref, ocm.ModifyElement())) MustBeSuccessful(cv.SetReference(ref.WithVersion("v2"))) Expect(len(Must(cv.SelectReferences(selectors.Name("test"))))).To(Equal(1)) @@ -201,7 +201,7 @@ var _ = Describe("add resources", func() { It("fails replace non-existent source)", func() { ref := ocm.NewComponentReference("test", COMPONENT+"/sub", "v1") - MustBeSuccessful(cv.SetReference(ref)) + MustBeSuccessful(cv.SetReference(ref, ocm.ModifyElement())) Expect(cv.SetReference(ref.WithExtraIdentity("attr", "value"), ocm.UpdateElement)).To( MatchError("element \"attr\"=\"value\",\"name\"=\"test\" not found")) @@ -209,21 +209,21 @@ var _ = Describe("add resources", func() { It("adds duplicate reference with different version", func() { ref := ocm.NewComponentReference("test", COMPONENT+"/sub", "v1") - MustBeSuccessful(cv.SetReference(ref)) + MustBeSuccessful(cv.SetReference(ref, ocm.ModifyElement())) MustBeSuccessful(cv.SetReference(ref.WithVersion("v2"), ocm.AppendElement)) Expect(len(Must(cv.SelectReferences(selectors.Name("test"))))).To(Equal(2)) }) It("rejects duplicate reference with same version", func() { ref := ocm.NewComponentReference("test", COMPONENT+"/sub", "v1") - MustBeSuccessful(cv.SetReference(ref)) + MustBeSuccessful(cv.SetReference(ref, ocm.ModifyElement())) Expect(cv.SetReference(ref.WithVersion("v1"), ocm.AppendElement)). To(MatchError("adding a new reference with same base identity requires different version")) }) It("rejects duplicate reference with extra identity", func() { ref := ocm.NewComponentReference("test", COMPONENT+"/sub", "v1").WithExtraIdentity("attr", "value") - MustBeSuccessful(cv.SetReference(ref)) + MustBeSuccessful(cv.SetReference(ref, ocm.ModifyElement())) Expect(cv.SetReference(ref, ocm.AppendElement)). To(MatchError("adding a new reference with same base identity requires different version")) }) diff --git a/api/ocm/compdesc/componentdescriptor.go b/api/ocm/compdesc/componentdescriptor.go index 02feb8d26c..8638dfdb7c 100644 --- a/api/ocm/compdesc/componentdescriptor.go +++ b/api/ocm/compdesc/componentdescriptor.go @@ -238,18 +238,21 @@ func (o *ElementMeta) GetIdentity(accessor ElementListAccessor) metav1.Identity identity = metav1.Identity{} } identity[SystemIdentityName] = o.Name - if accessor != nil { + if identity.Get(SystemIdentityVersion) == "" && accessor != nil { found := false l := accessor.Len() for i := 0; i < l; i++ { m := accessor.Get(i).GetMeta() - if m.GetName() == o.Name && m.GetExtraIdentity().Equals(o.ExtraIdentity) { - if found { - identity[SystemIdentityVersion] = o.Version - - break + if m.GetName() == o.Name { + mid := m.GetExtraIdentity() + mid.Remove(SystemIdentityVersion) + if mid.Equals(o.ExtraIdentity) { + if found { + identity[SystemIdentityVersion] = o.Version + break + } + found = true } - found = true } } } diff --git a/api/ocm/compdesc/default.go b/api/ocm/compdesc/default.go index af504034f2..22c3359152 100644 --- a/api/ocm/compdesc/default.go +++ b/api/ocm/compdesc/default.go @@ -27,9 +27,16 @@ func DefaultComponent(component *ComponentDescriptor) *ComponentDescriptor { return component } +func DefaultElements(component *ComponentDescriptor) { + DefaultResources(component) + DefaultSources(component) + DefaultReferences(component) +} + // DefaultResources defaults a list of resources. // The version of the component is defaulted for local resources that do not contain a version. // adds the version as identity if the resource identity would clash otherwise. +// The version is added to an extraIdentity, if it is not unique without it. func DefaultResources(component *ComponentDescriptor) { for i, res := range component.Resources { if res.Relation == v1.LocalRelation && len(res.Version) == 0 { @@ -39,7 +46,45 @@ func DefaultResources(component *ComponentDescriptor) { id := res.GetIdentity(component.Resources) if v, ok := id[SystemIdentityVersion]; ok { if res.ExtraIdentity == nil { - res.ExtraIdentity = v1.Identity{ + component.Resources[i].ExtraIdentity = v1.Identity{ + SystemIdentityVersion: v, + } + } else { + if _, ok := res.ExtraIdentity[SystemIdentityVersion]; !ok { + res.ExtraIdentity[SystemIdentityVersion] = v + } + } + } + } +} + +// DefaultSources defaults a list of sources. +// The version is added to an extraIdentity, if it is not unique without it. +func DefaultSources(component *ComponentDescriptor) { + for i, res := range component.Sources { + id := res.GetIdentity(component.Resources) + if v, ok := id[SystemIdentityVersion]; ok { + if res.ExtraIdentity == nil { + component.Sources[i].ExtraIdentity = v1.Identity{ + SystemIdentityVersion: v, + } + } else { + if _, ok := res.ExtraIdentity[SystemIdentityVersion]; !ok { + res.ExtraIdentity[SystemIdentityVersion] = v + } + } + } + } +} + +// DefaultReferences defaults a list of references. +// The version is added to an extraIdentity, if it is not unique without it. +func DefaultReferences(component *ComponentDescriptor) { + for i, res := range component.References { + id := res.GetIdentity(component.Resources) + if v, ok := id[SystemIdentityVersion]; ok { + if res.ExtraIdentity == nil { + component.References[i].ExtraIdentity = v1.Identity{ SystemIdentityVersion: v, } } else { diff --git a/api/ocm/compdesc/versions/ocm.software/v3alpha1/default.go b/api/ocm/compdesc/versions/ocm.software/v3alpha1/default.go index 0a03ce49ab..979c2b09ea 100644 --- a/api/ocm/compdesc/versions/ocm.software/v3alpha1/default.go +++ b/api/ocm/compdesc/versions/ocm.software/v3alpha1/default.go @@ -1,7 +1,6 @@ package v3alpha1 import ( - v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" "ocm.software/ocm/api/utils/runtime" ) @@ -20,30 +19,5 @@ func (cd *ComponentDescriptor) Default() error { cd.Spec.Resources = make([]Resource, 0) } - DefaultResources(cd) return nil } - -// DefaultResources defaults a list of resources. -// The version of the component is defaulted for local resources that do not contain a version. -// adds the version as identity if the resource identity would clash otherwise. -func DefaultResources(component *ComponentDescriptor) { - for i, res := range component.Spec.Resources { - if res.Relation == v1.LocalRelation && len(res.Version) == 0 { - component.Spec.Resources[i].Version = component.GetVersion() - } - - id := res.GetIdentity(component.Spec.Resources) - if v, ok := id[SystemIdentityVersion]; ok { - if res.ExtraIdentity == nil { - res.ExtraIdentity = v1.Identity{ - SystemIdentityVersion: v, - } - } else { - if _, ok := res.ExtraIdentity[SystemIdentityVersion]; !ok { - res.ExtraIdentity[SystemIdentityVersion] = v - } - } - } - } -} diff --git a/api/ocm/compdesc/versions/v2/default.go b/api/ocm/compdesc/versions/v2/default.go index ce4aec2201..08ea24a1a3 100644 --- a/api/ocm/compdesc/versions/v2/default.go +++ b/api/ocm/compdesc/versions/v2/default.go @@ -1,7 +1,6 @@ package v2 import ( - v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" "ocm.software/ocm/api/utils/runtime" ) @@ -20,30 +19,5 @@ func (cd *ComponentDescriptor) Default() error { cd.Resources = make([]Resource, 0) } - DefaultResources(cd) return nil } - -// DefaultResources defaults a list of resources. -// The version of the component is defaulted for local resources that do not contain a version. -// adds the version as identity if the resource identity would clash otherwise. -func DefaultResources(component *ComponentDescriptor) { - for i, res := range component.Resources { - if res.Relation == v1.LocalRelation && len(res.Version) == 0 { - component.Resources[i].Version = component.GetVersion() - } - - id := res.GetIdentity(component.Resources) - if v, ok := id[SystemIdentityVersion]; ok { - if res.ExtraIdentity == nil { - res.ExtraIdentity = v1.Identity{ - SystemIdentityVersion: v, - } - } else { - if _, ok := res.ExtraIdentity[SystemIdentityVersion]; !ok { - res.ExtraIdentity[SystemIdentityVersion] = v - } - } - } - } -} diff --git a/api/ocm/cpi/dummy.go b/api/ocm/cpi/dummy.go index 7181a35c05..fa45b54aa4 100644 --- a/api/ocm/cpi/dummy.go +++ b/api/ocm/cpi/dummy.go @@ -164,19 +164,19 @@ func (d *DummyComponentVersionAccess) SetResourceByAccess(art ResourceAccess, mo return errors.ErrNotSupported("resource modification") } -func (d *DummyComponentVersionAccess) SetSourceBlob(meta *SourceMeta, blob BlobAccess, refname string, global AccessSpec, opts ...TargetOption) error { +func (d *DummyComponentVersionAccess) SetSourceBlob(meta *SourceMeta, blob BlobAccess, refname string, global AccessSpec, opts ...TargetElementOption) error { return errors.ErrNotSupported("source modification") } -func (d *DummyComponentVersionAccess) SetSource(meta *SourceMeta, spec compdesc.AccessSpec, opts ...TargetOption) error { +func (d *DummyComponentVersionAccess) SetSource(meta *SourceMeta, spec compdesc.AccessSpec, opts ...TargetElementOption) error { return errors.ErrNotSupported("source modification") } -func (d *DummyComponentVersionAccess) SetSourceByAccess(art SourceAccess, opts ...TargetOption) error { +func (d *DummyComponentVersionAccess) SetSourceByAccess(art SourceAccess, opts ...TargetElementOption) error { return errors.ErrNotSupported() } -func (d *DummyComponentVersionAccess) SetReference(ref *ComponentReference, opts ...TargetOption) error { +func (d *DummyComponentVersionAccess) SetReference(ref *ComponentReference, opts ...ElementModificationOption) error { return errors.ErrNotSupported() } diff --git a/api/ocm/cpi/modopts.go b/api/ocm/cpi/modopts.go index 1666c0355b..ebdfeab00d 100644 --- a/api/ocm/cpi/modopts.go +++ b/api/ocm/cpi/modopts.go @@ -9,9 +9,12 @@ import ( ) type ( - TargetElement = internal.TargetElement - TargetOption = internal.TargetOption - TargetOptions = internal.TargetOptions + TargetElement = internal.TargetElement + TargetElementOption = internal.TargetElementOption + TargetElementOptions = internal.TargetElementOptions + + ElementModificationOption = internal.ElementModificationOption + ElementModificationOptions = internal.ElementModificationOptions ModificationOption = internal.ModificationOption ModificationOptions = internal.ModificationOptions @@ -26,8 +29,8 @@ type ( AddVersionOptions = internal.AddVersionOptions ) -func NewTargetOptions(list ...TargetOption) *TargetOptions { - var m TargetOptions +func NewTargetElementOptions(list ...TargetElementOption) *TargetElementOptions { + var m TargetElementOptions m.ApplyTargetOptions(list...) return &m } @@ -65,6 +68,10 @@ func NewModificationOptions(list ...ModificationOption) *ModificationOptions { return internal.NewModificationOptions(list...) } +func NewElementModificationOptions(list ...ElementModificationOption) *ElementModificationOptions { + return internal.NewElementModificationOptions(list...) +} + func TargetIndex(idx int) internal.TargetIndex { return internal.TargetIndex(-1) } @@ -77,10 +84,15 @@ func TargetIdentity(id v1.Identity) internal.TargetIdentity { return internal.TargetIdentity(id) } +// Deprecated: use ModifyElement. func ModifyResource(flag ...bool) internal.ModOptionImpl { return internal.ModifyResource(flag...) } +func ModifyElement(flag ...bool) internal.ElemModOptionImpl { + return internal.ModifyElement(flag...) +} + func AcceptExistentDigests(flag ...bool) internal.ModOptionImpl { return internal.AcceptExistentDigests(flag...) } diff --git a/api/ocm/cpi/repocpi/view_cv.go b/api/ocm/cpi/repocpi/view_cv.go index 98e43e2aa6..2c36d65db8 100644 --- a/api/ocm/cpi/repocpi/view_cv.go +++ b/api/ocm/cpi/repocpi/view_cv.go @@ -249,7 +249,7 @@ func (c *componentVersionAccessView) SetResourceBlob(meta *cpi.ResourceMeta, blo return fmt.Errorf("unable to add blob (component %s:%s resource %s): %w", c.GetName(), c.GetVersion(), meta.GetName(), err) } - if err := c.SetResource(meta, acc, eff, cpi.ModifyResource()); err != nil { + if err := c.SetResource(meta, acc, eff, cpi.ModifyElement()); err != nil { return fmt.Errorf("unable to set resource: %w", err) } @@ -264,7 +264,7 @@ func (c *componentVersionAccessView) AdjustSourceAccess(meta *cpi.SourceMeta, ac return errors.ErrUnknown(cpi.KIND_RESOURCE, meta.GetIdentity(cd.Resources).String()) } -func (c *componentVersionAccessView) SetSourceBlob(meta *cpi.SourceMeta, blob cpi.BlobAccess, refName string, global cpi.AccessSpec, modopts ...cpi.TargetOption) error { +func (c *componentVersionAccessView) SetSourceBlob(meta *cpi.SourceMeta, blob cpi.BlobAccess, refName string, global cpi.AccessSpec, modopts ...cpi.TargetElementOption) error { cpi.Logger(c).Debug("adding source blob", "source", meta.Name) if err := utils.ValidateObject(blob); err != nil { return err @@ -384,7 +384,7 @@ func (c *componentVersionAccessView) SetResource(meta *cpi.ResourceMeta, acc com cd := c.bridge.GetDescriptor() - idx, err := c.getElementIndex("resource", cd.Resources, res, &opts.TargetOptions) + idx, err := c.getElementIndex("resource", cd.Resources, res, &opts.TargetElementOptions) if err != nil { return err } @@ -393,7 +393,7 @@ func (c *componentVersionAccessView) SetResource(meta *cpi.ResourceMeta, acc com } if old == nil { - if !opts.IsModifyResource() && c.bridge.IsPersistent() { + if !opts.IsModifyElement() && c.bridge.IsPersistent() { return fmt.Errorf("new resource would invalidate signature") } } @@ -456,7 +456,7 @@ func (c *componentVersionAccessView) SetResource(meta *cpi.ResourceMeta, acc com if old != nil { eq := res.Equivalent(old) if !eq.IsLocalHashEqual() && c.bridge.IsPersistent() { - if !opts.IsModifyResource() { + if !opts.IsModifyElement() { return fmt.Errorf("resource would invalidate signature") } cd.Signatures = nil @@ -468,6 +468,10 @@ func (c *componentVersionAccessView) SetResource(meta *cpi.ResourceMeta, acc com } else { cd.Resources[idx] = *res } + if opts.IsModifyElement() { + // default handling for completing an extra identity for modifications, only. + compdesc.DefaultResources(cd) + } return c.bridge.Update(false) }) } @@ -499,7 +503,7 @@ func (c *componentVersionAccessView) evaluateResourceDigest(res, old *compdesc.R if !old.Digest.IsNone() { digester.HashAlgorithm = old.Digest.HashAlgorithm digester.NormalizationAlgorithm = old.Digest.NormalisationAlgorithm - if opts.IsAcceptExistentDigests() && !opts.IsModifyResource() && c.bridge.IsPersistent() { + if opts.IsAcceptExistentDigests() && !opts.IsModifyElement() && c.bridge.IsPersistent() { res.Digest = old.Digest value = old.Digest.Value } @@ -508,7 +512,7 @@ func (c *componentVersionAccessView) evaluateResourceDigest(res, old *compdesc.R return hashAlgo, digester, value } -func (c *componentVersionAccessView) SetSourceByAccess(art cpi.SourceAccess, optslist ...cpi.TargetOption) error { +func (c *componentVersionAccessView) SetSourceByAccess(art cpi.SourceAccess, optslist ...cpi.TargetElementOption) error { return setAccess(c, "source", art, func(meta *cpi.SourceMeta, acc compdesc.AccessSpec) error { return c.SetSource(meta, acc, optslist...) @@ -518,7 +522,7 @@ func (c *componentVersionAccessView) SetSourceByAccess(art cpi.SourceAccess, opt }) } -func (c *componentVersionAccessView) SetSource(meta *cpi.SourceMeta, acc compdesc.AccessSpec, optlist ...cpi.TargetOption) error { +func (c *componentVersionAccessView) SetSource(meta *cpi.SourceMeta, acc compdesc.AccessSpec, optlist ...cpi.TargetElementOption) error { if c.bridge.IsReadOnly() { return accessio.ErrReadOnly } @@ -544,33 +548,51 @@ func (c *componentVersionAccessView) SetSource(meta *cpi.SourceMeta, acc compdes } else { cd.Sources[idx] = *res } + compdesc.DefaultSources(cd) return c.bridge.Update(false) }) } -func (c *componentVersionAccessView) SetReference(ref *cpi.ComponentReference, optlist ...cpi.TargetOption) error { +func (c *componentVersionAccessView) SetReference(ref *cpi.ComponentReference, optlist ...cpi.ElementModificationOption) error { + opts := cpi.NewElementModificationOptions(optlist...) + moddef := false + return c.Execute(func() error { cd := c.bridge.GetDescriptor() if ref.Version == "" { return fmt.Errorf("version required for component version reference") } - idx, err := c.getElementIndex("reference", cd.References, ref, optlist...) + idx, err := c.getElementIndex("reference", cd.References, ref, &opts.TargetElementOptions) if err != nil { return err } if idx < 0 { + if !opts.IsModifyElement(moddef) { + return fmt.Errorf("adding reference would invalidate signature") + } cd.References = append(cd.References, *ref) } else { + eq := ref.Equivalent(&cd.References[idx]) + if !eq.IsEquivalent() && c.bridge.IsPersistent() { + if !opts.IsModifyElement(moddef) { + return fmt.Errorf("reference would invalidate signature") + } + cd.Signatures = nil + } + cd.References[idx].Equivalent(ref) cd.References[idx] = *ref } + if opts.IsModifyElement(moddef) { + compdesc.DefaultReferences(cd) + } return c.bridge.Update(false) }) } -func (c *componentVersionAccessView) getElementIndex(kind string, acc compdesc.ElementListAccessor, prov compdesc.ElementMetaProvider, optlist ...cpi.TargetOption) (int, error) { - opts := internal.NewTargetOptions(optlist...) +func (c *componentVersionAccessView) getElementIndex(kind string, acc compdesc.ElementListAccessor, prov compdesc.ElementMetaProvider, optlist ...cpi.TargetElementOption) (int, error) { + opts := internal.NewTargetElementOptions(optlist...) curidx := compdesc.ElementIndex(acc, prov) meta := prov.GetMeta() var idx int diff --git a/api/ocm/internal/modopts.go b/api/ocm/internal/modopts.go index 41826374bc..3732c59434 100644 --- a/api/ocm/internal/modopts.go +++ b/api/ocm/internal/modopts.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/mandelsoft/goutils/general" + "github.com/mandelsoft/goutils/generics" "github.com/mandelsoft/goutils/optionutils" "ocm.software/ocm/api/ocm/compdesc" @@ -100,32 +101,36 @@ type TargetElement interface { } type TargetOptionImpl interface { - TargetOption + TargetElementOption ModificationOption BlobModificationOption } -type TargetOptions struct { +type TargetElementOptions struct { TargetElement TargetElement } -type TargetOption interface { - ApplyTargetOption(options *TargetOptions) +type TargetElementOption interface { + ApplyTargetOption(options *TargetElementOptions) } -func (m *TargetOptions) ApplyBlobModificationOption(opts *BlobModificationOptions) { - m.ApplyTargetOption(&opts.TargetOptions) +func (m *TargetElementOptions) ApplyBlobModificationOption(opts *BlobModificationOptions) { + m.ApplyTargetOption(&opts.TargetElementOptions) } -func (m *TargetOptions) ApplyModificationOption(opts *ModificationOptions) { - m.ApplyTargetOption(&opts.TargetOptions) +func (m *TargetElementOptions) ApplyModificationOption(opts *ModificationOptions) { + m.ApplyTargetOption(&opts.TargetElementOptions) } -func (m *TargetOptions) ApplyTargetOption(opts *TargetOptions) { +func (m *TargetElementOptions) ApplyElementModificationOption(opts *ElementModificationOptions) { + m.ApplyTargetOption(&opts.TargetElementOptions) +} + +func (m *TargetElementOptions) ApplyTargetOption(opts *TargetElementOptions) { optionutils.Transfer(&opts.TargetElement, m.TargetElement) } -func (m *TargetOptions) ApplyTargetOptions(list ...TargetOption) *TargetOptions { +func (m *TargetElementOptions) ApplyTargetOptions(list ...TargetElementOption) *TargetElementOptions { for _, o := range list { if o != nil { o.ApplyTargetOption(m) @@ -134,12 +139,55 @@ func (m *TargetOptions) ApplyTargetOptions(list ...TargetOption) *TargetOptions return m } -func NewTargetOptions(list ...TargetOption) *TargetOptions { - var m TargetOptions +func NewTargetElementOptions(list ...TargetElementOption) *TargetElementOptions { + var m TargetElementOptions m.ApplyTargetOptions(list...) return &m } +type ElementModificationOption interface { + ApplyElementModificationOption(opts *ElementModificationOptions) +} + +type ElementModificationOptions struct { + TargetElementOptions + + // ModifyElement disables the modification of signature relevant + // resource parts. + ModifyElement *bool +} + +func (m *ElementModificationOptions) ApplyBlobModificationOption(opts *BlobModificationOptions) { + m.ApplyElementModificationOption(&opts.ElementModificationOptions) +} + +func (m *ElementModificationOptions) ApplyModificationOption(opts *ModificationOptions) { + m.ApplyElementModificationOption(&opts.ElementModificationOptions) +} + +func (m *ElementModificationOptions) ApplyElementModificationOption(opts *ElementModificationOptions) { + optionutils.Transfer(&opts.ModifyElement, m.ModifyElement) +} + +func (m *ElementModificationOptions) ApplyElementModificationOptions(list ...ElementModificationOption) *ElementModificationOptions { + for _, o := range list { + if o != nil { + o.ApplyElementModificationOption(m) + } + } + return m +} + +func (m *ElementModificationOptions) IsModifyElement(def ...bool) bool { + return utils.AsBool(m.ModifyElement, def...) +} + +func NewElementModificationOptions(list ...ElementModificationOption) *ElementModificationOptions { + var m ElementModificationOptions + m.ApplyElementModificationOptions(list...) + return &m +} + type ModificationOption interface { ApplyModificationOption(opts *ModificationOptions) } @@ -149,12 +197,14 @@ type ModOptionImpl interface { BlobModificationOption } -type ModificationOptions struct { - TargetOptions +type ElemModOptionImpl interface { + ElementModificationOption + ModificationOption + BlobModificationOption +} - // ModifyResource disables the modification of signature releveant - // resource parts. - ModifyResource *bool +type ModificationOptions struct { + ElementModificationOptions // AcceptExistentDigests don't validate/recalculate the content digest // of resources. @@ -173,10 +223,6 @@ type ModificationOptions struct { SkipDigest *bool } -func (m *ModificationOptions) IsModifyResource() bool { - return utils.AsBool(m.ModifyResource) -} - func (m *ModificationOptions) IsAcceptExistentDigests() bool { return utils.AsBool(m.AcceptExistentDigests) } @@ -203,8 +249,8 @@ func (m *ModificationOptions) ApplyBlobModificationOption(opts *BlobModification } func (m *ModificationOptions) ApplyModificationOption(opts *ModificationOptions) { - m.TargetOptions.ApplyTargetOption(&opts.TargetOptions) - optionutils.Transfer(&opts.ModifyResource, m.ModifyResource) + m.TargetElementOptions.ApplyTargetOption(&opts.TargetElementOptions) + optionutils.Transfer(&opts.ModifyElement, m.ModifyElement) optionutils.Transfer(&opts.AcceptExistentDigests, m.AcceptExistentDigests) optionutils.Transfer(&opts.SkipDigest, m.SkipDigest) optionutils.Transfer(&opts.SkipVerify, m.SkipVerify) @@ -238,10 +284,17 @@ func (m TargetIndex) ApplyBlobModificationOption(opts *BlobModificationOptions) } func (m TargetIndex) ApplyModificationOption(opts *ModificationOptions) { - m.ApplyTargetOption(&opts.TargetOptions) + m.ApplyTargetOption(&opts.TargetElementOptions) } -func (m TargetIndex) ApplyTargetOption(opts *TargetOptions) { +func (m TargetIndex) ApplyElementModificationOption(opts *ElementModificationOptions) { + if m < 0 { + opts.ModifyElement = generics.Pointer(true) + } + m.ApplyTargetOption(&opts.TargetElementOptions) +} + +func (m TargetIndex) ApplyTargetOption(opts *TargetElementOptions) { opts.TargetElement = m } @@ -259,10 +312,14 @@ func (m TargetIdentityOrAppend) ApplyBlobModificationOption(opts *BlobModificati } func (m TargetIdentityOrAppend) ApplyModificationOption(opts *ModificationOptions) { - m.ApplyTargetOption(&opts.TargetOptions) + m.ApplyTargetOption(&opts.TargetElementOptions) +} + +func (m TargetIdentityOrAppend) ApplyElementModificationOption(opts *ElementModificationOptions) { + m.ApplyTargetOption(&opts.TargetElementOptions) } -func (m TargetIdentityOrAppend) ApplyTargetOption(opts *TargetOptions) { +func (m TargetIdentityOrAppend) ApplyTargetOption(opts *TargetElementOptions) { opts.TargetElement = m } @@ -285,10 +342,14 @@ func (m TargetIdentity) ApplyBlobModificationOption(opts *BlobModificationOption } func (m TargetIdentity) ApplyModificationOption(opts *ModificationOptions) { - m.ApplyTargetOption(&opts.TargetOptions) + m.ApplyTargetOption(&opts.TargetElementOptions) } -func (m TargetIdentity) ApplyTargetOption(opts *TargetOptions) { +func (m TargetIdentity) ApplyElementModificationOption(opts *ElementModificationOptions) { + m.ApplyTargetOption(&opts.TargetElementOptions) +} + +func (m TargetIdentity) ApplyTargetOption(opts *TargetElementOptions) { opts.TargetElement = m } @@ -313,27 +374,39 @@ func (m replaceElement) ApplyBlobModificationOption(opts *BlobModificationOption } func (m replaceElement) ApplyModificationOption(opts *ModificationOptions) { - m.ApplyTargetOption(&opts.TargetOptions) + m.ApplyTargetOption(&opts.TargetElementOptions) } -func (m replaceElement) ApplyTargetOption(opts *TargetOptions) { +func (m replaceElement) ApplyElementModificationOption(opts *ElementModificationOptions) { + m.ApplyTargetOption(&opts.TargetElementOptions) +} + +func (m replaceElement) ApplyTargetOption(opts *TargetElementOptions) { opts.TargetElement = m } //////////////////////////////////////////////////////////////////////////////// -type modifyresource bool +type modifyelement bool -func (m modifyresource) ApplyBlobModificationOption(opts *BlobModificationOptions) { +func (m modifyelement) ApplyBlobModificationOption(opts *BlobModificationOptions) { m.ApplyModificationOption(&opts.ModificationOptions) } -func (m modifyresource) ApplyModificationOption(opts *ModificationOptions) { - opts.ModifyResource = utils.BoolP(m) +func (m modifyelement) ApplyModificationOption(opts *ModificationOptions) { + opts.ModifyElement = utils.BoolP(m) +} + +func (m modifyelement) ApplyElementModificationOption(opts *ElementModificationOptions) { + opts.ModifyElement = utils.BoolP(m) } func ModifyResource(flag ...bool) ModOptionImpl { - return modifyresource(utils.OptionalDefaultedBool(true, flag...)) + return modifyelement(utils.OptionalDefaultedBool(true, flag...)) +} + +func ModifyElement(flag ...bool) ElemModOptionImpl { + return modifyelement(utils.OptionalDefaultedBool(true, flag...)) } //////////////////////////////////////////////////////////////////////////////// diff --git a/api/ocm/internal/repository.go b/api/ocm/internal/repository.go index 4aa4008afc..7ed53168f7 100644 --- a/api/ocm/internal/repository.go +++ b/api/ocm/internal/repository.go @@ -144,10 +144,10 @@ type ComponentVersionAccess interface { // SetSource updates or sets anew source. The options only use the // target options. All other options are ignored. - SetSource(*SourceMeta, compdesc.AccessSpec, ...TargetOption) error + SetSource(*SourceMeta, compdesc.AccessSpec, ...TargetElementOption) error // SetSourceByAccess updates or sets anew source. The options only use the // target options. All other options are ignored. - SetSourceByAccess(art SourceAccess, opts ...TargetOption) error + SetSourceByAccess(art SourceAccess, opts ...TargetElementOption) error GetReference(meta metav1.Identity) (ComponentReference, error) GetReferenceIndex(meta metav1.Identity) int @@ -155,7 +155,10 @@ type ComponentVersionAccess interface { GetReferences() []ComponentReference SelectReferences(sel ...refsel.Selector) ([]ComponentReference, error) - SetReference(ref *ComponentReference, opts ...TargetOption) error + // SetReference adds or updates a reference. By default, it does not allow for + // signature relevant changes. If such operations should be possible + // the option ModifyElement() has to be passed as option. + SetReference(ref *ComponentReference, opts ...ElementModificationOption) error // AddBlob adds a local blob and returns an appropriate local access spec. AddBlob(blob BlobAccess, artType, refName string, global AccessSpec, opts ...BlobUploadOption) (AccessSpec, error) @@ -166,7 +169,7 @@ type ComponentVersionAccess interface { AdjustSourceAccess(meta *SourceMeta, acc compdesc.AccessSpec) error // SetSourceBlob updates or sets anew source. The options only use the // target options. All other options are ignored. - SetSourceBlob(meta *SourceMeta, blob BlobAccess, refname string, global AccessSpec, opts ...TargetOption) error + SetSourceBlob(meta *SourceMeta, blob BlobAccess, refname string, global AccessSpec, opts ...TargetElementOption) error // AccessMethod provides an access method implementation for // an access spec. This might be a repository local implementation diff --git a/api/ocm/modopts.go b/api/ocm/modopts.go index 5c271f0e6b..a980ae0485 100644 --- a/api/ocm/modopts.go +++ b/api/ocm/modopts.go @@ -9,9 +9,12 @@ import ( ) type ( - TargetElement = internal.TargetElement - TargetOption = internal.TargetOption - TargetOptions = internal.TargetOptions + TargetElement = internal.TargetElement + TargetElementOption = internal.TargetElementOption + TargetElementOptions = internal.TargetElementOptions + + ElementModificationOption = internal.ElementModificationOption + ElementModificationOptions = internal.ElementModificationOptions ModificationOption = internal.ModificationOption ModificationOptions = internal.ModificationOptions @@ -60,7 +63,7 @@ func NewModificationOptions(list ...ModificationOption) *ModificationOptions { } func TargetIndex(idx int) internal.TargetOptionImpl { - return internal.TargetIndex(-1) + return internal.TargetIndex(idx) } const AppendElement = internal.TargetIndex(-1) @@ -75,10 +78,15 @@ func TargetIdentityOrCreate(id v1.Identity) internal.TargetOptionImpl { return internal.TargetIdentityOrAppend(id) } +// Deprecated: use ModifyElement. func ModifyResource(flag ...bool) internal.ModOptionImpl { return internal.ModifyResource(flag...) } +func ModifyElement(flag ...bool) internal.ElemModOptionImpl { + return internal.ModifyElement(flag...) +} + func AcceptExistentDigests(flag ...bool) internal.ModOptionImpl { return internal.AcceptExistentDigests(flag...) } diff --git a/api/ocm/tools/signing/handler_test.go b/api/ocm/tools/signing/handler_test.go index 6707f84a8f..4b7984543c 100644 --- a/api/ocm/tools/signing/handler_test.go +++ b/api/ocm/tools/signing/handler_test.go @@ -58,18 +58,28 @@ var _ = Describe("Simple signing handlers", func() { meta := ocm.NewResourceMeta("blob", resourcetypes.PLAIN_TEXT, v1.LocalRelation) meta.Version = "v1" - meta.ExtraIdentity = map[string]string{} MustBeSuccessful(cv.SetResourceBlob(meta, blobaccess.ForString(mime.MIME_TEXT, "test data"), "", nil)) + meta.ExtraIdentity = map[string]string{} meta.Version = "v2" MustBeSuccessful(cv.SetResourceBlob(meta, blobaccess.ForString(mime.MIME_TEXT, "other test data"), "", nil, ocm.TargetIndex(-1))) }) - It("signs without modification", func() { + It("signs without modification (compatibility)", func() { Must(signing.SignComponentVersion(cv, "signature", signing.PrivateKey("signature", priv))) cd := cv.GetDescriptor() + cd.Resources[0].ExtraIdentity = v1.Identity{} + cd.Resources[1].ExtraIdentity = v1.Identity{} Expect(len(cd.Resources)).To(Equal(2)) Expect(len(cd.Resources[0].ExtraIdentity)).To(Equal(0)) Expect(len(cd.Resources[1].ExtraIdentity)).To(Equal(0)) }) + + It("signs defaulted", func() { + Must(signing.SignComponentVersion(cv, "signature", signing.PrivateKey("signature", priv))) + cd := cv.GetDescriptor() + Expect(len(cd.Resources)).To(Equal(2)) + Expect(len(cd.Resources[0].ExtraIdentity)).To(Equal(1)) + Expect(len(cd.Resources[1].ExtraIdentity)).To(Equal(1)) + }) }) }) diff --git a/api/ocm/tools/signing/signing_test.go b/api/ocm/tools/signing/signing_test.go index f374ec0528..a54e91c9bb 100644 --- a/api/ocm/tools/signing/signing_test.go +++ b/api/ocm/tools/signing/signing_test.go @@ -1286,6 +1286,159 @@ applying to version "github.com/mandelsoft/ref2:v1"[github.com/mandelsoft/ref2:v CheckStore(store, common.NewNameVersion(COMPONENTA, VERSION)) }) }) + + Context("handle extra identity", func() { + BeforeEach(func() { + env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { + env.ComponentVersion(COMPONENTA, VERSION, func() { + env.Provider(PROVIDER) + env.Resource("test", "v1", resourcetypes.PLAIN_TEXT, metav1.ExternalRelation, func() { + env.BlobStringData(mime.MIME_TEXT, "test data") + }) + env.Resource("test", "v2", resourcetypes.PLAIN_TEXT, metav1.ExternalRelation, func() { + env.BlobStringData(mime.MIME_TEXT, "extended test data") + env.ModificationOptions(ocm.AppendElement) + env.ExtraIdentities() + }) + }) + }) + }) + + It("signs version with non-unique resource names", func() { + session := datacontext.NewSession() + defer session.Close() + + src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) + archcloser := session.AddCloser(src) + + cv := Must(src.LookupComponentVersion(COMPONENTA, VERSION)) + closer := session.AddCloser(cv) + + cd := cv.GetDescriptor() + + Expect(cd.Resources[0].GetIdentity(cv.GetDescriptor().Resources)).To(YAMLEqual(` +name: test +version: v1 +`)) + Expect(cd.Resources[0].ExtraIdentity).To(YAMLEqual(` +version: v1 +`)) + Expect(cd.Resources[1].GetIdentity(cv.GetDescriptor().Resources)).To(YAMLEqual(` +name: test +version: v2 +`)) + Expect(cd.Resources[1].ExtraIdentity).To(YAMLEqual(` +version: v2 +`)) + data := Must(compdesc.Encode(cd, compdesc.DefaultYAMLCodec)) + Expect(string(data)).To(YAMLEqual(` + component: + componentReferences: [] + name: github.com/mandelsoft/test + provider: mandelsoft + repositoryContexts: [] + resources: + - access: + localReference: sha256:916f0027a575074ce72a331777c3478d6513f786a591bd892da1a577bf2335f9 + mediaType: text/plain + type: localBlob + digest: + hashAlgorithm: SHA-256 + normalisationAlgorithm: genericBlobDigest/v1 + value: 916f0027a575074ce72a331777c3478d6513f786a591bd892da1a577bf2335f9 + name: test + relation: external + type: plainText + version: v1 + extraIdentity: + version: v1 + - access: + localReference: sha256:920ce99fb13b43ca0408caee6e61f6335ea5156d79aa98e733e1ed2393e0f649 + mediaType: text/plain + type: localBlob + digest: + hashAlgorithm: SHA-256 + normalisationAlgorithm: genericBlobDigest/v1 + value: 920ce99fb13b43ca0408caee6e61f6335ea5156d79aa98e733e1ed2393e0f649 + name: test + relation: external + type: plainText + version: v2 + extraIdentity: + version: v2 + sources: [] + version: v1 + meta: + schemaVersion: v2 +`)) + + digest := "70c1b7f5e2260a283e24788c81ea7f8f6e9a70a8544dbf62d6f3a27285f6b633" + + pr, buf := common.NewBufferedPrinter() + // key taken from signing attr + dig := Must(SignComponentVersion(cv, SIGNATURE, SignerByAlgo(SIGN_ALGO), Printer(pr))) + Expect(closer.Close()).To(Succeed()) + Expect(archcloser.Close()).To(Succeed()) + Expect(dig.Value).To(StringEqualWithContext(digest)) + + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/test:v1]... + resource 0: "name"="test","version"="v1": digest SHA-256:916f0027a575074ce72a331777c3478d6513f786a591bd892da1a577bf2335f9[genericBlobDigest/v1] + resource 1: "name"="test","version"="v2": digest SHA-256:920ce99fb13b43ca0408caee6e61f6335ea5156d79aa98e733e1ed2393e0f649[genericBlobDigest/v1] +`)) + + src = Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env)) + session.AddCloser(src) + cv = Must(src.LookupComponentVersion(COMPONENTA, VERSION)) + session.AddCloser(cv) + + cd = cv.GetDescriptor().Copy() + Expect(len(cd.Signatures)).To(Equal(1)) + cd.Signatures = nil // for comparison + data = Must(compdesc.Encode(cd, compdesc.DefaultYAMLCodec)) + + Expect(string(data)).To(YAMLEqual(` + component: + componentReferences: [] + name: github.com/mandelsoft/test + provider: mandelsoft + repositoryContexts: [] + resources: + - access: + localReference: sha256:916f0027a575074ce72a331777c3478d6513f786a591bd892da1a577bf2335f9 + mediaType: text/plain + type: localBlob + digest: + hashAlgorithm: SHA-256 + normalisationAlgorithm: genericBlobDigest/v1 + value: 916f0027a575074ce72a331777c3478d6513f786a591bd892da1a577bf2335f9 + name: test + relation: external + type: plainText + version: v1 + extraIdentity: + version: v1 + - access: + localReference: sha256:920ce99fb13b43ca0408caee6e61f6335ea5156d79aa98e733e1ed2393e0f649 + mediaType: text/plain + type: localBlob + digest: + hashAlgorithm: SHA-256 + normalisationAlgorithm: genericBlobDigest/v1 + value: 920ce99fb13b43ca0408caee6e61f6335ea5156d79aa98e733e1ed2393e0f649 + name: test + relation: external + type: plainText + version: v2 + extraIdentity: + version: v2 + sources: [] + version: v1 + meta: + schemaVersion: v2 +`)) + }) + }) }) func CheckStore(store VerifiedStore, ve common.VersionedElement) { diff --git a/api/ocm/tools/transfer/transfer.go b/api/ocm/tools/transfer/transfer.go index 8490138868..ad6121728e 100644 --- a/api/ocm/tools/transfer/transfer.go +++ b/api/ocm/tools/transfer/transfer.go @@ -273,7 +273,7 @@ func copyVersion(printer common.Printer, log logging.Logger, hist common.History err = handler.HandleTransferResource(r, m, hint, t) } else { if err == nil { // old resource found -> keep current access method - t.SetResource(r.Meta(), old.Access, ocm.ModifyResource(), ocm.SkipVerify()) + t.SetResource(r.Meta(), old.Access, ocm.ModifyElement(), ocm.SkipVerify()) } notifyArtifactInfo(printer, log, "resource", i, r.Meta(), hint, "already present") } diff --git a/cmds/ocm/commands/ocmcmds/common/addhdlrs/base.go b/cmds/ocm/commands/ocmcmds/common/addhdlrs/base.go index 41512f23c9..3927286a42 100644 --- a/cmds/ocm/commands/ocmcmds/common/addhdlrs/base.go +++ b/cmds/ocm/commands/ocmcmds/common/addhdlrs/base.go @@ -37,6 +37,10 @@ func (h *ResourceSpecHandlerBase) AddFlags(opts *pflag.FlagSet) { h.options.AddFlags(opts) } -func (h *ResourceSpecHandlerBase) GetTargetOpts() []ocm.TargetOption { - return options.FindOptions[ocm.TargetOption](h.options) +func (h *ResourceSpecHandlerBase) GetTargetOpts() []ocm.TargetElementOption { + return options.FindOptions[ocm.TargetElementOption](h.options) +} + +func (h *ResourceSpecHandlerBase) GetElementModificationOpts() []ocm.ElementModificationOption { + return options.FindOptions[ocm.ElementModificationOption](h.options) } diff --git a/cmds/ocm/commands/ocmcmds/common/addhdlrs/options.go b/cmds/ocm/commands/ocmcmds/common/addhdlrs/options.go index e4e305a268..2297e16847 100644 --- a/cmds/ocm/commands/ocmcmds/common/addhdlrs/options.go +++ b/cmds/ocm/commands/ocmcmds/common/addhdlrs/options.go @@ -1,36 +1,66 @@ package addhdlrs import ( + "github.com/mandelsoft/goutils/generics" "github.com/spf13/pflag" "ocm.software/ocm/api/ocm" ) type Options struct { + // Replace enables to replace existing elements (same raw identity) with a different version instead + // of appending a new element. Replace bool + // PreserveSignature disables the modification of signature relevant information. + PreserveSignature bool } -var _ ocm.ModificationOption = (*Options)(nil) +var ( + _ ocm.ModificationOption = (*Options)(nil) + _ ocm.ElementModificationOption = (*Options)(nil) + _ ocm.BlobModificationOption = (*Options)(nil) + _ ocm.TargetElementOption = (*Options)(nil) +) func (o *Options) AddFlags(fs *pflag.FlagSet) { - f := fs.Lookup("replace") + o.addBoolFlag(fs, &o.Replace, "replace", "R", false, "replace existing elements") + o.addBoolFlag(fs, &o.PreserveSignature, "preserve-signature", "P", false, "preserve existing signatures") +} + +func (o *Options) addBoolFlag(fs *pflag.FlagSet, p *bool, long string, short string, def bool, usage string) { + f := fs.Lookup(long) if f != nil { - if f.Value.Type() == "bool" { - return + if f.Value.Type() != "bool" { + f = nil } } - fs.BoolVarP(&o.Replace, "replace", "R", false, "replace existing elements") + if f == nil { + fs.BoolVarP(p, long, short, def, usage) + } +} + +func (o *Options) applyPreserve(opts *ocm.ElementModificationOptions) { + if !o.PreserveSignature { + opts.ModifyElement = generics.Pointer(true) + } } func (o *Options) ApplyBlobModificationOption(opts *ocm.BlobModificationOptions) { - o.ApplyTargetOption(&opts.TargetOptions) + o.applyPreserve(&opts.ElementModificationOptions) + o.ApplyTargetOption(&opts.TargetElementOptions) } func (o *Options) ApplyModificationOption(opts *ocm.ModificationOptions) { - o.ApplyTargetOption(&opts.TargetOptions) + o.applyPreserve(&opts.ElementModificationOptions) + o.ApplyTargetOption(&opts.TargetElementOptions) } -func (o *Options) ApplyTargetOption(opts *ocm.TargetOptions) { +func (o *Options) ApplyElementModificationOption(opts *ocm.ElementModificationOptions) { + o.applyPreserve(opts) + o.ApplyTargetOption(&opts.TargetElementOptions) +} + +func (o *Options) ApplyTargetOption(opts *ocm.TargetElementOptions) { if !o.Replace { opts.TargetElement = ocm.AppendElement } @@ -40,6 +70,9 @@ func (o *Options) Description() string { return ` The --replace option allows users to specify whether adding an element with the same name and extra identity but different version as an -existing element append (false) or replace (true) the existing element. +existing element, append (false) or replace (true) the existing element. + +The --preserve-signature option prohibits changes of signature +relevant elements. ` } diff --git a/cmds/ocm/commands/ocmcmds/common/addhdlrs/refs/elements.go b/cmds/ocm/commands/ocmcmds/common/addhdlrs/refs/elements.go index 3c529accd7..ddab09974b 100644 --- a/cmds/ocm/commands/ocmcmds/common/addhdlrs/refs/elements.go +++ b/cmds/ocm/commands/ocmcmds/common/addhdlrs/refs/elements.go @@ -66,7 +66,8 @@ func (h *ResourceSpecHandler) Set(v ocm.ComponentVersionAccess, r addhdlrs.Eleme }, ComponentName: spec.ComponentName, } - return v.SetReference(meta, h.GetTargetOpts()...) + + return v.SetReference(meta, h.GetElementModificationOpts()...) } //////////////////////////////////////////////////////////////////////////////// diff --git a/cmds/ocm/commands/ocmcmds/common/addhdlrs/rscs/elements.go b/cmds/ocm/commands/ocmcmds/common/addhdlrs/rscs/elements.go index ef57f97e49..c51fa9d12e 100644 --- a/cmds/ocm/commands/ocmcmds/common/addhdlrs/rscs/elements.go +++ b/cmds/ocm/commands/ocmcmds/common/addhdlrs/rscs/elements.go @@ -63,7 +63,7 @@ func (*ResourceSpecHandler) RequireInputs() bool { return true } -func (ResourceSpecHandler) Decode(data []byte) (addhdlrs.ElementSpec, error) { +func (*ResourceSpecHandler) Decode(data []byte) (addhdlrs.ElementSpec, error) { var desc ResourceSpec err := runtime.DefaultYAMLEncoding.Unmarshal(data, &desc) if err != nil { @@ -72,7 +72,7 @@ func (ResourceSpecHandler) Decode(data []byte) (addhdlrs.ElementSpec, error) { return &desc, nil } -func (h ResourceSpecHandler) Set(v ocm.ComponentVersionAccess, r addhdlrs.Element, acc compdesc.AccessSpec) error { +func (h *ResourceSpecHandler) Set(v ocm.ComponentVersionAccess, r addhdlrs.Element, acc compdesc.AccessSpec) error { spec, ok := r.Spec().(*ResourceSpec) if !ok { return fmt.Errorf("element spec is not a valid resource spec, failed to assert type %T to ResourceSpec", r.Spec()) @@ -105,9 +105,11 @@ func (h ResourceSpecHandler) Set(v ocm.ComponentVersionAccess, r addhdlrs.Elemen if spec.SkipDigestGeneration { opts = append(opts, ocm.SkipDigest()) //nolint:staticcheck // skip digest still used for tests } - if ocm.IsIntermediate(v.Repository().GetSpecification()) { - opts = append(opts, ocm.ModifyResource()) - } + /* + if ocm.IsIntermediate(v.Repository().GetSpecification()) { + opts = append(opts, ocm.ModifyElement()) + } + */ return v.SetResource(meta, acc, opts...) } diff --git a/cmds/ocm/commands/ocmcmds/common/addhdlrs/srcs/elements.go b/cmds/ocm/commands/ocmcmds/common/addhdlrs/srcs/elements.go index 6ab83fca1d..9d8dced1dc 100644 --- a/cmds/ocm/commands/ocmcmds/common/addhdlrs/srcs/elements.go +++ b/cmds/ocm/commands/ocmcmds/common/addhdlrs/srcs/elements.go @@ -29,11 +29,11 @@ func New(opts ...options.Options) *ResourceSpecHandler { return &ResourceSpecHandler{addhdlrs.NewBase(opts...)} } -func (ResourceSpecHandler) Key() string { +func (*ResourceSpecHandler) Key() string { return "source" } -func (ResourceSpecHandler) RequireInputs() bool { +func (*ResourceSpecHandler) RequireInputs() bool { return true } diff --git a/cmds/ocm/commands/ocmcmds/components/hash/cmd.go b/cmds/ocm/commands/ocmcmds/components/hash/cmd.go index 43922b6ff0..b2ef1f9932 100644 --- a/cmds/ocm/commands/ocmcmds/components/hash/cmd.go +++ b/cmds/ocm/commands/ocmcmds/components/hash/cmd.go @@ -13,6 +13,7 @@ import ( "ocm.software/ocm/api/ocm" "ocm.software/ocm/api/ocm/compdesc" common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/utils/out" "ocm.software/ocm/cmds/ocm/commands/common/options/closureoption" ocmcommon "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/handlers/comphdlr" @@ -180,17 +181,26 @@ func (h *action) Close() error { func (h *action) Out() error { if len(h.norms) > 1 { - dir := h.mode.outfile - dir = strings.TrimSuffix(dir, ".ncd") - err := h.ctx.FileSystem().Mkdir(dir, 0o755) - if err != nil { - return fmt.Errorf("cannot create output dir %s", dir) - } - for k, n := range h.norms { - p := filepath.Join(dir, k.String()) - err := h.write(p+".ncd", n) + if h.mode.outfile == "" || h.mode.outfile == "-" { + for _, n := range h.norms { + err := h.write(h.mode.outfile, n) + if err != nil { + return err + } + } + } else { + dir := h.mode.outfile + dir = strings.TrimSuffix(dir, ".ncd") + err := h.ctx.FileSystem().Mkdir(dir, 0o755) if err != nil { - return err + return fmt.Errorf("cannot create output dir %s", dir) + } + for k, n := range h.norms { + p := filepath.Join(dir, k.String()) + err := h.write(p+".ncd", n) + if err != nil { + return err + } } } } else { @@ -202,12 +212,17 @@ func (h *action) Out() error { } func (h *action) write(p, n string) error { - dir := filepath.Dir(p) - err := h.ctx.FileSystem().MkdirAll(dir, 0o755) - if err != nil { - return fmt.Errorf("cannot create dir %s", dir) + if p == "" || p == "-" { + out.Outln(h.ctx, n) + return nil + } else { + dir := filepath.Dir(p) + err := h.ctx.FileSystem().MkdirAll(dir, 0o755) + if err != nil { + return fmt.Errorf("cannot create dir %s", dir) + } + return vfs.WriteFile(h.ctx.FileSystem(), p, []byte(n), 0o644) } - return vfs.WriteFile(h.ctx.FileSystem(), p, []byte(n), 0o644) } ///////// diff --git a/cmds/ocm/commands/ocmcmds/components/hash/cmd_test.go b/cmds/ocm/commands/ocmcmds/components/hash/cmd_test.go index 7783820f74..f79e2c1c05 100644 --- a/cmds/ocm/commands/ocmcmds/components/hash/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/components/hash/cmd_test.go @@ -2,6 +2,8 @@ package hash_test import ( "bytes" + "crypto/sha256" + "encoding/hex" "fmt" . "github.com/mandelsoft/goutils/testutils" @@ -50,6 +52,48 @@ test.de/x v1 37f7f500d87f4b0a8765649f7c047db382e272b73e042805131df57279991b `)) }) + It("normalize component archive v1", func() { + env.ComponentArchive(ARCH, accessio.FormatDirectory, COMP, VERSION, func() { + env.Provider(PROVIDER) + }) + + buf := bytes.NewBuffer(nil) + Expect(env.CatchOutput(buf).Execute("hash", "components", ARCH, "-O", "-", "-o", "norm")).To(Succeed()) + Expect(buf.String()).To(Equal(`[{"component":[{"componentReferences":[]},{"name":"test.de/x"},{"provider":"mandelsoft"},{"resources":[]},{"version":"v1"}]},{"meta":[{"schemaVersion":"v2"}]}] +`)) + }) + + It("normalize component archive v2", func() { + env.ComponentArchive(ARCH, accessio.FormatDirectory, COMP, VERSION, func() { + env.Provider(PROVIDER) + }) + + buf := bytes.NewBuffer(nil) + Expect(env.CatchOutput(buf).Execute("hash", "components", ARCH, "-N", "jsonNormalisation/v2", "-o", "norm")).To(Succeed()) + Expect(buf.String()).To(StringEqualTrimmedWithContext(`{"component":{"componentReferences":[],"name":"test.de/x","provider":{"name":"mandelsoft"},"resources":[],"sources":[],"version":"v1"}} +`)) + }) + + It("check hash", func() { + env.ComponentArchive(ARCH, accessio.FormatDirectory, COMP, VERSION, func() { + env.Provider(PROVIDER) + }) + + buf := bytes.NewBuffer(nil) + Expect(env.CatchOutput(buf).Execute("hash", "components", ARCH, "-o", "yaml")).To(Succeed()) + Expect(buf.String()).To(StringEqualTrimmedWithContext(` +--- +component: test.de/x +context: [] +hash: 37f7f500d87f4b0a8765649f7c047db382e272b73e042805131df57279991b2b +normalized: '[{"component":[{"componentReferences":[]},{"name":"test.de/x"},{"provider":"mandelsoft"},{"resources":[]},{"version":"v1"}]},{"meta":[{"schemaVersion":"v2"}]}]' +version: v1 +`)) + + h := sha256.Sum256([]byte(`[{"component":[{"componentReferences":[]},{"name":"test.de/x"},{"provider":"mandelsoft"},{"resources":[]},{"version":"v1"}]},{"meta":[{"schemaVersion":"v2"}]}]`)) + Expect(hex.EncodeToString(h[:])).To(Equal("37f7f500d87f4b0a8765649f7c047db382e272b73e042805131df57279991b2b")) + }) + It("hash component archive with resources", func() { env.ComponentArchive(ARCH, accessio.FormatDirectory, COMP, VERSION, func() { env.Provider(PROVIDER) diff --git a/cmds/ocm/commands/ocmcmds/components/hash/options.go b/cmds/ocm/commands/ocmcmds/components/hash/options.go index d3165c732b..99346e88a2 100644 --- a/cmds/ocm/commands/ocmcmds/components/hash/options.go +++ b/cmds/ocm/commands/ocmcmds/components/hash/options.go @@ -33,7 +33,7 @@ func (o *Option) AddFlags(fs *pflag.FlagSet) { fs.BoolVarP(&o.Actual, "actual", "", false, "use actual component descriptor") fs.BoolVarP(&o.Update, "update", "U", false, "update digests in component version") fs.BoolVarP(&o.Verify, "verify", "V", false, "verify digests found in component version") - fs.StringVarP(&o.outfile, "outfile", "O", "norm.ncd", "Output file for normalized component descriptor") + fs.StringVarP(&o.outfile, "outfile", "O", "-", "Output file for normalized component descriptor") } func (o *Option) Complete(cmd *Command) error { diff --git a/docs/reference/ocm_add_componentversions.md b/docs/reference/ocm_add_componentversions.md index 73b8857604..b400223c54 100644 --- a/docs/reference/ocm_add_componentversions.md +++ b/docs/reference/ocm_add_componentversions.md @@ -26,6 +26,7 @@ componentversions, componentversion, cv, components, component, comps, comp, c -h, --help help for componentversions --lookup stringArray repository name or spec for closure lookup fallback -O, --output string output file for dry-run + -P, --preserve-signature preserve existing signatures -R, --replace replace existing elements -S, --scheme string schema version (default "v2") -s, --settings stringArray settings file with variable settings (yaml) @@ -55,7 +56,10 @@ components will be added by value. The --replace option allows users to specify whether adding an element with the same name and extra identity but different version as an -existing element append (false) or replace (true) the existing element. +existing element, append (false) or replace (true) the existing element. + +The --preserve-signature option prohibits changes of signature +relevant elements. The source, resource and reference list can be composed according to the commands diff --git a/docs/reference/ocm_add_references.md b/docs/reference/ocm_add_references.md index 6af5dfca9c..dfc6834cb1 100644 --- a/docs/reference/ocm_add_references.md +++ b/docs/reference/ocm_add_references.md @@ -20,6 +20,7 @@ references, reference, refs -F, --file string target file/directory (default "component-archive") -h, --help help for references -O, --output string output file for dry-run + -P, --preserve-signature preserve existing signatures -R, --replace replace existing elements -s, --settings stringArray settings file with variable settings (yaml) --templater string templater to use (go, none, spiff, subst) (default "subst") @@ -112,7 +113,10 @@ There are several templaters that can be selected by the --templater--replace option allows users to specify whether adding an element with the same name and extra identity but different version as an -existing element append (false) or replace (true) the existing element. +existing element, append (false) or replace (true) the existing element. + +The --preserve-signature option prohibits changes of signature +relevant elements. All yaml/json defined resources can be templated. diff --git a/docs/reference/ocm_add_resources.md b/docs/reference/ocm_add_resources.md index 88985eff7b..99d746c1ad 100644 --- a/docs/reference/ocm_add_resources.md +++ b/docs/reference/ocm_add_resources.md @@ -20,6 +20,7 @@ resources, resource, res, r -F, --file string target file/directory (default "component-archive") -h, --help help for resources -O, --output string output file for dry-run + -P, --preserve-signature preserve existing signatures -R, --replace replace existing elements -s, --settings stringArray settings file with variable settings (yaml) --skip-digest-generation skip digest creation @@ -976,7 +977,10 @@ shown below. The --replace option allows users to specify whether adding an element with the same name and extra identity but different version as an -existing element append (false) or replace (true) the existing element. +existing element, append (false) or replace (true) the existing element. + +The --preserve-signature option prohibits changes of signature +relevant elements. All yaml/json defined resources can be templated. diff --git a/docs/reference/ocm_add_sources.md b/docs/reference/ocm_add_sources.md index 8426c5e600..54f7d17b70 100644 --- a/docs/reference/ocm_add_sources.md +++ b/docs/reference/ocm_add_sources.md @@ -20,6 +20,7 @@ sources, source, src, s -F, --file string target file/directory (default "component-archive") -h, --help help for sources -O, --output string output file for dry-run + -P, --preserve-signature preserve existing signatures -R, --replace replace existing elements -s, --settings stringArray settings file with variable settings (yaml) --templater string templater to use (go, none, spiff, subst) (default "subst") @@ -974,7 +975,10 @@ shown below. The --replace option allows users to specify whether adding an element with the same name and extra identity but different version as an -existing element append (false) or replace (true) the existing element. +existing element, append (false) or replace (true) the existing element. + +The --preserve-signature option prohibits changes of signature +relevant elements. All yaml/json defined resources can be templated. diff --git a/docs/reference/ocm_hash_componentversions.md b/docs/reference/ocm_hash_componentversions.md index a00153f15a..561a9c16c9 100644 --- a/docs/reference/ocm_hash_componentversions.md +++ b/docs/reference/ocm_hash_componentversions.md @@ -22,7 +22,7 @@ componentversions, componentversion, cv, components, component, comps, comp, c --latest restrict component versions to latest --lookup stringArray repository name or spec for closure lookup fallback -N, --normalization string normalization algorithm (default "jsonNormalisation/v1") - -O, --outfile string Output file for normalized component descriptor (default "norm.ncd") + -O, --outfile string Output file for normalized component descriptor (default "-") -o, --output string output mode (JSON, json, norm, wide, yaml) -r, --recursive follow component reference nesting --repo string repository name or spec