diff --git a/go.mod b/go.mod index 472970a..02386ad 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.17 require ( github.com/fsnotify/fsnotify v1.5.1 - github.com/hashicorp/go-multierror v1.1.1 github.com/opencontainers/runc v1.1.2 github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 github.com/opencontainers/runtime-tools v0.0.0-20190417131837-cd1349b7c47e @@ -21,6 +20,7 @@ require ( github.com/blang/semver v3.5.1+incompatible // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/opencontainers/selinux v1.10.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/internal/multierror/multierror.go b/internal/multierror/multierror.go new file mode 100644 index 0000000..d92f7ca --- /dev/null +++ b/internal/multierror/multierror.go @@ -0,0 +1,84 @@ +/* + Copyright © 2022 The CDI Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package multierror + +import ( + "strings" +) + +// New combines several errors into a single error. Parameters that are nil are +// ignored. If no errors are passed in or all parameters are nil, then the +// result is also nil. +func New(errors ...error) error { + // Filter out nil entries. + numErrors := 0 + for _, err := range errors { + if err != nil { + errors[numErrors] = err + numErrors++ + } + } + if numErrors == 0 { + return nil + } + return multiError(errors[0:numErrors]) +} + +// multiError is the underlying implementation used by New. It can be instantiated +// directly to get access to the Append method or to cast a plain slice of +// errors to get a single error. +// +// Beware that a null multiError is not the same as a nil error. +type multiError []error + +// multiError returns all individual error strings concatenated with "\n" +func (e multiError) Error() string { + var builder strings.Builder + for i, err := range e { + if i > 0 { + _, _ = builder.WriteString("\n") + } + _, _ = builder.WriteString(err.Error()) + } + return builder.String() +} + +// Append returns a new multi error all errors concatenated. Errors that are +// multi errors get flattened, nil is ignored. +func Append(err error, errors ...error) error { + var result multiError + if m, ok := err.(multiError); ok { + result = m + } else if err != nil { + result = append(result, err) + } + + for _, e := range errors { + if e == nil { + continue + } + if m, ok := e.(multiError); ok { + result = append(result, m...) + } else { + result = append(result, e) + } + } + if len(result) == 0 { + return nil + } + return result +} diff --git a/internal/multierror/multierror.test b/internal/multierror/multierror.test new file mode 100755 index 0000000..5cab42c Binary files /dev/null and b/internal/multierror/multierror.test differ diff --git a/internal/multierror/multierror_test.go b/internal/multierror/multierror_test.go new file mode 100644 index 0000000..210cf57 --- /dev/null +++ b/internal/multierror/multierror_test.go @@ -0,0 +1,38 @@ +/* + Copyright © 2022 The CDI Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package multierror + +import ( + "testing" + "errors" + + "github.com/stretchr/testify/assert" +) + +func TestNew(t *testing.T) { + assert.Equal(t, nil, New()) + assert.Equal(t, nil, New(nil)) + assert.Equal(t, "hello\nworld", New(errors.New("hello"), errors.New("world")).Error()) +} + +func TestAppend(t *testing.T) { + assert.Equal(t, nil, Append(nil)) + assert.Equal(t, nil, Append(nil, nil)) + assert.Equal(t, multiError{errors.New("hello"), errors.New("world"), errors.New("x"), errors.New("y")}, + Append(New(errors.New("hello"), errors.New("world")), New(errors.New("x"), errors.New("y")))) + +} diff --git a/pkg/cdi/cache.go b/pkg/cdi/cache.go index 60e4d83..dee8548 100644 --- a/pkg/cdi/cache.go +++ b/pkg/cdi/cache.go @@ -26,9 +26,9 @@ import ( stderr "errors" + "github.com/container-orchestrated-devices/container-device-interface/internal/multierror" cdi "github.com/container-orchestrated-devices/container-device-interface/specs-go" "github.com/fsnotify/fsnotify" - "github.com/hashicorp/go-multierror" oci "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" ) @@ -127,8 +127,8 @@ func (c *Cache) Refresh() error { // collect and return cached errors, much like refresh() does it var result error - for _, err := range c.errors { - result = multierror.Append(result, err...) + for _, errors := range c.errors { + result = multierror.Append(result, errors...) } return result } @@ -198,11 +198,7 @@ func (c *Cache) refresh() error { c.devices = devices c.errors = specErrors - if len(result) > 0 { - return multierror.Append(nil, result...) - } - - return nil + return multierror.New(result...) } // RefreshIfRequired triggers a refresh if necessary. diff --git a/schema/schema.go b/schema/schema.go index 0b44329..5831af8 100644 --- a/schema/schema.go +++ b/schema/schema.go @@ -25,10 +25,11 @@ import ( "net/http" "path/filepath" "strings" + "fmt" "sigs.k8s.io/yaml" - "github.com/hashicorp/go-multierror" + "github.com/container-orchestrated-devices/container-device-interface/internal/multierror" "github.com/pkg/errors" schema "github.com/xeipuuv/gojsonschema" ) @@ -227,7 +228,7 @@ func (s *Schema) validate(doc schema.JSONLoader) error { return &Error{Result: docErr} } -// Error returns the given Result's error as a multierror(.Error()). +// Error returns the given Result's errors as a single error string. func (e *Error) Error() string { if e == nil || e.Result == nil || e.Result.Valid() { return "" @@ -235,9 +236,9 @@ func (e *Error) Error() string { var multi error for _, err := range e.Result.Errors() { - multi = multierror.Append(multi, errors.Errorf("%v", err)) + multi = multierror.Append(multi, fmt.Errorf("%v", err)) } - return strings.TrimRight(multi.Error(), "\n") + return multi.Error() } var (