Skip to content

Commit

Permalink
support for deferred error propagation (#167)
Browse files Browse the repository at this point in the history
A typical problem is to handle errors provided by deferred functions,
for example Close().

Another scenario is the need to wrap all errors provided in a function
with a common context description.

The errors package now provides a function PropagateError, which
can be used to solve both problems.

If takes an optional error context, which will be used to wrap
a potential error returned by the calling function.
Additionally it is possible to add a function providing
an error. This error, if present is composed with an error
provided by the function return when called as deferred function.

Example:

func() (efferr error) {
  ...
  defer errors.PropagateErrorf(&efferr, stream.Close, "error context")
  ...
  return err
}

This error providing function may be the Finalize() method of a
utils.Finalizer.

This is now directly supported by the Finalizer.

Example:

func() (efferr error) {
  var finalize utils.Finalizer
  ...
  defer finalize.FinalizeWithErrorPropagationf(&efferr, "error context")
  ...
  finalize.Close(stream)
  ...
  return err
}
  • Loading branch information
mandelsoft authored Oct 30, 2022
1 parent 3b98a13 commit 852bdfc
Show file tree
Hide file tree
Showing 23 changed files with 522 additions and 93 deletions.
2 changes: 1 addition & 1 deletion cmds/ocm/commands/ocmcmds/common/cmds/signing/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ func (a *action) Add(e interface{}) error {
cv := o.ComponentVersion
sopts := *a.sopts
sopts.Resolver = ocm.NewCompoundResolver(o.Repository, a.sopts.Resolver)
d, err := signing.Apply(a.printer, &a.state, cv, &sopts)
d, err := signing.Apply(a.printer, &a.state, cv, &sopts, true)
a.errlist.Add(err)
if err == nil {
a.printer.Printf("successfully %s %s:%s (digest %s:%s)\n", a.desc[0], cv.GetName(), cv.GetVersion(), d.HashAlgorithm, d.Value)
Expand Down
8 changes: 4 additions & 4 deletions cmds/ocm/commands/ocmcmds/components/sign/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,15 +187,15 @@ successfully signed github.com/mandelsoft/ref:v1 (digest sha256:` + digest + `)
buf := bytes.NewBuffer(nil)
Expect(env.CatchErrorOutput(buf).Execute("sign", "components", "-s", SIGNATURE, "-K", PRIVKEY, "--repo", ARCH, COMPONENTB+":"+VERSION)).To(HaveOccurred())
Expect(buf.String()).To(StringEqualTrimmedWithContext(`
Error: {signing: failed resolving component reference ref[github.com/mandelsoft/test:v1] in github.com/mandelsoft/ref:v1: ocm reference "github.com/mandelsoft/test:v1" not found}
Error: signing: github.com/mandelsoft/ref:v1: failed resolving component reference ref[github.com/mandelsoft/test:v1]: ocm reference "github.com/mandelsoft/test:v1" not found
`))
})

It("sign archive", func() {
buf := bytes.NewBuffer(nil)
Expect(env.CatchErrorOutput(buf).Execute("sign", "components", "-s", SIGNATURE, "-K", PRIVKEY, ARCH)).To(HaveOccurred())
Expect(buf.String()).To(StringEqualTrimmedWithContext(`
Error: {signing: failed resolving component reference ref[github.com/mandelsoft/test:v1] in github.com/mandelsoft/ref:v1: ocm reference "github.com/mandelsoft/test:v1" not found}
Error: signing: github.com/mandelsoft/ref:v1: failed resolving component reference ref[github.com/mandelsoft/test:v1]: ocm reference "github.com/mandelsoft/test:v1" not found
`))
})
})
Expand All @@ -215,15 +215,15 @@ Error: {signing: failed resolving component reference ref[github.com/mandelsoft/
buf := bytes.NewBuffer(nil)
Expect(env.CatchErrorOutput(buf).Execute("sign", "components", "-s", SIGNATURE, "-K", PRIVKEY, "--repo", ARCH, COMPONENTB+":"+VERSION)).To(HaveOccurred())
Expect(buf.String()).To(StringEqualTrimmedWithContext(`
Error: {signing: failed resolving component reference ref[github.com/mandelsoft/test:v1] in github.com/mandelsoft/ref:v1: ocm reference "github.com/mandelsoft/test:v1" not found}
Error: signing: github.com/mandelsoft/ref:v1: failed resolving component reference ref[github.com/mandelsoft/test:v1]: ocm reference "github.com/mandelsoft/test:v1" not found
`))
})

It("sign archive", func() {
buf := bytes.NewBuffer(nil)
Expect(env.CatchErrorOutput(buf).Execute("sign", "components", "-s", SIGNATURE, "-K", PRIVKEY, ARCH)).To(HaveOccurred())
Expect(buf.String()).To(StringEqualTrimmedWithContext(`
Error: {signing: failed resolving component reference ref[github.com/mandelsoft/test:v1] in github.com/mandelsoft/ref:v1: ocm reference "github.com/mandelsoft/test:v1" not found}
Error: signing: github.com/mandelsoft/ref:v1: failed resolving component reference ref[github.com/mandelsoft/test:v1]: ocm reference "github.com/mandelsoft/test:v1" not found
`))
})
})
Expand Down
4 changes: 4 additions & 0 deletions pkg/common/history.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ func (h History) Equals(o History) bool {
return true
}

// Add provided a new extended non-cyclic history.
// If the new entry would lead to a cycle an appropriate
// error is returned.
func (h *History) Add(kind string, nv NameVersion) error {
if h.Contains(nv) {
return errors.ErrRecusion(kind, nv, *h)
Expand All @@ -75,6 +78,7 @@ func (h *History) Add(kind string, nv NameVersion) error {
return nil
}

// Append provides a new extended history without cycle check.
func (h History) Append(nv ...NameVersion) History {
result := make(History, len(h)+len(nv))
copy(result, h)
Expand Down
2 changes: 1 addition & 1 deletion pkg/contexts/config/config/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ var _ = Describe("generic config handling", func() {

err = cfgctx.ApplyConfig(cfg, "testconfig")
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal("testconfig: {applying generic config list: config entry 0--testconfig: config type \"Dummy\" is unknown, config entry 1--testconfig: config type \"Dummy\" is unknown}"))
Expect(err.Error()).To(Equal("testconfig: applying generic config list: {config entry 0--testconfig: config type \"Dummy\" is unknown, config entry 1--testconfig: config type \"Dummy\" is unknown}"))
gen, cfgs := cfgctx.GetConfig(config.AllGenerations, nil)
Expect(gen).To(Equal(int64(3)))
Expect(len(cfgs)).To(Equal(3))
Expand Down
67 changes: 38 additions & 29 deletions pkg/contexts/ocm/signing/handle.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1"
"github.com/open-component-model/ocm/pkg/contexts/ocm/cpi"
"github.com/open-component-model/ocm/pkg/errors"
"github.com/open-component-model/ocm/pkg/utils"
)

func ToDigestSpec(v interface{}) *metav1.DigestSpec {
Expand All @@ -24,23 +25,32 @@ func ToDigestSpec(v interface{}) *metav1.DigestSpec {
return v.(*metav1.DigestSpec)
}

func Apply(printer common.Printer, state *common.WalkingState, cv ocm.ComponentVersionAccess, opts *Options) (*metav1.DigestSpec, error) {
func Apply(printer common.Printer, state *common.WalkingState, cv ocm.ComponentVersionAccess, opts *Options, closecv ...bool) (*metav1.DigestSpec, error) {
if printer == nil {
printer = common.NewPrinter(nil)
}
if state == nil {
s := common.NewWalkingState()
state = &s
}
return apply(printer, *state, cv, opts)
return apply(printer, *state, cv, opts, utils.Optional(closecv...))
}

func apply(printer common.Printer, state common.WalkingState, cv ocm.ComponentVersionAccess, opts *Options) (*metav1.DigestSpec, error) {
func apply(printer common.Printer, state common.WalkingState, cv ocm.ComponentVersionAccess, opts *Options, closecv bool) (d *metav1.DigestSpec, efferr error) {
var closer errors.ErrorFunction
if closecv {
closer = cv.Close
}
nv := common.VersionedElementKey(cv)
defer errors.PropagateErrorf(&efferr, closer, "%s", state.History.Append(nv))

if ok, err := state.Add(ocm.KIND_COMPONENTVERSION, nv); !ok {
return ToDigestSpec(state.Closure[nv]), err
}
return _apply(printer, state, nv, cv, opts)
}

func _apply(printer common.Printer, state common.WalkingState, nv common.NameVersion, cv ocm.ComponentVersionAccess, opts *Options) (*metav1.DigestSpec, error) {
cd := cv.GetDescriptor().Copy()
octx := cv.GetContext()
printer.Printf("applying to version %q...\n", nv)
Expand All @@ -51,14 +61,14 @@ func apply(printer common.Printer, state common.WalkingState, cv ocm.ComponentVe
signatureNames = append(signatureNames, s.Name)
}
if len(signatureNames) == 0 && opts.DoVerify() {
return nil, errors.Newf("no signature found in %s", state.History)
return nil, errors.Newf("no signature found")
}
}
if opts.DoVerify() && !opts.DoSign() {
for _, n := range signatureNames {
f := cd.GetSignatureIndex(n)
if f < 0 {
return nil, errors.Newf("signature %q not found in %s", n, state.History)
return nil, errors.Newf("signature %q not found", n)
}
}
}
Expand All @@ -71,19 +81,18 @@ func apply(printer common.Printer, state common.WalkingState, cv ocm.ComponentVe
if reference.Digest == nil || opts.Recursively || opts.Verify {
nested, err := opts.Resolver.LookupComponentVersion(reference.GetComponentName(), reference.GetVersion())
if err != nil {
return nil, errors.Wrapf(err, refMsg(reference, state, "failed resolving component reference"))
return nil, errors.Wrapf(err, refMsg(reference, "failed resolving component reference"))
}
closer := accessio.OnceCloser(nested)
defer closer.Close()
digestOpts, err := opts.For(reference.Digest)
if err != nil {
return nil, errors.Wrapf(err, refMsg(reference, state, "failed resolving hasher for existing digest for component reference"))
return nil, errors.Wrapf(err, refMsg(reference, "failed resolving hasher for existing digest for component reference"))
}
calculatedDigest, err = apply(printer.AddGap(" "), state, nested, digestOpts)
calculatedDigest, err = apply(printer.AddGap(" "), state, nested, digestOpts, true)
if err != nil {
return nil, errors.Wrapf(err, refMsg(reference, state, "failed applying to component reference"))
return nil, errors.Wrapf(err, refMsg(reference, "failed applying to component reference"))
}
closer.Close()
} else {
printer.Printf(" accepting digest from reference %s", reference)
calculatedDigest = reference.Digest
Expand All @@ -92,7 +101,7 @@ func apply(printer common.Printer, state common.WalkingState, cv ocm.ComponentVe
if reference.Digest == nil {
cd.References[i].Digest = calculatedDigest
} else if calculatedDigest != nil && !reflect.DeepEqual(reference.Digest, calculatedDigest) {
return nil, errors.Newf(refMsg(reference, state, "calculated reference digest (%+v) mismatches existing digest (%+v) for", calculatedDigest, reference.Digest))
return nil, errors.Newf(refMsg(reference, "calculated reference digest (%+v) mismatches existing digest (%+v) for", calculatedDigest, reference.Digest))
}
printer.Printf(" reference %d: %s:%s: digest %s\n", i, reference.ComponentName, reference.Version, calculatedDigest)
}
Expand All @@ -102,7 +111,7 @@ func apply(printer common.Printer, state common.WalkingState, cv ocm.ComponentVe
raw := &cd.Resources[i]
acc, err := res.Access()
if err != nil {
return nil, errors.Wrapf(err, resMsg(raw, "", state, "failed getting access for resource"))
return nil, errors.Wrapf(err, resMsg(raw, "", "failed getting access for resource"))
}
if _, ok := opts.SkipAccessTypes[acc.GetKind()]; ok {
// set the do not sign digest notation on skip-access-type resources
Expand All @@ -116,7 +125,7 @@ func apply(printer common.Printer, state common.WalkingState, cv ocm.ComponentVe

meth, err := acc.AccessMethod(cv)
if err != nil {
return nil, errors.Wrapf(err, resMsg(raw, acc.Describe(octx), state, "failed creating access for resource"))
return nil, errors.Wrapf(err, resMsg(raw, acc.Describe(octx), "failed creating access for resource"))
}
var req []cpi.DigesterType
if raw.Digest != nil {
Expand All @@ -129,20 +138,20 @@ func apply(printer common.Printer, state common.WalkingState, cv ocm.ComponentVe
}
digest, err := blobdigesters.DetermineDigests(res.Meta().GetType(), opts.Hasher, opts.Registry, meth, req...)
if err != nil {
return nil, errors.Wrapf(err, resMsg(raw, acc.Describe(octx), state, "failed determining digest for resource"))
return nil, errors.Wrapf(err, resMsg(raw, acc.Describe(octx), "failed determining digest for resource"))
}
if len(digest) == 0 {
return nil, errors.Newf(resMsg(raw, acc.Describe(octx), state, "no digester accepts resource"))
return nil, errors.Newf(resMsg(raw, acc.Describe(octx), "no digester accepts resource"))
}
if raw.Digest != nil && !reflect.DeepEqual(*raw.Digest, digest[0]) {
return nil, errors.Newf(resMsg(raw, acc.Describe(octx), state, "calculated resource digest (%+v) mismatches existing digest (%+v) for", digest, raw.Digest))
return nil, errors.Newf(resMsg(raw, acc.Describe(octx), "calculated resource digest (%+v) mismatches existing digest (%+v) for", digest, raw.Digest))
}
cd.Resources[i].Digest = &digest[0]
printer.Printf(" resource %d: %s: digest %s\n", i, res.Meta().GetIdentity(cv.GetDescriptor().Resources), &digest[0])
}
digest, err := compdesc.Hash(cd, opts.NormalizationAlgo, opts.Hasher.Create())
if err != nil {
return nil, errors.Wrapf(err, "failed hashing component descriptor %s ", state.History)
return nil, errors.Wrapf(err, "failed hashing component descriptor")
}
spec := &metav1.DigestSpec{
HashAlgorithm: opts.Hasher.Algorithm(),
Expand All @@ -160,7 +169,7 @@ func apply(printer common.Printer, state common.WalkingState, cv ocm.ComponentVe
pub := opts.PublicKey(n)
if pub == nil {
if opts.SignatureConfigured(n) {
return nil, errors.ErrNotFound(compdesc.KIND_PUBLIC_KEY, n, state.History.String())
return nil, errors.ErrNotFound(compdesc.KIND_PUBLIC_KEY, n)
}
printer.Printf("Warning: no public key for signature %q in %s\n", n, state.History)
continue
Expand All @@ -169,24 +178,24 @@ func apply(printer common.Printer, state common.WalkingState, cv ocm.ComponentVe
verifier := opts.Registry.GetVerifier(sig.Signature.Algorithm)
if verifier == nil {
if opts.SignatureConfigured(n) {
return nil, errors.ErrUnknown(compdesc.KIND_VERIFY_ALGORITHM, n, state.History.String())
return nil, errors.ErrUnknown(compdesc.KIND_VERIFY_ALGORITHM, n)
}
printer.Printf("Warning: no verifier (%s) found for signature %q in %s\n", sig.Signature.Algorithm, n, state.History)
continue
}
hasher := opts.Registry.GetHasher(sig.Digest.HashAlgorithm)
if hasher == nil {
return nil, errors.ErrUnknown(compdesc.KIND_HASH_ALGORITHM, sig.Digest.HashAlgorithm, state.History.String())
return nil, errors.ErrUnknown(compdesc.KIND_HASH_ALGORITHM, sig.Digest.HashAlgorithm)
}
err = verifier.Verify(sig.Digest.Value, hasher.Crypto(), sig.ConvertToSigning(), pub)
if err != nil {
return nil, errors.ErrInvalidWrap(err, compdesc.KIND_SIGNATURE, sig.Signature.Algorithm, state.History.String())
return nil, errors.ErrInvalidWrap(err, compdesc.KIND_SIGNATURE, sig.Signature.Algorithm)
}
found = append(found, n)
}
if len(found) == 0 {
if !opts.DoSign() {
return nil, errors.Newf("no verifiable signature found in %s", state.History)
return nil, errors.Newf("no verifiable signature found")
}
}
}
Expand All @@ -195,11 +204,11 @@ func apply(printer common.Printer, state common.WalkingState, cv ocm.ComponentVe
if opts.DoSign() && (!opts.DoVerify() || found == -1) {
sig, err := opts.Signer.Sign(digest, opts.Hasher.Crypto(), opts.Issuer, opts.PrivateKey())
if err != nil {
return nil, errors.Wrapf(err, "failed signing component descriptor %s ", state.History)
return nil, errors.Wrapf(err, "failed signing component descriptor")
}
if sig.Issuer != "" {
if opts.Issuer != "" && opts.Issuer != sig.Issuer {
return nil, errors.Newf("signature issuer %q does not match intended issuer %q in %s", sig.Issuer, opts.Issuer, state.History)
return nil, errors.Newf("signature issuer %q does not match intended issuer %q", sig.Issuer, opts.Issuer)
}
} else {
sig.Issuer = opts.Issuer
Expand Down Expand Up @@ -236,13 +245,13 @@ func apply(printer common.Printer, state common.WalkingState, cv ocm.ComponentVe
return spec, nil
}

func refMsg(ref compdesc.ComponentReference, state common.WalkingState, msg string, args ...interface{}) string {
return fmt.Sprintf("%s %s in %s", fmt.Sprintf(msg, args...), ref, state.History)
func refMsg(ref compdesc.ComponentReference, msg string, args ...interface{}) string {
return fmt.Sprintf("%s %s", fmt.Sprintf(msg, args...), ref)
}

func resMsg(ref *compdesc.Resource, acc string, state common.WalkingState, msg string, args ...interface{}) string {
func resMsg(ref *compdesc.Resource, acc string, msg string, args ...interface{}) string {
if acc != "" {
return fmt.Sprintf("%s %s:%s (%s) in %s", fmt.Sprintf(msg, args...), ref.Name, ref.Version, acc, state.History)
return fmt.Sprintf("%s %s:%s (%s)", fmt.Sprintf(msg, args...), ref.Name, ref.Version, acc)
}
return fmt.Sprintf("%s %s:%s in %s", fmt.Sprintf(msg, args...), ref.Name, ref.Version, state.History)
return fmt.Sprintf("%s %s:%s", fmt.Sprintf(msg, args...), ref.Name, ref.Version)
}
4 changes: 2 additions & 2 deletions pkg/contexts/ocm/signing/signing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ var _ = Describe("access method", func() {

_, err = Apply(nil, nil, cv, opts)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal("no signature found in github.com/mandelsoft/test:v1"))
Expect(err.Error()).To(StringEqualWithContext("github.com/mandelsoft/test:v1: no signature found"))
})
})

Expand Down Expand Up @@ -303,7 +303,7 @@ var _ = Describe("access method", func() {

_, err = Apply(nil, nil, cv, opts)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal("failed resolving component reference ref[github.com/mandelsoft/test:v1] in github.com/mandelsoft/ref:v1: component version \"github.com/mandelsoft/test:v1\" not found: oci artefact \"v1\" not found in component-descriptors/github.com/mandelsoft/test"))
Expect(err.Error()).To(StringEqualWithContext("github.com/mandelsoft/ref:v1: failed resolving component reference ref[github.com/mandelsoft/test:v1]: component version \"github.com/mandelsoft/test:v1\" not found: oci artefact \"v1\" not found in component-descriptors/github.com/mandelsoft/test"))
})
})
})
10 changes: 5 additions & 5 deletions pkg/errors/alreadyexists.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,26 @@

package errors

type errAlreadyExists struct {
type AlreadyExistsError struct {
errinfo
}

var formatAlreadyExists = NewDefaultFormatter("", "already exists", "in")

func ErrAlreadyExists(spec ...string) error {
return &errAlreadyExists{newErrInfo(formatAlreadyExists, spec...)}
return &AlreadyExistsError{newErrInfo(formatAlreadyExists, spec...)}
}

func ErrAlreadyExistsWrap(err error, spec ...string) error {
return &errAlreadyExists{wrapErrInfo(err, formatAlreadyExists, spec...)}
return &AlreadyExistsError{wrapErrInfo(err, formatAlreadyExists, spec...)}
}

func IsErrAlreadyExists(err error) bool {
return IsA(err, &errAlreadyExists{})
return IsA(err, &AlreadyExistsError{})
}

func IsErrAlreadyExistsKind(err error, kind string) bool {
var uerr *errNotFound
var uerr *NotFoundError
if err == nil || !As(err, &uerr) {
return false
}
Expand Down
8 changes: 4 additions & 4 deletions pkg/errors/closed.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@

package errors

type errClosed struct {
type ClosedError struct {
errinfo
}

var formatClosed = NewDefaultFormatter("is", "closed", "for")

func ErrClosed(spec ...string) error {
return &errClosed{newErrInfo(formatClosed, spec...)}
return &ClosedError{newErrInfo(formatClosed, spec...)}
}

func IsErrClosed(err error) bool {
return IsA(err, &errClosed{})
return IsA(err, &ClosedError{})
}

func IsErrClosedKind(err error, kind string) bool {
var uerr *errClosed
var uerr *ClosedError
if err == nil || !As(err, &uerr) {
return false
}
Expand Down
Loading

0 comments on commit 852bdfc

Please sign in to comment.