Skip to content
This repository was archived by the owner on Nov 27, 2023. It is now read-only.

Commit 6486f19

Browse files
committed
Add multierror
docker/api/multierror wraps go-multierror from hashicorp with our default error formating
1 parent 2d14bfe commit 6486f19

File tree

6 files changed

+153
-11
lines changed

6 files changed

+153
-11
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ COPY --from=make-cli /api/bin/* .
4545
FROM scratch AS cross
4646
COPY --from=make-cross /api/bin/* .
4747

48-
FROM make-protos as test
48+
FROM fs as test
4949
RUN make -f builder.Makefile test
5050

5151
FROM fs AS lint

cli/cmd/context.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ import (
3333
"os"
3434
"text/tabwriter"
3535

36-
"github.com/hashicorp/go-multierror"
3736
"github.com/spf13/cobra"
3837

3938
"github.com/docker/api/context/store"
39+
"github.com/docker/api/multierror"
4040
)
4141

4242
// ContextCommand manages contexts

cli/cmd/rm.go

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ package cmd
22

33
import (
44
"fmt"
5-
"strings"
65

76
"github.com/pkg/errors"
87
"github.com/spf13/cobra"
98

109
"github.com/docker/api/client"
10+
"github.com/docker/api/multierror"
1111
)
1212

1313
type rmOpts struct {
@@ -23,26 +23,22 @@ func RmCommand() *cobra.Command {
2323
Short: "Remove containers",
2424
Args: cobra.MinimumNArgs(1),
2525
RunE: func(cmd *cobra.Command, args []string) error {
26-
var errs []string
2726
c, err := client.New(cmd.Context())
2827
if err != nil {
2928
return errors.Wrap(err, "cannot connect to backend")
3029
}
3130

31+
var errs *multierror.Error
3232
for _, id := range args {
3333
err := c.ContainerService().Delete(cmd.Context(), id, opts.force)
3434
if err != nil {
35-
errs = append(errs, err.Error()+" "+id)
35+
errs = multierror.Append(errs, err)
3636
continue
3737
}
3838
fmt.Println(id)
3939
}
4040

41-
if len(errs) > 0 {
42-
return errors.New(strings.Join(errs, "\n"))
43-
}
44-
45-
return nil
41+
return errs.ErrorOrNil()
4642
},
4743
}
4844

moby/backend.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ import (
77
"github.com/docker/docker/api/types"
88
"github.com/docker/docker/api/types/container"
99
"github.com/docker/docker/client"
10+
"github.com/pkg/errors"
1011

1112
"github.com/docker/api/backend"
1213
"github.com/docker/api/compose"
1314
"github.com/docker/api/containers"
15+
"github.com/docker/api/errdefs"
1416
)
1517

1618
type mobyService struct {
@@ -127,7 +129,11 @@ func (ms *mobyService) Logs(ctx context.Context, containerName string, request c
127129
}
128130

129131
func (ms *mobyService) Delete(ctx context.Context, containerID string, force bool) error {
130-
return ms.apiClient.ContainerRemove(ctx, containerID, types.ContainerRemoveOptions{
132+
err := ms.apiClient.ContainerRemove(ctx, containerID, types.ContainerRemoveOptions{
131133
Force: force,
132134
})
135+
if client.IsErrNotFound(err) {
136+
return errors.Wrapf(errdefs.ErrNotFound, "container %q", containerID)
137+
}
138+
return err
133139
}

multierror/multierror.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package multierror
2+
3+
import (
4+
"strings"
5+
6+
"github.com/hashicorp/go-multierror"
7+
)
8+
9+
// Error wraps a multierror.Error and defines a default
10+
// formatting function that fits cli needs
11+
type Error struct {
12+
err *multierror.Error
13+
}
14+
15+
func (e *Error) Error() string {
16+
if e == nil || e.err == nil {
17+
return ""
18+
}
19+
e.err.ErrorFormat = listErrorFunc
20+
return e.err.Error()
21+
}
22+
23+
// WrappedErrors returns the list of errors that this Error is wrapping.
24+
// It is an implementation of the errwrap.Wrapper interface so that
25+
// multierror.Error can be used with that library.
26+
//
27+
// This method is not safe to be called concurrently and is no different
28+
// than accessing the Errors field directly. It is implemented only to
29+
// satisfy the errwrap.Wrapper interface.
30+
func (e *Error) WrappedErrors() []error {
31+
return e.err.WrappedErrors()
32+
}
33+
34+
// Unwrap returns an error from Error (or nil if there are no errors)
35+
func (e *Error) Unwrap() error {
36+
if e == nil || e.err == nil {
37+
return nil
38+
}
39+
return e.err.Unwrap()
40+
}
41+
42+
// ErrorOrNil returns an error interface if this Error represents
43+
// a list of errors, or returns nil if the list of errors is empty. This
44+
// function is useful at the end of accumulation to make sure that the value
45+
// returned represents the existence of errors.
46+
func (e *Error) ErrorOrNil() error {
47+
if e == nil || e.err == nil {
48+
return nil
49+
}
50+
if len(e.err.Errors) == 0 {
51+
return nil
52+
}
53+
54+
return e
55+
}
56+
57+
// Append adds an error to a multierror, if err is
58+
// not a multierror it will be converted to one
59+
func Append(err error, errs ...error) *Error {
60+
switch err := err.(type) {
61+
case *Error:
62+
if err == nil {
63+
err = new(Error)
64+
}
65+
for _, e := range errs {
66+
err.err = multierror.Append(err.err, e)
67+
}
68+
return err
69+
default:
70+
newErrs := make([]error, 0, len(errs)+1)
71+
if err != nil {
72+
newErrs = append(newErrs, err)
73+
}
74+
newErrs = append(newErrs, errs...)
75+
76+
return Append(&Error{}, newErrs...)
77+
}
78+
}
79+
80+
func listErrorFunc(errs []error) string {
81+
if len(errs) == 1 {
82+
return errs[0].Error()
83+
}
84+
85+
messages := make([]string, len(errs))
86+
87+
for i, err := range errs {
88+
messages[i] = "Error: " + err.Error()
89+
}
90+
91+
return strings.Join(messages, "\n")
92+
}

multierror/multierror_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package multierror
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestSingleError(t *testing.T) {
11+
var err *Error
12+
err = Append(err, errors.New("error"))
13+
assert.Equal(t, 1, len(err.WrappedErrors()))
14+
}
15+
16+
func TestGoError(t *testing.T) {
17+
var err error
18+
result := Append(err, errors.New("error"))
19+
assert.Equal(t, 1, len(result.WrappedErrors()))
20+
}
21+
22+
func TestMultiError(t *testing.T) {
23+
var err *Error
24+
err = Append(err,
25+
errors.New("first"),
26+
errors.New("second"),
27+
)
28+
assert.Equal(t, 2, len(err.WrappedErrors()))
29+
assert.Equal(t, "Error: first\nError: second", err.Error())
30+
}
31+
32+
func TestUnwrap(t *testing.T) {
33+
var err *Error
34+
assert.Equal(t, nil, errors.Unwrap(err))
35+
36+
err = Append(err, errors.New("first"))
37+
e := errors.Unwrap(err)
38+
assert.Equal(t, "first", e.Error())
39+
}
40+
41+
func TestErrorOrNil(t *testing.T) {
42+
var err *Error
43+
assert.Equal(t, nil, err.ErrorOrNil())
44+
45+
err = Append(err, errors.New("error"))
46+
e := err.ErrorOrNil()
47+
assert.Equal(t, "error", e.Error())
48+
}

0 commit comments

Comments
 (0)