Skip to content

Commit 8c4be61

Browse files
authored
Merge pull request containerd#68 from coryb/wrap-errors
preserve exit status with wrapped errors
2 parents 7c5957f + 78b8bdd commit 8c4be61

File tree

4 files changed

+108
-9
lines changed

4 files changed

+108
-9
lines changed

.travis.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
language: go
22
go:
3-
- 1.12.x
43
- 1.13.x
4+
- 1.14.x
5+
- 1.15.x
56

67
install:
78
- go get -t ./...

console_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ func TestTempConsole(t *testing.T) {
3333

3434
func TestTempConsoleWithXdgRuntimeDir(t *testing.T) {
3535
tmpDir := "/tmp/foo"
36+
// prevent interferring with other tests
37+
defer os.Setenv("XDG_RUNTIME_DIR", os.Getenv("XDG_RUNTIME_DIR"))
3638
if err := os.Setenv("XDG_RUNTIME_DIR", tmpDir); err != nil {
3739
t.Fatal(err)
3840
}

runc.go

+16-8
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ func (r *Runc) Create(context context.Context, id, bundle string, opts *CreateOp
160160
}
161161
status, err := Monitor.Wait(cmd, ec)
162162
if err == nil && status != 0 {
163-
err = fmt.Errorf("%s did not terminate successfully", cmd.Args[0])
163+
err = fmt.Errorf("%s did not terminate successfully: %w", cmd.Args[0], &ExitError{status})
164164
}
165165
return err
166166
}
@@ -194,7 +194,7 @@ func (o *ExecOpts) args() (out []string, err error) {
194194
return out, nil
195195
}
196196

197-
// Exec executres and additional process inside the container based on a full
197+
// Exec executes an additional process inside the container based on a full
198198
// OCI Process specification
199199
func (r *Runc) Exec(context context.Context, id string, spec specs.Process, opts *ExecOpts) error {
200200
f, err := ioutil.TempFile(os.Getenv("XDG_RUNTIME_DIR"), "runc-process")
@@ -223,7 +223,7 @@ func (r *Runc) Exec(context context.Context, id string, spec specs.Process, opts
223223
data, err := cmdOutput(cmd, true)
224224
defer putBuf(data)
225225
if err != nil {
226-
return fmt.Errorf("%s: %s", err, data.String())
226+
return fmt.Errorf("%w: %s", err, data.String())
227227
}
228228
return nil
229229
}
@@ -240,7 +240,7 @@ func (r *Runc) Exec(context context.Context, id string, spec specs.Process, opts
240240
}
241241
status, err := Monitor.Wait(cmd, ec)
242242
if err == nil && status != 0 {
243-
err = fmt.Errorf("%s did not terminate successfully", cmd.Args[0])
243+
err = fmt.Errorf("%s did not terminate successfully: %w", cmd.Args[0], &ExitError{status})
244244
}
245245
return err
246246
}
@@ -266,7 +266,7 @@ func (r *Runc) Run(context context.Context, id, bundle string, opts *CreateOpts)
266266
}
267267
status, err := Monitor.Wait(cmd, ec)
268268
if err == nil && status != 0 {
269-
err = fmt.Errorf("%s did not terminate successfully", cmd.Args[0])
269+
err = fmt.Errorf("%s did not terminate successfully: %w", cmd.Args[0], &ExitError{status})
270270
}
271271
return status, err
272272
}
@@ -584,7 +584,7 @@ func (r *Runc) Restore(context context.Context, id, bundle string, opts *Restore
584584
}
585585
status, err := Monitor.Wait(cmd, ec)
586586
if err == nil && status != 0 {
587-
err = fmt.Errorf("%s did not terminate successfully", cmd.Args[0])
587+
err = fmt.Errorf("%s did not terminate successfully: %w", cmd.Args[0], &ExitError{status})
588588
}
589589
return status, err
590590
}
@@ -681,7 +681,7 @@ func (r *Runc) runOrError(cmd *exec.Cmd) error {
681681
}
682682
status, err := Monitor.Wait(cmd, ec)
683683
if err == nil && status != 0 {
684-
err = fmt.Errorf("%s did not terminate successfully", cmd.Args[0])
684+
err = fmt.Errorf("%s did not terminate successfully: %w", cmd.Args[0], &ExitError{status})
685685
}
686686
return err
687687
}
@@ -709,8 +709,16 @@ func cmdOutput(cmd *exec.Cmd, combined bool) (*bytes.Buffer, error) {
709709

710710
status, err := Monitor.Wait(cmd, ec)
711711
if err == nil && status != 0 {
712-
err = fmt.Errorf("%s did not terminate successfully", cmd.Args[0])
712+
err = fmt.Errorf("%s did not terminate successfully: %w", cmd.Args[0], &ExitError{status})
713713
}
714714

715715
return b, err
716716
}
717+
718+
type ExitError struct {
719+
Status int
720+
}
721+
722+
func (e *ExitError) Error() string {
723+
return fmt.Sprintf("exit status %d", e.Status)
724+
}

runc_test.go

+88
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@ package runc
1818

1919
import (
2020
"context"
21+
"errors"
2122
"sync"
2223
"testing"
24+
25+
specs "github.com/opencontainers/runtime-spec/specs-go"
2326
)
2427

2528
func TestParseVersion(t *testing.T) {
@@ -104,3 +107,88 @@ func TestParallelCmds(t *testing.T) {
104107
}
105108
wg.Wait()
106109
}
110+
111+
func TestRuncRunExit(t *testing.T) {
112+
ctx := context.Background()
113+
okRunc := &Runc{
114+
Command: "/bin/true",
115+
}
116+
117+
status, err := okRunc.Run(ctx, "fake-id", "fake-bundle", &CreateOpts{})
118+
if err != nil {
119+
t.Fatalf("Unexpected error from Run: %s", err)
120+
}
121+
if status != 0 {
122+
t.Fatalf("Expected exit status 0 from Run, got %d", status)
123+
}
124+
125+
failRunc := &Runc{
126+
Command: "/bin/false",
127+
}
128+
129+
status, err = failRunc.Run(ctx, "fake-id", "fake-bundle", &CreateOpts{})
130+
if err == nil {
131+
t.Fatal("Expected error from Run, but got nil")
132+
}
133+
if status != 1 {
134+
t.Fatalf("Expected exit status 1 from Run, got %d", status)
135+
}
136+
extractedStatus := extractStatus(err)
137+
if extractedStatus != status {
138+
t.Fatalf("Expected extracted exit status %d from Run, got %d", status, extractedStatus)
139+
}
140+
}
141+
142+
func TestRuncExecExit(t *testing.T) {
143+
ctx := context.Background()
144+
okRunc := &Runc{
145+
Command: "/bin/true",
146+
}
147+
err := okRunc.Exec(ctx, "fake-id", specs.Process{}, &ExecOpts{})
148+
if err != nil {
149+
t.Fatalf("Unexpected error from Exec: %s", err)
150+
}
151+
status := extractStatus(err)
152+
if status != 0 {
153+
t.Fatalf("Expected exit status 0 from Exec, got %d", status)
154+
}
155+
156+
failRunc := &Runc{
157+
Command: "/bin/false",
158+
}
159+
160+
err = failRunc.Exec(ctx, "fake-id", specs.Process{}, &ExecOpts{})
161+
if err == nil {
162+
t.Fatal("Expected error from Exec, but got nil")
163+
}
164+
status = extractStatus(err)
165+
if status != 1 {
166+
t.Fatalf("Expected exit status 1 from Exec, got %d", status)
167+
}
168+
169+
io, err := NewSTDIO()
170+
if err != nil {
171+
t.Fatalf("Unexpected error from NewSTDIO: %s", err)
172+
}
173+
err = failRunc.Exec(ctx, "fake-id", specs.Process{}, &ExecOpts{
174+
IO: io,
175+
})
176+
if err == nil {
177+
t.Fatal("Expected error from Exec, but got nil")
178+
}
179+
status = extractStatus(err)
180+
if status != 1 {
181+
t.Fatalf("Expected exit status 1 from Exec, got %d", status)
182+
}
183+
}
184+
185+
func extractStatus(err error) int {
186+
if err == nil {
187+
return 0
188+
}
189+
var exitError *ExitError
190+
if errors.As(err, &exitError) {
191+
return exitError.Status
192+
}
193+
return -1
194+
}

0 commit comments

Comments
 (0)