Browse code

vendor: update containerd to acdcf13d5eaf0dfe0eaeabe7194a82535549bc2b

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>

Tonis Tiigi authored on 2019/10/22 14:57:08
Showing 31 changed files
... ...
@@ -80,7 +80,7 @@ google.golang.org/grpc                              6eaf6f47437a6b4e2153a190160e
80 80
 # the containerd project first, and update both after that is merged.
81 81
 # This commit does not need to match RUNC_COMMIT as it is used for helper
82 82
 # packages but should be newer or equal.
83
-github.com/opencontainers/runc                      3e425f80a8c931f88e6d94a8c831b9d5aa481657 # v1.0.0-rc8-92-g84373aaa
83
+github.com/opencontainers/runc                      d736ef14f0288d6993a1845745d6756cfc9ddd5a # v1.0.0-rc9
84 84
 github.com/opencontainers/runtime-spec              29686dbc5559d93fb1ef402eeda3e35c38d75af4 # v1.0.1-59-g29686db
85 85
 github.com/opencontainers/image-spec                d60099175f88c47cd379c4738d158884749ed235 # v1.0.1
86 86
 github.com/seccomp/libseccomp-golang                689e3c1541a84461afc49c1c87352a6cedf72e9c # v0.9.1
... ...
@@ -117,7 +117,7 @@ github.com/googleapis/gax-go                        317e0006254c44a0ac427cc52a0e
117 117
 google.golang.org/genproto                          694d95ba50e67b2e363f3483057db5d4910c18f9
118 118
 
119 119
 # containerd
120
-github.com/containerd/containerd                    36cf5b690dcc00ff0f34ff7799209050c3d0c59a # v1.3.0
120
+github.com/containerd/containerd                    acdcf13d5eaf0dfe0eaeabe7194a82535549bc2b
121 121
 github.com/containerd/fifo                          bda0ff6ed73c67bfb5e62bc9c697f146b7fd7f13
122 122
 github.com/containerd/continuity                    f2a389ac0a02ce21c09edd7344677a601970f41c
123 123
 github.com/containerd/cgroups                       5fbad35c2a7e855762d3c60f2e474ffcad0d470a
... ...
@@ -210,6 +210,34 @@ See [PLUGINS.md](PLUGINS.md) for how to create plugins
210 210
 Please see [RELEASES.md](RELEASES.md) for details on versioning and stability
211 211
 of containerd components.
212 212
 
213
+Downloadable 64-bit Intel/AMD binaries of all official releases are available on
214
+our [releases page](https://github.com/containerd/containerd/releases), as well as
215
+auto-published to the [cri-containerd-release storage bucket](https://console.cloud.google.com/storage/browser/cri-containerd-release?pli=1).
216
+
217
+For other architectures and distribution support, you will find that many
218
+Linux distributions package their own containerd and provide it across several
219
+architectures, such as [Canonical's Ubuntu packaging](https://launchpad.net/ubuntu/bionic/+package/containerd).
220
+
221
+#### Enabling command auto-completion
222
+
223
+Starting with containerd 1.4, the urfave client feature for auto-creation of bash
224
+autocompletion data is enabled. To use the autocomplete feature in your shell, source
225
+the autocomplete/bash_autocomplete file in your .bashrc file while setting the `PROG`
226
+variable to `ctr`:
227
+
228
+```
229
+$ PROG=ctr source vendor/github.com/urfave/cli/autocomplete/bash_autocomplete
230
+```
231
+
232
+#### Distribution of `ctr` autocomplete for bash
233
+
234
+Copy `vendor/github.com/urfave/cli/autocomplete/bash_autocomplete` into
235
+`/etc/bash_completion.d/` and rename it to `ctr`.
236
+
237
+Provide documentation to users to `source` this file into their shell if
238
+you don't place the autocomplete file in a location where it is automatically
239
+loaded for user's bash shell environment.
240
+
213 241
 ### Communication
214 242
 
215 243
 For async communication and long running discussions please use issues and pull requests on the github repo.
... ...
@@ -263,7 +263,7 @@ func getRecords(ctx context.Context, store content.Provider, desc ocispec.Descri
263 263
 		images.HandlerFunc(exportHandler),
264 264
 	)
265 265
 
266
-	// Walk sequentially since the number of fetchs is likely one and doing in
266
+	// Walk sequentially since the number of fetches is likely one and doing in
267 267
 	// parallel requires locking the export handler
268 268
 	if err := images.Walk(ctx, handlers, desc); err != nil {
269 269
 		return nil, err
... ...
@@ -20,7 +20,7 @@ import (
20 20
 	"strings"
21 21
 
22 22
 	"github.com/containerd/containerd/reference"
23
-	distref "github.com/docker/distribution/reference"
23
+	distref "github.com/containerd/containerd/reference/docker"
24 24
 	"github.com/opencontainers/go-digest"
25 25
 	"github.com/pkg/errors"
26 26
 )
... ...
@@ -24,7 +24,7 @@ import (
24 24
 )
25 25
 
26 26
 // WithLease attaches a lease on the context
27
-func (c *Client) WithLease(ctx context.Context) (context.Context, func(context.Context) error, error) {
27
+func (c *Client) WithLease(ctx context.Context, opts ...leases.Opt) (context.Context, func(context.Context) error, error) {
28 28
 	_, ok := leases.FromContext(ctx)
29 29
 	if ok {
30 30
 		return ctx, func(context.Context) error {
... ...
@@ -34,7 +34,15 @@ func (c *Client) WithLease(ctx context.Context) (context.Context, func(context.C
34 34
 
35 35
 	ls := c.LeasesService()
36 36
 
37
-	l, err := ls.Create(ctx, leases.WithRandomID(), leases.WithExpiration(24*time.Hour))
37
+	if len(opts) == 0 {
38
+		// Use default lease configuration if no options provided
39
+		opts = []leases.Opt{
40
+			leases.WithRandomID(),
41
+			leases.WithExpiration(24 * time.Hour),
42
+		}
43
+	}
44
+
45
+	l, err := ls.Create(ctx, opts...)
38 46
 	if err != nil {
39 47
 		return nil, nil, err
40 48
 	}
... ...
@@ -1006,6 +1006,21 @@ func WithParentCgroupDevices(_ context.Context, _ Client, _ *containers.Containe
1006 1006
 	return nil
1007 1007
 }
1008 1008
 
1009
+// WithAllDevicesAllowed permits READ WRITE MKNOD on all devices nodes for the container
1010
+func WithAllDevicesAllowed(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
1011
+	setLinux(s)
1012
+	if s.Linux.Resources == nil {
1013
+		s.Linux.Resources = &specs.LinuxResources{}
1014
+	}
1015
+	s.Linux.Resources.Devices = []specs.LinuxDeviceCgroup{
1016
+		{
1017
+			Allow:  true,
1018
+			Access: rwm,
1019
+		},
1020
+	}
1021
+	return nil
1022
+}
1023
+
1009 1024
 // WithDefaultUnixDevices adds the default devices for unix such as /dev/null, /dev/random to
1010 1025
 // the container's resource cgroup spec
1011 1026
 func WithDefaultUnixDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
... ...
@@ -1100,7 +1115,6 @@ func WithDefaultUnixDevices(_ context.Context, _ Client, _ *containers.Container
1100 1100
 }
1101 1101
 
1102 1102
 // WithPrivileged sets up options for a privileged container
1103
-// TODO(justincormack) device handling
1104 1103
 var WithPrivileged = Compose(
1105 1104
 	WithAllCapabilities,
1106 1105
 	WithMaskedPaths(nil),
... ...
@@ -19,12 +19,69 @@
19 19
 package oci
20 20
 
21 21
 import (
22
+	"context"
23
+	"io/ioutil"
22 24
 	"os"
25
+	"path/filepath"
23 26
 
27
+	"github.com/containerd/containerd/containers"
24 28
 	specs "github.com/opencontainers/runtime-spec/specs-go"
25 29
 	"golang.org/x/sys/unix"
26 30
 )
27 31
 
32
+// WithHostDevices adds all the hosts device nodes to the container's spec
33
+func WithHostDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
34
+	setLinux(s)
35
+
36
+	devs, err := getDevices("/dev")
37
+	if err != nil {
38
+		return err
39
+	}
40
+	s.Linux.Devices = append(s.Linux.Devices, devs...)
41
+	return nil
42
+}
43
+
44
+func getDevices(path string) ([]specs.LinuxDevice, error) {
45
+	files, err := ioutil.ReadDir(path)
46
+	if err != nil {
47
+		return nil, err
48
+	}
49
+	var out []specs.LinuxDevice
50
+	for _, f := range files {
51
+		switch {
52
+		case f.IsDir():
53
+			switch f.Name() {
54
+			// ".lxc" & ".lxd-mounts" added to address https://github.com/lxc/lxd/issues/2825
55
+			// ".udev" added to address https://github.com/opencontainers/runc/issues/2093
56
+			case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts", ".udev":
57
+				continue
58
+			default:
59
+				sub, err := getDevices(filepath.Join(path, f.Name()))
60
+				if err != nil {
61
+					return nil, err
62
+				}
63
+
64
+				out = append(out, sub...)
65
+				continue
66
+			}
67
+		case f.Name() == "console":
68
+			continue
69
+		}
70
+		device, err := deviceFromPath(filepath.Join(path, f.Name()), "rwm")
71
+		if err != nil {
72
+			if err == ErrNotADevice {
73
+				continue
74
+			}
75
+			if os.IsNotExist(err) {
76
+				continue
77
+			}
78
+			return nil, err
79
+		}
80
+		out = append(out, *device)
81
+	}
82
+	return out, nil
83
+}
84
+
28 85
 func deviceFromPath(path, permissions string) (*specs.LinuxDevice, error) {
29 86
 	var stat unix.Stat_t
30 87
 	if err := unix.Lstat(path, &stat); err != nil {
... ...
@@ -19,12 +19,69 @@
19 19
 package oci
20 20
 
21 21
 import (
22
+	"context"
23
+	"io/ioutil"
22 24
 	"os"
25
+	"path/filepath"
23 26
 
27
+	"github.com/containerd/containerd/containers"
24 28
 	specs "github.com/opencontainers/runtime-spec/specs-go"
25 29
 	"golang.org/x/sys/unix"
26 30
 )
27 31
 
32
+// WithHostDevices adds all the hosts device nodes to the container's spec
33
+func WithHostDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
34
+	setLinux(s)
35
+
36
+	devs, err := getDevices("/dev")
37
+	if err != nil {
38
+		return err
39
+	}
40
+	s.Linux.Devices = append(s.Linux.Devices, devs...)
41
+	return nil
42
+}
43
+
44
+func getDevices(path string) ([]specs.LinuxDevice, error) {
45
+	files, err := ioutil.ReadDir(path)
46
+	if err != nil {
47
+		return nil, err
48
+	}
49
+	var out []specs.LinuxDevice
50
+	for _, f := range files {
51
+		switch {
52
+		case f.IsDir():
53
+			switch f.Name() {
54
+			// ".lxc" & ".lxd-mounts" added to address https://github.com/lxc/lxd/issues/2825
55
+			// ".udev" added to address https://github.com/opencontainers/runc/issues/2093
56
+			case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts", ".udev":
57
+				continue
58
+			default:
59
+				sub, err := getDevices(filepath.Join(path, f.Name()))
60
+				if err != nil {
61
+					return nil, err
62
+				}
63
+
64
+				out = append(out, sub...)
65
+				continue
66
+			}
67
+		case f.Name() == "console":
68
+			continue
69
+		}
70
+		device, err := deviceFromPath(filepath.Join(path, f.Name()), "rwm")
71
+		if err != nil {
72
+			if err == ErrNotADevice {
73
+				continue
74
+			}
75
+			if os.IsNotExist(err) {
76
+				continue
77
+			}
78
+			return nil, err
79
+		}
80
+		out = append(out, *device)
81
+	}
82
+	return out, nil
83
+}
84
+
28 85
 func deviceFromPath(path, permissions string) (*specs.LinuxDevice, error) {
29 86
 	var stat unix.Stat_t
30 87
 	if err := unix.Lstat(path, &stat); err != nil {
... ...
@@ -67,6 +67,13 @@ func WithWindowNetworksAllowUnqualifiedDNSQuery() SpecOpts {
67 67
 	}
68 68
 }
69 69
 
70
+// WithHostDevices adds all the hosts device nodes to the container's spec
71
+//
72
+// Not supported on windows
73
+func WithHostDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
74
+	return nil
75
+}
76
+
70 77
 func deviceFromPath(path, permissions string) (*specs.LinuxDevice, error) {
71 78
 	return nil, errors.New("device from path not supported on Windows")
72 79
 }
... ...
@@ -69,3 +69,7 @@ func (s *deletedState) SetExited(status int) {
69 69
 func (s *deletedState) Exec(ctx context.Context, path string, r *ExecConfig) (Process, error) {
70 70
 	return nil, errors.Errorf("cannot exec in a deleted state")
71 71
 }
72
+
73
+func (s *deletedState) Status(ctx context.Context) (string, error) {
74
+	return "stopped", nil
75
+}
... ...
@@ -261,17 +261,5 @@ func (e *execProcess) Status(ctx context.Context) (string, error) {
261 261
 	}
262 262
 	e.mu.Lock()
263 263
 	defer e.mu.Unlock()
264
-	// if we don't have a pid(pid=0) then the exec process has just been created
265
-	if e.pid.get() == 0 {
266
-		return "created", nil
267
-	}
268
-	if e.pid.get() == StoppedPID {
269
-		return "stopped", nil
270
-	}
271
-	// if we have a pid and it can be signaled, the process is running
272
-	if err := unix.Kill(e.pid.get(), 0); err == nil {
273
-		return "running", nil
274
-	}
275
-	// else if we have a pid but it can nolonger be signaled, it has stopped
276
-	return "stopped", nil
264
+	return e.execState.Status(ctx)
277 265
 }
... ...
@@ -31,6 +31,7 @@ type execState interface {
31 31
 	Delete(context.Context) error
32 32
 	Kill(context.Context, uint32, bool) error
33 33
 	SetExited(int)
34
+	Status(context.Context) (string, error)
34 35
 }
35 36
 
36 37
 type execCreatedState struct {
... ...
@@ -82,6 +83,10 @@ func (s *execCreatedState) SetExited(status int) {
82 82
 	}
83 83
 }
84 84
 
85
+func (s *execCreatedState) Status(ctx context.Context) (string, error) {
86
+	return "created", nil
87
+}
88
+
85 89
 type execRunningState struct {
86 90
 	p *execProcess
87 91
 }
... ...
@@ -120,6 +125,10 @@ func (s *execRunningState) SetExited(status int) {
120 120
 	}
121 121
 }
122 122
 
123
+func (s *execRunningState) Status(ctx context.Context) (string, error) {
124
+	return "running", nil
125
+}
126
+
123 127
 type execStoppedState struct {
124 128
 	p *execProcess
125 129
 }
... ...
@@ -157,3 +166,7 @@ func (s *execStoppedState) Kill(ctx context.Context, sig uint32, all bool) error
157 157
 func (s *execStoppedState) SetExited(status int) {
158 158
 	// no op
159 159
 }
160
+
161
+func (s *execStoppedState) Status(ctx context.Context) (string, error) {
162
+	return "stopped", nil
163
+}
... ...
@@ -56,12 +56,14 @@ type Init struct {
56 56
 
57 57
 	WorkDir string
58 58
 
59
-	id           string
60
-	Bundle       string
61
-	console      console.Console
62
-	Platform     stdio.Platform
63
-	io           *processIO
64
-	runtime      *runc.Runc
59
+	id       string
60
+	Bundle   string
61
+	console  console.Console
62
+	Platform stdio.Platform
63
+	io       *processIO
64
+	runtime  *runc.Runc
65
+	// pausing preserves the pausing state.
66
+	pausing      *atomicBool
65 67
 	status       int
66 68
 	exited       time.Time
67 69
 	pid          safePid
... ...
@@ -97,6 +99,7 @@ func New(id string, runtime *runc.Runc, stdio stdio.Stdio) *Init {
97 97
 	p := &Init{
98 98
 		id:        id,
99 99
 		runtime:   runtime,
100
+		pausing:   new(atomicBool),
100 101
 		stdio:     stdio,
101 102
 		status:    0,
102 103
 		waitBlock: make(chan struct{}),
... ...
@@ -237,17 +240,14 @@ func (p *Init) ExitedAt() time.Time {
237 237
 
238 238
 // Status of the process
239 239
 func (p *Init) Status(ctx context.Context) (string, error) {
240
+	if p.pausing.get() {
241
+		return "pausing", nil
242
+	}
243
+
240 244
 	p.mu.Lock()
241 245
 	defer p.mu.Unlock()
242 246
 
243
-	c, err := p.runtime.State(ctx, p.id)
244
-	if err != nil {
245
-		if strings.Contains(err.Error(), "does not exist") {
246
-			return "stopped", nil
247
-		}
248
-		return "", p.runtimeError(err, "OCI runtime state failed")
249
-	}
250
-	return c.Status, nil
247
+	return p.initState.Status(ctx)
251 248
 }
252 249
 
253 250
 // Start the init process
... ...
@@ -37,6 +37,7 @@ type initState interface {
37 37
 	Exec(context.Context, string, *ExecConfig) (Process, error)
38 38
 	Kill(context.Context, uint32, bool) error
39 39
 	SetExited(int)
40
+	Status(context.Context) (string, error)
40 41
 }
41 42
 
42 43
 type createdState struct {
... ...
@@ -103,6 +104,10 @@ func (s *createdState) Exec(ctx context.Context, path string, r *ExecConfig) (Pr
103 103
 	return s.p.exec(ctx, path, r)
104 104
 }
105 105
 
106
+func (s *createdState) Status(ctx context.Context) (string, error) {
107
+	return "created", nil
108
+}
109
+
106 110
 type createdCheckpointState struct {
107 111
 	p    *Init
108 112
 	opts *runc.RestoreOpts
... ...
@@ -211,6 +216,10 @@ func (s *createdCheckpointState) Exec(ctx context.Context, path string, r *ExecC
211 211
 	return nil, errors.Errorf("cannot exec in a created state")
212 212
 }
213 213
 
214
+func (s *createdCheckpointState) Status(ctx context.Context) (string, error) {
215
+	return "created", nil
216
+}
217
+
214 218
 type runningState struct {
215 219
 	p *Init
216 220
 }
... ...
@@ -228,6 +237,13 @@ func (s *runningState) transition(name string) error {
228 228
 }
229 229
 
230 230
 func (s *runningState) Pause(ctx context.Context) error {
231
+	s.p.pausing.set(true)
232
+	// NOTE "pausing" will be returned in the short window
233
+	// after `transition("paused")`, before `pausing` is reset
234
+	// to false. That doesn't break the state machine, just
235
+	// delays the "paused" state a little bit.
236
+	defer s.p.pausing.set(false)
237
+
231 238
 	if err := s.p.runtime.Pause(ctx, s.p.id); err != nil {
232 239
 		return s.p.runtimeError(err, "OCI runtime pause failed")
233 240
 	}
... ...
@@ -271,6 +287,10 @@ func (s *runningState) Exec(ctx context.Context, path string, r *ExecConfig) (Pr
271 271
 	return s.p.exec(ctx, path, r)
272 272
 }
273 273
 
274
+func (s *runningState) Status(ctx context.Context) (string, error) {
275
+	return "running", nil
276
+}
277
+
274 278
 type pausedState struct {
275 279
 	p *Init
276 280
 }
... ...
@@ -335,6 +355,10 @@ func (s *pausedState) Exec(ctx context.Context, path string, r *ExecConfig) (Pro
335 335
 	return nil, errors.Errorf("cannot exec in a paused state")
336 336
 }
337 337
 
338
+func (s *pausedState) Status(ctx context.Context) (string, error) {
339
+	return "paused", nil
340
+}
341
+
338 342
 type stoppedState struct {
339 343
 	p *Init
340 344
 }
... ...
@@ -387,3 +411,7 @@ func (s *stoppedState) SetExited(status int) {
387 387
 func (s *stoppedState) Exec(ctx context.Context, path string, r *ExecConfig) (Process, error) {
388 388
 	return nil, errors.Errorf("cannot exec in a stopped state")
389 389
 }
390
+
391
+func (s *stoppedState) Status(ctx context.Context) (string, error) {
392
+	return "stopped", nil
393
+}
... ...
@@ -40,7 +40,9 @@ import (
40 40
 
41 41
 var bufPool = sync.Pool{
42 42
 	New: func() interface{} {
43
-		buffer := make([]byte, 32<<10)
43
+		// setting to 4096 to align with PIPE_BUF
44
+		// http://man7.org/linux/man-pages/man7/pipe.7.html
45
+		buffer := make([]byte, 4096)
44 46
 		return &buffer
45 47
 	},
46 48
 }
... ...
@@ -27,6 +27,7 @@ import (
27 27
 	"path/filepath"
28 28
 	"strings"
29 29
 	"sync"
30
+	"sync/atomic"
30 31
 	"time"
31 32
 
32 33
 	"github.com/containerd/containerd/errdefs"
... ...
@@ -62,6 +63,20 @@ func (s *safePid) set(pid int) {
62 62
 	s.Unlock()
63 63
 }
64 64
 
65
+type atomicBool int32
66
+
67
+func (ab *atomicBool) set(b bool) {
68
+	if b {
69
+		atomic.StoreInt32((*int32)(ab), 1)
70
+	} else {
71
+		atomic.StoreInt32((*int32)(ab), 0)
72
+	}
73
+}
74
+
75
+func (ab *atomicBool) get() bool {
76
+	return atomic.LoadInt32((*int32)(ab)) == 1
77
+}
78
+
65 79
 // TODO(mlaventure): move to runc package?
66 80
 func getLastRuntimeError(r *runc.Runc) (string, error) {
67 81
 	if r.Log == "" {
68 82
new file mode 100644
... ...
@@ -0,0 +1,797 @@
0
+/*
1
+   Copyright The containerd Authors.
2
+
3
+   Licensed under the Apache License, Version 2.0 (the "License");
4
+   you may not use this file except in compliance with the License.
5
+   You may obtain a copy of the License at
6
+
7
+       http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+   Unless required by applicable law or agreed to in writing, software
10
+   distributed under the License is distributed on an "AS IS" BASIS,
11
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+   See the License for the specific language governing permissions and
13
+   limitations under the License.
14
+*/
15
+
16
+// Package docker provides a general type to represent any way of referencing images within the registry.
17
+// Its main purpose is to abstract tags and digests (content-addressable hash).
18
+//
19
+// Grammar
20
+//
21
+// 	reference                       := name [ ":" tag ] [ "@" digest ]
22
+//	name                            := [domain '/'] path-component ['/' path-component]*
23
+//	domain                          := domain-component ['.' domain-component]* [':' port-number]
24
+//	domain-component                := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/
25
+//	port-number                     := /[0-9]+/
26
+//	path-component                  := alpha-numeric [separator alpha-numeric]*
27
+// 	alpha-numeric                   := /[a-z0-9]+/
28
+//	separator                       := /[_.]|__|[-]*/
29
+//
30
+//	tag                             := /[\w][\w.-]{0,127}/
31
+//
32
+//	digest                          := digest-algorithm ":" digest-hex
33
+//	digest-algorithm                := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ]*
34
+//	digest-algorithm-separator      := /[+.-_]/
35
+//	digest-algorithm-component      := /[A-Za-z][A-Za-z0-9]*/
36
+//	digest-hex                      := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value
37
+//
38
+//	identifier                      := /[a-f0-9]{64}/
39
+//	short-identifier                := /[a-f0-9]{6,64}/
40
+package docker
41
+
42
+import (
43
+	"errors"
44
+	"fmt"
45
+	"path"
46
+	"regexp"
47
+	"strings"
48
+
49
+	"github.com/opencontainers/go-digest"
50
+)
51
+
52
+const (
53
+	// NameTotalLengthMax is the maximum total number of characters in a repository name.
54
+	NameTotalLengthMax = 255
55
+)
56
+
57
+var (
58
+	// ErrReferenceInvalidFormat represents an error while trying to parse a string as a reference.
59
+	ErrReferenceInvalidFormat = errors.New("invalid reference format")
60
+
61
+	// ErrTagInvalidFormat represents an error while trying to parse a string as a tag.
62
+	ErrTagInvalidFormat = errors.New("invalid tag format")
63
+
64
+	// ErrDigestInvalidFormat represents an error while trying to parse a string as a tag.
65
+	ErrDigestInvalidFormat = errors.New("invalid digest format")
66
+
67
+	// ErrNameContainsUppercase is returned for invalid repository names that contain uppercase characters.
68
+	ErrNameContainsUppercase = errors.New("repository name must be lowercase")
69
+
70
+	// ErrNameEmpty is returned for empty, invalid repository names.
71
+	ErrNameEmpty = errors.New("repository name must have at least one component")
72
+
73
+	// ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax.
74
+	ErrNameTooLong = fmt.Errorf("repository name must not be more than %v characters", NameTotalLengthMax)
75
+
76
+	// ErrNameNotCanonical is returned when a name is not canonical.
77
+	ErrNameNotCanonical = errors.New("repository name must be canonical")
78
+)
79
+
80
+// Reference is an opaque object reference identifier that may include
81
+// modifiers such as a hostname, name, tag, and digest.
82
+type Reference interface {
83
+	// String returns the full reference
84
+	String() string
85
+}
86
+
87
+// Field provides a wrapper type for resolving correct reference types when
88
+// working with encoding.
89
+type Field struct {
90
+	reference Reference
91
+}
92
+
93
+// AsField wraps a reference in a Field for encoding.
94
+func AsField(reference Reference) Field {
95
+	return Field{reference}
96
+}
97
+
98
+// Reference unwraps the reference type from the field to
99
+// return the Reference object. This object should be
100
+// of the appropriate type to further check for different
101
+// reference types.
102
+func (f Field) Reference() Reference {
103
+	return f.reference
104
+}
105
+
106
+// MarshalText serializes the field to byte text which
107
+// is the string of the reference.
108
+func (f Field) MarshalText() (p []byte, err error) {
109
+	return []byte(f.reference.String()), nil
110
+}
111
+
112
+// UnmarshalText parses text bytes by invoking the
113
+// reference parser to ensure the appropriately
114
+// typed reference object is wrapped by field.
115
+func (f *Field) UnmarshalText(p []byte) error {
116
+	r, err := Parse(string(p))
117
+	if err != nil {
118
+		return err
119
+	}
120
+
121
+	f.reference = r
122
+	return nil
123
+}
124
+
125
+// Named is an object with a full name
126
+type Named interface {
127
+	Reference
128
+	Name() string
129
+}
130
+
131
+// Tagged is an object which has a tag
132
+type Tagged interface {
133
+	Reference
134
+	Tag() string
135
+}
136
+
137
+// NamedTagged is an object including a name and tag.
138
+type NamedTagged interface {
139
+	Named
140
+	Tag() string
141
+}
142
+
143
+// Digested is an object which has a digest
144
+// in which it can be referenced by
145
+type Digested interface {
146
+	Reference
147
+	Digest() digest.Digest
148
+}
149
+
150
+// Canonical reference is an object with a fully unique
151
+// name including a name with domain and digest
152
+type Canonical interface {
153
+	Named
154
+	Digest() digest.Digest
155
+}
156
+
157
+// namedRepository is a reference to a repository with a name.
158
+// A namedRepository has both domain and path components.
159
+type namedRepository interface {
160
+	Named
161
+	Domain() string
162
+	Path() string
163
+}
164
+
165
+// Domain returns the domain part of the Named reference
166
+func Domain(named Named) string {
167
+	if r, ok := named.(namedRepository); ok {
168
+		return r.Domain()
169
+	}
170
+	domain, _ := splitDomain(named.Name())
171
+	return domain
172
+}
173
+
174
+// Path returns the name without the domain part of the Named reference
175
+func Path(named Named) (name string) {
176
+	if r, ok := named.(namedRepository); ok {
177
+		return r.Path()
178
+	}
179
+	_, path := splitDomain(named.Name())
180
+	return path
181
+}
182
+
183
+func splitDomain(name string) (string, string) {
184
+	match := anchoredNameRegexp.FindStringSubmatch(name)
185
+	if len(match) != 3 {
186
+		return "", name
187
+	}
188
+	return match[1], match[2]
189
+}
190
+
191
+// SplitHostname splits a named reference into a
192
+// hostname and name string. If no valid hostname is
193
+// found, the hostname is empty and the full value
194
+// is returned as name
195
+// DEPRECATED: Use Domain or Path
196
+func SplitHostname(named Named) (string, string) {
197
+	if r, ok := named.(namedRepository); ok {
198
+		return r.Domain(), r.Path()
199
+	}
200
+	return splitDomain(named.Name())
201
+}
202
+
203
+// Parse parses s and returns a syntactically valid Reference.
204
+// If an error was encountered it is returned, along with a nil Reference.
205
+// NOTE: Parse will not handle short digests.
206
+func Parse(s string) (Reference, error) {
207
+	matches := ReferenceRegexp.FindStringSubmatch(s)
208
+	if matches == nil {
209
+		if s == "" {
210
+			return nil, ErrNameEmpty
211
+		}
212
+		if ReferenceRegexp.FindStringSubmatch(strings.ToLower(s)) != nil {
213
+			return nil, ErrNameContainsUppercase
214
+		}
215
+		return nil, ErrReferenceInvalidFormat
216
+	}
217
+
218
+	if len(matches[1]) > NameTotalLengthMax {
219
+		return nil, ErrNameTooLong
220
+	}
221
+
222
+	var repo repository
223
+
224
+	nameMatch := anchoredNameRegexp.FindStringSubmatch(matches[1])
225
+	if len(nameMatch) == 3 {
226
+		repo.domain = nameMatch[1]
227
+		repo.path = nameMatch[2]
228
+	} else {
229
+		repo.domain = ""
230
+		repo.path = matches[1]
231
+	}
232
+
233
+	ref := reference{
234
+		namedRepository: repo,
235
+		tag:             matches[2],
236
+	}
237
+	if matches[3] != "" {
238
+		var err error
239
+		ref.digest, err = digest.Parse(matches[3])
240
+		if err != nil {
241
+			return nil, err
242
+		}
243
+	}
244
+
245
+	r := getBestReferenceType(ref)
246
+	if r == nil {
247
+		return nil, ErrNameEmpty
248
+	}
249
+
250
+	return r, nil
251
+}
252
+
253
+// ParseNamed parses s and returns a syntactically valid reference implementing
254
+// the Named interface. The reference must have a name and be in the canonical
255
+// form, otherwise an error is returned.
256
+// If an error was encountered it is returned, along with a nil Reference.
257
+// NOTE: ParseNamed will not handle short digests.
258
+func ParseNamed(s string) (Named, error) {
259
+	named, err := ParseNormalizedNamed(s)
260
+	if err != nil {
261
+		return nil, err
262
+	}
263
+	if named.String() != s {
264
+		return nil, ErrNameNotCanonical
265
+	}
266
+	return named, nil
267
+}
268
+
269
+// WithName returns a named object representing the given string. If the input
270
+// is invalid ErrReferenceInvalidFormat will be returned.
271
+func WithName(name string) (Named, error) {
272
+	if len(name) > NameTotalLengthMax {
273
+		return nil, ErrNameTooLong
274
+	}
275
+
276
+	match := anchoredNameRegexp.FindStringSubmatch(name)
277
+	if match == nil || len(match) != 3 {
278
+		return nil, ErrReferenceInvalidFormat
279
+	}
280
+	return repository{
281
+		domain: match[1],
282
+		path:   match[2],
283
+	}, nil
284
+}
285
+
286
+// WithTag combines the name from "name" and the tag from "tag" to form a
287
+// reference incorporating both the name and the tag.
288
+func WithTag(name Named, tag string) (NamedTagged, error) {
289
+	if !anchoredTagRegexp.MatchString(tag) {
290
+		return nil, ErrTagInvalidFormat
291
+	}
292
+	var repo repository
293
+	if r, ok := name.(namedRepository); ok {
294
+		repo.domain = r.Domain()
295
+		repo.path = r.Path()
296
+	} else {
297
+		repo.path = name.Name()
298
+	}
299
+	if canonical, ok := name.(Canonical); ok {
300
+		return reference{
301
+			namedRepository: repo,
302
+			tag:             tag,
303
+			digest:          canonical.Digest(),
304
+		}, nil
305
+	}
306
+	return taggedReference{
307
+		namedRepository: repo,
308
+		tag:             tag,
309
+	}, nil
310
+}
311
+
312
+// WithDigest combines the name from "name" and the digest from "digest" to form
313
+// a reference incorporating both the name and the digest.
314
+func WithDigest(name Named, digest digest.Digest) (Canonical, error) {
315
+	if !anchoredDigestRegexp.MatchString(digest.String()) {
316
+		return nil, ErrDigestInvalidFormat
317
+	}
318
+	var repo repository
319
+	if r, ok := name.(namedRepository); ok {
320
+		repo.domain = r.Domain()
321
+		repo.path = r.Path()
322
+	} else {
323
+		repo.path = name.Name()
324
+	}
325
+	if tagged, ok := name.(Tagged); ok {
326
+		return reference{
327
+			namedRepository: repo,
328
+			tag:             tagged.Tag(),
329
+			digest:          digest,
330
+		}, nil
331
+	}
332
+	return canonicalReference{
333
+		namedRepository: repo,
334
+		digest:          digest,
335
+	}, nil
336
+}
337
+
338
+// TrimNamed removes any tag or digest from the named reference.
339
+func TrimNamed(ref Named) Named {
340
+	domain, path := SplitHostname(ref)
341
+	return repository{
342
+		domain: domain,
343
+		path:   path,
344
+	}
345
+}
346
+
347
+func getBestReferenceType(ref reference) Reference {
348
+	if ref.Name() == "" {
349
+		// Allow digest only references
350
+		if ref.digest != "" {
351
+			return digestReference(ref.digest)
352
+		}
353
+		return nil
354
+	}
355
+	if ref.tag == "" {
356
+		if ref.digest != "" {
357
+			return canonicalReference{
358
+				namedRepository: ref.namedRepository,
359
+				digest:          ref.digest,
360
+			}
361
+		}
362
+		return ref.namedRepository
363
+	}
364
+	if ref.digest == "" {
365
+		return taggedReference{
366
+			namedRepository: ref.namedRepository,
367
+			tag:             ref.tag,
368
+		}
369
+	}
370
+
371
+	return ref
372
+}
373
+
374
+type reference struct {
375
+	namedRepository
376
+	tag    string
377
+	digest digest.Digest
378
+}
379
+
380
+func (r reference) String() string {
381
+	return r.Name() + ":" + r.tag + "@" + r.digest.String()
382
+}
383
+
384
+func (r reference) Tag() string {
385
+	return r.tag
386
+}
387
+
388
+func (r reference) Digest() digest.Digest {
389
+	return r.digest
390
+}
391
+
392
+type repository struct {
393
+	domain string
394
+	path   string
395
+}
396
+
397
+func (r repository) String() string {
398
+	return r.Name()
399
+}
400
+
401
+func (r repository) Name() string {
402
+	if r.domain == "" {
403
+		return r.path
404
+	}
405
+	return r.domain + "/" + r.path
406
+}
407
+
408
+func (r repository) Domain() string {
409
+	return r.domain
410
+}
411
+
412
+func (r repository) Path() string {
413
+	return r.path
414
+}
415
+
416
+type digestReference digest.Digest
417
+
418
+func (d digestReference) String() string {
419
+	return digest.Digest(d).String()
420
+}
421
+
422
+func (d digestReference) Digest() digest.Digest {
423
+	return digest.Digest(d)
424
+}
425
+
426
+type taggedReference struct {
427
+	namedRepository
428
+	tag string
429
+}
430
+
431
+func (t taggedReference) String() string {
432
+	return t.Name() + ":" + t.tag
433
+}
434
+
435
+func (t taggedReference) Tag() string {
436
+	return t.tag
437
+}
438
+
439
+type canonicalReference struct {
440
+	namedRepository
441
+	digest digest.Digest
442
+}
443
+
444
+func (c canonicalReference) String() string {
445
+	return c.Name() + "@" + c.digest.String()
446
+}
447
+
448
+func (c canonicalReference) Digest() digest.Digest {
449
+	return c.digest
450
+}
451
+
452
+var (
453
+	// alphaNumericRegexp defines the alpha numeric atom, typically a
454
+	// component of names. This only allows lower case characters and digits.
455
+	alphaNumericRegexp = match(`[a-z0-9]+`)
456
+
457
+	// separatorRegexp defines the separators allowed to be embedded in name
458
+	// components. This allow one period, one or two underscore and multiple
459
+	// dashes.
460
+	separatorRegexp = match(`(?:[._]|__|[-]*)`)
461
+
462
+	// nameComponentRegexp restricts registry path component names to start
463
+	// with at least one letter or number, with following parts able to be
464
+	// separated by one period, one or two underscore and multiple dashes.
465
+	nameComponentRegexp = expression(
466
+		alphaNumericRegexp,
467
+		optional(repeated(separatorRegexp, alphaNumericRegexp)))
468
+
469
+	// domainComponentRegexp restricts the registry domain component of a
470
+	// repository name to start with a component as defined by DomainRegexp
471
+	// and followed by an optional port.
472
+	domainComponentRegexp = match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`)
473
+
474
+	// DomainRegexp defines the structure of potential domain components
475
+	// that may be part of image names. This is purposely a subset of what is
476
+	// allowed by DNS to ensure backwards compatibility with Docker image
477
+	// names.
478
+	DomainRegexp = expression(
479
+		domainComponentRegexp,
480
+		optional(repeated(literal(`.`), domainComponentRegexp)),
481
+		optional(literal(`:`), match(`[0-9]+`)))
482
+
483
+	// TagRegexp matches valid tag names. From docker/docker:graph/tags.go.
484
+	TagRegexp = match(`[\w][\w.-]{0,127}`)
485
+
486
+	// anchoredTagRegexp matches valid tag names, anchored at the start and
487
+	// end of the matched string.
488
+	anchoredTagRegexp = anchored(TagRegexp)
489
+
490
+	// DigestRegexp matches valid digests.
491
+	DigestRegexp = match(`[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}`)
492
+
493
+	// anchoredDigestRegexp matches valid digests, anchored at the start and
494
+	// end of the matched string.
495
+	anchoredDigestRegexp = anchored(DigestRegexp)
496
+
497
+	// NameRegexp is the format for the name component of references. The
498
+	// regexp has capturing groups for the domain and name part omitting
499
+	// the separating forward slash from either.
500
+	NameRegexp = expression(
501
+		optional(DomainRegexp, literal(`/`)),
502
+		nameComponentRegexp,
503
+		optional(repeated(literal(`/`), nameComponentRegexp)))
504
+
505
+	// anchoredNameRegexp is used to parse a name value, capturing the
506
+	// domain and trailing components.
507
+	anchoredNameRegexp = anchored(
508
+		optional(capture(DomainRegexp), literal(`/`)),
509
+		capture(nameComponentRegexp,
510
+			optional(repeated(literal(`/`), nameComponentRegexp))))
511
+
512
+	// ReferenceRegexp is the full supported format of a reference. The regexp
513
+	// is anchored and has capturing groups for name, tag, and digest
514
+	// components.
515
+	ReferenceRegexp = anchored(capture(NameRegexp),
516
+		optional(literal(":"), capture(TagRegexp)),
517
+		optional(literal("@"), capture(DigestRegexp)))
518
+
519
+	// IdentifierRegexp is the format for string identifier used as a
520
+	// content addressable identifier using sha256. These identifiers
521
+	// are like digests without the algorithm, since sha256 is used.
522
+	IdentifierRegexp = match(`([a-f0-9]{64})`)
523
+
524
+	// ShortIdentifierRegexp is the format used to represent a prefix
525
+	// of an identifier. A prefix may be used to match a sha256 identifier
526
+	// within a list of trusted identifiers.
527
+	ShortIdentifierRegexp = match(`([a-f0-9]{6,64})`)
528
+
529
+	// anchoredIdentifierRegexp is used to check or match an
530
+	// identifier value, anchored at start and end of string.
531
+	anchoredIdentifierRegexp = anchored(IdentifierRegexp)
532
+)
533
+
534
+// match compiles the string to a regular expression.
535
+var match = regexp.MustCompile
536
+
537
+// literal compiles s into a literal regular expression, escaping any regexp
538
+// reserved characters.
539
+func literal(s string) *regexp.Regexp {
540
+	re := match(regexp.QuoteMeta(s))
541
+
542
+	if _, complete := re.LiteralPrefix(); !complete {
543
+		panic("must be a literal")
544
+	}
545
+
546
+	return re
547
+}
548
+
549
+// expression defines a full expression, where each regular expression must
550
+// follow the previous.
551
+func expression(res ...*regexp.Regexp) *regexp.Regexp {
552
+	var s string
553
+	for _, re := range res {
554
+		s += re.String()
555
+	}
556
+
557
+	return match(s)
558
+}
559
+
560
+// optional wraps the expression in a non-capturing group and makes the
561
+// production optional.
562
+func optional(res ...*regexp.Regexp) *regexp.Regexp {
563
+	return match(group(expression(res...)).String() + `?`)
564
+}
565
+
566
+// repeated wraps the regexp in a non-capturing group to get one or more
567
+// matches.
568
+func repeated(res ...*regexp.Regexp) *regexp.Regexp {
569
+	return match(group(expression(res...)).String() + `+`)
570
+}
571
+
572
+// group wraps the regexp in a non-capturing group.
573
+func group(res ...*regexp.Regexp) *regexp.Regexp {
574
+	return match(`(?:` + expression(res...).String() + `)`)
575
+}
576
+
577
+// capture wraps the expression in a capturing group.
578
+func capture(res ...*regexp.Regexp) *regexp.Regexp {
579
+	return match(`(` + expression(res...).String() + `)`)
580
+}
581
+
582
+// anchored anchors the regular expression by adding start and end delimiters.
583
+func anchored(res ...*regexp.Regexp) *regexp.Regexp {
584
+	return match(`^` + expression(res...).String() + `$`)
585
+}
586
+
587
+var (
588
+	legacyDefaultDomain = "index.docker.io"
589
+	defaultDomain       = "docker.io"
590
+	officialRepoName    = "library"
591
+	defaultTag          = "latest"
592
+)
593
+
594
+// normalizedNamed represents a name which has been
595
+// normalized and has a familiar form. A familiar name
596
+// is what is used in Docker UI. An example normalized
597
+// name is "docker.io/library/ubuntu" and corresponding
598
+// familiar name of "ubuntu".
599
+type normalizedNamed interface {
600
+	Named
601
+	Familiar() Named
602
+}
603
+
604
+// ParseNormalizedNamed parses a string into a named reference
605
+// transforming a familiar name from Docker UI to a fully
606
+// qualified reference. If the value may be an identifier
607
+// use ParseAnyReference.
608
+func ParseNormalizedNamed(s string) (Named, error) {
609
+	if ok := anchoredIdentifierRegexp.MatchString(s); ok {
610
+		return nil, fmt.Errorf("invalid repository name (%s), cannot specify 64-byte hexadecimal strings", s)
611
+	}
612
+	domain, remainder := splitDockerDomain(s)
613
+	var remoteName string
614
+	if tagSep := strings.IndexRune(remainder, ':'); tagSep > -1 {
615
+		remoteName = remainder[:tagSep]
616
+	} else {
617
+		remoteName = remainder
618
+	}
619
+	if strings.ToLower(remoteName) != remoteName {
620
+		return nil, errors.New("invalid reference format: repository name must be lowercase")
621
+	}
622
+
623
+	ref, err := Parse(domain + "/" + remainder)
624
+	if err != nil {
625
+		return nil, err
626
+	}
627
+	named, isNamed := ref.(Named)
628
+	if !isNamed {
629
+		return nil, fmt.Errorf("reference %s has no name", ref.String())
630
+	}
631
+	return named, nil
632
+}
633
+
634
+// ParseDockerRef normalizes the image reference following the docker convention. This is added
635
+// mainly for backward compatibility.
636
+// The reference returned can only be either tagged or digested. For reference contains both tag
637
+// and digest, the function returns digested reference, e.g. docker.io/library/busybox:latest@
638
+// sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa will be returned as
639
+// docker.io/library/busybox@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa.
640
+func ParseDockerRef(ref string) (Named, error) {
641
+	named, err := ParseNormalizedNamed(ref)
642
+	if err != nil {
643
+		return nil, err
644
+	}
645
+	if _, ok := named.(NamedTagged); ok {
646
+		if canonical, ok := named.(Canonical); ok {
647
+			// The reference is both tagged and digested, only
648
+			// return digested.
649
+			newNamed, err := WithName(canonical.Name())
650
+			if err != nil {
651
+				return nil, err
652
+			}
653
+			newCanonical, err := WithDigest(newNamed, canonical.Digest())
654
+			if err != nil {
655
+				return nil, err
656
+			}
657
+			return newCanonical, nil
658
+		}
659
+	}
660
+	return TagNameOnly(named), nil
661
+}
662
+
663
+// splitDockerDomain splits a repository name to domain and remotename string.
664
+// If no valid domain is found, the default domain is used. Repository name
665
+// needs to be already validated before.
666
+func splitDockerDomain(name string) (domain, remainder string) {
667
+	i := strings.IndexRune(name, '/')
668
+	if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost") {
669
+		domain, remainder = defaultDomain, name
670
+	} else {
671
+		domain, remainder = name[:i], name[i+1:]
672
+	}
673
+	if domain == legacyDefaultDomain {
674
+		domain = defaultDomain
675
+	}
676
+	if domain == defaultDomain && !strings.ContainsRune(remainder, '/') {
677
+		remainder = officialRepoName + "/" + remainder
678
+	}
679
+	return
680
+}
681
+
682
+// familiarizeName returns a shortened version of the name familiar
683
+// to to the Docker UI. Familiar names have the default domain
684
+// "docker.io" and "library/" repository prefix removed.
685
+// For example, "docker.io/library/redis" will have the familiar
686
+// name "redis" and "docker.io/dmcgowan/myapp" will be "dmcgowan/myapp".
687
+// Returns a familiarized named only reference.
688
+func familiarizeName(named namedRepository) repository {
689
+	repo := repository{
690
+		domain: named.Domain(),
691
+		path:   named.Path(),
692
+	}
693
+
694
+	if repo.domain == defaultDomain {
695
+		repo.domain = ""
696
+		// Handle official repositories which have the pattern "library/<official repo name>"
697
+		if split := strings.Split(repo.path, "/"); len(split) == 2 && split[0] == officialRepoName {
698
+			repo.path = split[1]
699
+		}
700
+	}
701
+	return repo
702
+}
703
+
704
+func (r reference) Familiar() Named {
705
+	return reference{
706
+		namedRepository: familiarizeName(r.namedRepository),
707
+		tag:             r.tag,
708
+		digest:          r.digest,
709
+	}
710
+}
711
+
712
+func (r repository) Familiar() Named {
713
+	return familiarizeName(r)
714
+}
715
+
716
+func (t taggedReference) Familiar() Named {
717
+	return taggedReference{
718
+		namedRepository: familiarizeName(t.namedRepository),
719
+		tag:             t.tag,
720
+	}
721
+}
722
+
723
+func (c canonicalReference) Familiar() Named {
724
+	return canonicalReference{
725
+		namedRepository: familiarizeName(c.namedRepository),
726
+		digest:          c.digest,
727
+	}
728
+}
729
+
730
+// TagNameOnly adds the default tag "latest" to a reference if it only has
731
+// a repo name.
732
+func TagNameOnly(ref Named) Named {
733
+	if IsNameOnly(ref) {
734
+		namedTagged, err := WithTag(ref, defaultTag)
735
+		if err != nil {
736
+			// Default tag must be valid, to create a NamedTagged
737
+			// type with non-validated input the WithTag function
738
+			// should be used instead
739
+			panic(err)
740
+		}
741
+		return namedTagged
742
+	}
743
+	return ref
744
+}
745
+
746
+// ParseAnyReference parses a reference string as a possible identifier,
747
+// full digest, or familiar name.
748
+func ParseAnyReference(ref string) (Reference, error) {
749
+	if ok := anchoredIdentifierRegexp.MatchString(ref); ok {
750
+		return digestReference("sha256:" + ref), nil
751
+	}
752
+	if dgst, err := digest.Parse(ref); err == nil {
753
+		return digestReference(dgst), nil
754
+	}
755
+
756
+	return ParseNormalizedNamed(ref)
757
+}
758
+
759
+// IsNameOnly returns true if reference only contains a repo name.
760
+func IsNameOnly(ref Named) bool {
761
+	if _, ok := ref.(NamedTagged); ok {
762
+		return false
763
+	}
764
+	if _, ok := ref.(Canonical); ok {
765
+		return false
766
+	}
767
+	return true
768
+}
769
+
770
+// FamiliarName returns the familiar name string
771
+// for the given named, familiarizing if needed.
772
+func FamiliarName(ref Named) string {
773
+	if nn, ok := ref.(normalizedNamed); ok {
774
+		return nn.Familiar().Name()
775
+	}
776
+	return ref.Name()
777
+}
778
+
779
+// FamiliarString returns the familiar string representation
780
+// for the given reference, familiarizing if needed.
781
+func FamiliarString(ref Reference) string {
782
+	if nn, ok := ref.(normalizedNamed); ok {
783
+		return nn.Familiar().String()
784
+	}
785
+	return ref.String()
786
+}
787
+
788
+// FamiliarMatch reports whether ref matches the specified pattern.
789
+// See https://godoc.org/path#Match for supported patterns.
790
+func FamiliarMatch(pattern string, ref Reference) (bool, error) {
791
+	matched, err := path.Match(pattern, FamiliarString(ref))
792
+	if namedRef, isNamed := ref.(Named); isNamed && !matched {
793
+		matched, _ = path.Match(pattern, FamiliarName(namedRef))
794
+	}
795
+	return matched, err
796
+}
0 797
new file mode 100644
... ...
@@ -0,0 +1,283 @@
0
+/*
1
+   Copyright The containerd Authors.
2
+
3
+   Licensed under the Apache License, Version 2.0 (the "License");
4
+   you may not use this file except in compliance with the License.
5
+   You may obtain a copy of the License at
6
+
7
+       http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+   Unless required by applicable law or agreed to in writing, software
10
+   distributed under the License is distributed on an "AS IS" BASIS,
11
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+   See the License for the specific language governing permissions and
13
+   limitations under the License.
14
+*/
15
+
16
+package docker
17
+
18
+import (
19
+	"encoding/json"
20
+	"fmt"
21
+	"strings"
22
+)
23
+
24
+// ErrorCoder is the base interface for ErrorCode and Error allowing
25
+// users of each to just call ErrorCode to get the real ID of each
26
+type ErrorCoder interface {
27
+	ErrorCode() ErrorCode
28
+}
29
+
30
+// ErrorCode represents the error type. The errors are serialized via strings
31
+// and the integer format may change and should *never* be exported.
32
+type ErrorCode int
33
+
34
+var _ error = ErrorCode(0)
35
+
36
+// ErrorCode just returns itself
37
+func (ec ErrorCode) ErrorCode() ErrorCode {
38
+	return ec
39
+}
40
+
41
+// Error returns the ID/Value
42
+func (ec ErrorCode) Error() string {
43
+	// NOTE(stevvooe): Cannot use message here since it may have unpopulated args.
44
+	return strings.ToLower(strings.Replace(ec.String(), "_", " ", -1))
45
+}
46
+
47
+// Descriptor returns the descriptor for the error code.
48
+func (ec ErrorCode) Descriptor() ErrorDescriptor {
49
+	d, ok := errorCodeToDescriptors[ec]
50
+
51
+	if !ok {
52
+		return ErrorCodeUnknown.Descriptor()
53
+	}
54
+
55
+	return d
56
+}
57
+
58
+// String returns the canonical identifier for this error code.
59
+func (ec ErrorCode) String() string {
60
+	return ec.Descriptor().Value
61
+}
62
+
63
+// Message returned the human-readable error message for this error code.
64
+func (ec ErrorCode) Message() string {
65
+	return ec.Descriptor().Message
66
+}
67
+
68
+// MarshalText encodes the receiver into UTF-8-encoded text and returns the
69
+// result.
70
+func (ec ErrorCode) MarshalText() (text []byte, err error) {
71
+	return []byte(ec.String()), nil
72
+}
73
+
74
+// UnmarshalText decodes the form generated by MarshalText.
75
+func (ec *ErrorCode) UnmarshalText(text []byte) error {
76
+	desc, ok := idToDescriptors[string(text)]
77
+
78
+	if !ok {
79
+		desc = ErrorCodeUnknown.Descriptor()
80
+	}
81
+
82
+	*ec = desc.Code
83
+
84
+	return nil
85
+}
86
+
87
+// WithMessage creates a new Error struct based on the passed-in info and
88
+// overrides the Message property.
89
+func (ec ErrorCode) WithMessage(message string) Error {
90
+	return Error{
91
+		Code:    ec,
92
+		Message: message,
93
+	}
94
+}
95
+
96
+// WithDetail creates a new Error struct based on the passed-in info and
97
+// set the Detail property appropriately
98
+func (ec ErrorCode) WithDetail(detail interface{}) Error {
99
+	return Error{
100
+		Code:    ec,
101
+		Message: ec.Message(),
102
+	}.WithDetail(detail)
103
+}
104
+
105
+// WithArgs creates a new Error struct and sets the Args slice
106
+func (ec ErrorCode) WithArgs(args ...interface{}) Error {
107
+	return Error{
108
+		Code:    ec,
109
+		Message: ec.Message(),
110
+	}.WithArgs(args...)
111
+}
112
+
113
+// Error provides a wrapper around ErrorCode with extra Details provided.
114
+type Error struct {
115
+	Code    ErrorCode   `json:"code"`
116
+	Message string      `json:"message"`
117
+	Detail  interface{} `json:"detail,omitempty"`
118
+
119
+	// TODO(duglin): See if we need an "args" property so we can do the
120
+	// variable substitution right before showing the message to the user
121
+}
122
+
123
+var _ error = Error{}
124
+
125
+// ErrorCode returns the ID/Value of this Error
126
+func (e Error) ErrorCode() ErrorCode {
127
+	return e.Code
128
+}
129
+
130
+// Error returns a human readable representation of the error.
131
+func (e Error) Error() string {
132
+	return fmt.Sprintf("%s: %s", e.Code.Error(), e.Message)
133
+}
134
+
135
+// WithDetail will return a new Error, based on the current one, but with
136
+// some Detail info added
137
+func (e Error) WithDetail(detail interface{}) Error {
138
+	return Error{
139
+		Code:    e.Code,
140
+		Message: e.Message,
141
+		Detail:  detail,
142
+	}
143
+}
144
+
145
+// WithArgs uses the passed-in list of interface{} as the substitution
146
+// variables in the Error's Message string, but returns a new Error
147
+func (e Error) WithArgs(args ...interface{}) Error {
148
+	return Error{
149
+		Code:    e.Code,
150
+		Message: fmt.Sprintf(e.Code.Message(), args...),
151
+		Detail:  e.Detail,
152
+	}
153
+}
154
+
155
+// ErrorDescriptor provides relevant information about a given error code.
156
+type ErrorDescriptor struct {
157
+	// Code is the error code that this descriptor describes.
158
+	Code ErrorCode
159
+
160
+	// Value provides a unique, string key, often captilized with
161
+	// underscores, to identify the error code. This value is used as the
162
+	// keyed value when serializing api errors.
163
+	Value string
164
+
165
+	// Message is a short, human readable decription of the error condition
166
+	// included in API responses.
167
+	Message string
168
+
169
+	// Description provides a complete account of the errors purpose, suitable
170
+	// for use in documentation.
171
+	Description string
172
+
173
+	// HTTPStatusCode provides the http status code that is associated with
174
+	// this error condition.
175
+	HTTPStatusCode int
176
+}
177
+
178
+// ParseErrorCode returns the value by the string error code.
179
+// `ErrorCodeUnknown` will be returned if the error is not known.
180
+func ParseErrorCode(value string) ErrorCode {
181
+	ed, ok := idToDescriptors[value]
182
+	if ok {
183
+		return ed.Code
184
+	}
185
+
186
+	return ErrorCodeUnknown
187
+}
188
+
189
+// Errors provides the envelope for multiple errors and a few sugar methods
190
+// for use within the application.
191
+type Errors []error
192
+
193
+var _ error = Errors{}
194
+
195
+func (errs Errors) Error() string {
196
+	switch len(errs) {
197
+	case 0:
198
+		return "<nil>"
199
+	case 1:
200
+		return errs[0].Error()
201
+	default:
202
+		msg := "errors:\n"
203
+		for _, err := range errs {
204
+			msg += err.Error() + "\n"
205
+		}
206
+		return msg
207
+	}
208
+}
209
+
210
+// Len returns the current number of errors.
211
+func (errs Errors) Len() int {
212
+	return len(errs)
213
+}
214
+
215
+// MarshalJSON converts slice of error, ErrorCode or Error into a
216
+// slice of Error - then serializes
217
+func (errs Errors) MarshalJSON() ([]byte, error) {
218
+	var tmpErrs struct {
219
+		Errors []Error `json:"errors,omitempty"`
220
+	}
221
+
222
+	for _, daErr := range errs {
223
+		var err Error
224
+
225
+		switch daErr := daErr.(type) {
226
+		case ErrorCode:
227
+			err = daErr.WithDetail(nil)
228
+		case Error:
229
+			err = daErr
230
+		default:
231
+			err = ErrorCodeUnknown.WithDetail(daErr)
232
+
233
+		}
234
+
235
+		// If the Error struct was setup and they forgot to set the
236
+		// Message field (meaning its "") then grab it from the ErrCode
237
+		msg := err.Message
238
+		if msg == "" {
239
+			msg = err.Code.Message()
240
+		}
241
+
242
+		tmpErrs.Errors = append(tmpErrs.Errors, Error{
243
+			Code:    err.Code,
244
+			Message: msg,
245
+			Detail:  err.Detail,
246
+		})
247
+	}
248
+
249
+	return json.Marshal(tmpErrs)
250
+}
251
+
252
+// UnmarshalJSON deserializes []Error and then converts it into slice of
253
+// Error or ErrorCode
254
+func (errs *Errors) UnmarshalJSON(data []byte) error {
255
+	var tmpErrs struct {
256
+		Errors []Error
257
+	}
258
+
259
+	if err := json.Unmarshal(data, &tmpErrs); err != nil {
260
+		return err
261
+	}
262
+
263
+	var newErrs Errors
264
+	for _, daErr := range tmpErrs.Errors {
265
+		// If Message is empty or exactly matches the Code's message string
266
+		// then just use the Code, no need for a full Error struct
267
+		if daErr.Detail == nil && (daErr.Message == "" || daErr.Message == daErr.Code.Message()) {
268
+			// Error's w/o details get converted to ErrorCode
269
+			newErrs = append(newErrs, daErr.Code)
270
+		} else {
271
+			// Error's w/ details are untouched
272
+			newErrs = append(newErrs, Error{
273
+				Code:    daErr.Code,
274
+				Message: daErr.Message,
275
+				Detail:  daErr.Detail,
276
+			})
277
+		}
278
+	}
279
+
280
+	*errs = newErrs
281
+	return nil
282
+}
0 283
new file mode 100644
... ...
@@ -0,0 +1,154 @@
0
+/*
1
+   Copyright The containerd Authors.
2
+
3
+   Licensed under the Apache License, Version 2.0 (the "License");
4
+   you may not use this file except in compliance with the License.
5
+   You may obtain a copy of the License at
6
+
7
+       http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+   Unless required by applicable law or agreed to in writing, software
10
+   distributed under the License is distributed on an "AS IS" BASIS,
11
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+   See the License for the specific language governing permissions and
13
+   limitations under the License.
14
+*/
15
+
16
+package docker
17
+
18
+import (
19
+	"fmt"
20
+	"net/http"
21
+	"sort"
22
+	"sync"
23
+)
24
+
25
+var (
26
+	errorCodeToDescriptors = map[ErrorCode]ErrorDescriptor{}
27
+	idToDescriptors        = map[string]ErrorDescriptor{}
28
+	groupToDescriptors     = map[string][]ErrorDescriptor{}
29
+)
30
+
31
+var (
32
+	// ErrorCodeUnknown is a generic error that can be used as a last
33
+	// resort if there is no situation-specific error message that can be used
34
+	ErrorCodeUnknown = Register("errcode", ErrorDescriptor{
35
+		Value:   "UNKNOWN",
36
+		Message: "unknown error",
37
+		Description: `Generic error returned when the error does not have an
38
+			                                            API classification.`,
39
+		HTTPStatusCode: http.StatusInternalServerError,
40
+	})
41
+
42
+	// ErrorCodeUnsupported is returned when an operation is not supported.
43
+	ErrorCodeUnsupported = Register("errcode", ErrorDescriptor{
44
+		Value:   "UNSUPPORTED",
45
+		Message: "The operation is unsupported.",
46
+		Description: `The operation was unsupported due to a missing
47
+		implementation or invalid set of parameters.`,
48
+		HTTPStatusCode: http.StatusMethodNotAllowed,
49
+	})
50
+
51
+	// ErrorCodeUnauthorized is returned if a request requires
52
+	// authentication.
53
+	ErrorCodeUnauthorized = Register("errcode", ErrorDescriptor{
54
+		Value:   "UNAUTHORIZED",
55
+		Message: "authentication required",
56
+		Description: `The access controller was unable to authenticate
57
+		the client. Often this will be accompanied by a
58
+		Www-Authenticate HTTP response header indicating how to
59
+		authenticate.`,
60
+		HTTPStatusCode: http.StatusUnauthorized,
61
+	})
62
+
63
+	// ErrorCodeDenied is returned if a client does not have sufficient
64
+	// permission to perform an action.
65
+	ErrorCodeDenied = Register("errcode", ErrorDescriptor{
66
+		Value:   "DENIED",
67
+		Message: "requested access to the resource is denied",
68
+		Description: `The access controller denied access for the
69
+		operation on a resource.`,
70
+		HTTPStatusCode: http.StatusForbidden,
71
+	})
72
+
73
+	// ErrorCodeUnavailable provides a common error to report unavailability
74
+	// of a service or endpoint.
75
+	ErrorCodeUnavailable = Register("errcode", ErrorDescriptor{
76
+		Value:          "UNAVAILABLE",
77
+		Message:        "service unavailable",
78
+		Description:    "Returned when a service is not available",
79
+		HTTPStatusCode: http.StatusServiceUnavailable,
80
+	})
81
+
82
+	// ErrorCodeTooManyRequests is returned if a client attempts too many
83
+	// times to contact a service endpoint.
84
+	ErrorCodeTooManyRequests = Register("errcode", ErrorDescriptor{
85
+		Value:   "TOOMANYREQUESTS",
86
+		Message: "too many requests",
87
+		Description: `Returned when a client attempts to contact a
88
+		service too many times`,
89
+		HTTPStatusCode: http.StatusTooManyRequests,
90
+	})
91
+)
92
+
93
+var nextCode = 1000
94
+var registerLock sync.Mutex
95
+
96
+// Register will make the passed-in error known to the environment and
97
+// return a new ErrorCode
98
+func Register(group string, descriptor ErrorDescriptor) ErrorCode {
99
+	registerLock.Lock()
100
+	defer registerLock.Unlock()
101
+
102
+	descriptor.Code = ErrorCode(nextCode)
103
+
104
+	if _, ok := idToDescriptors[descriptor.Value]; ok {
105
+		panic(fmt.Sprintf("ErrorValue %q is already registered", descriptor.Value))
106
+	}
107
+	if _, ok := errorCodeToDescriptors[descriptor.Code]; ok {
108
+		panic(fmt.Sprintf("ErrorCode %v is already registered", descriptor.Code))
109
+	}
110
+
111
+	groupToDescriptors[group] = append(groupToDescriptors[group], descriptor)
112
+	errorCodeToDescriptors[descriptor.Code] = descriptor
113
+	idToDescriptors[descriptor.Value] = descriptor
114
+
115
+	nextCode++
116
+	return descriptor.Code
117
+}
118
+
119
+type byValue []ErrorDescriptor
120
+
121
+func (a byValue) Len() int           { return len(a) }
122
+func (a byValue) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
123
+func (a byValue) Less(i, j int) bool { return a[i].Value < a[j].Value }
124
+
125
+// GetGroupNames returns the list of Error group names that are registered
126
+func GetGroupNames() []string {
127
+	keys := []string{}
128
+
129
+	for k := range groupToDescriptors {
130
+		keys = append(keys, k)
131
+	}
132
+	sort.Strings(keys)
133
+	return keys
134
+}
135
+
136
+// GetErrorCodeGroup returns the named group of error descriptors
137
+func GetErrorCodeGroup(name string) []ErrorDescriptor {
138
+	desc := groupToDescriptors[name]
139
+	sort.Sort(byValue(desc))
140
+	return desc
141
+}
142
+
143
+// GetErrorAllDescriptors returns a slice of all ErrorDescriptors that are
144
+// registered, irrespective of what group they're in
145
+func GetErrorAllDescriptors() []ErrorDescriptor {
146
+	result := []ErrorDescriptor{}
147
+
148
+	for _, group := range GetGroupNames() {
149
+		result = append(result, GetErrorCodeGroup(group)...)
150
+	}
151
+	sort.Sort(byValue(result))
152
+	return result
153
+}
... ...
@@ -29,7 +29,6 @@ import (
29 29
 	"github.com/containerd/containerd/errdefs"
30 30
 	"github.com/containerd/containerd/images"
31 31
 	"github.com/containerd/containerd/log"
32
-	"github.com/docker/distribution/registry/api/errcode"
33 32
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
34 33
 	"github.com/pkg/errors"
35 34
 )
... ...
@@ -160,7 +159,7 @@ func (r dockerFetcher) open(ctx context.Context, req *request, mediatype string,
160 160
 		if resp.StatusCode == http.StatusNotFound {
161 161
 			return nil, errors.Wrapf(errdefs.ErrNotFound, "content at %v not found", req.String())
162 162
 		}
163
-		var registryErr errcode.Errors
163
+		var registryErr Errors
164 164
 		if err := json.NewDecoder(resp.Body).Decode(&registryErr); err != nil || registryErr.Len() < 1 {
165 165
 			return nil, errors.Errorf("unexpected status code %v: %v", req.String(), resp.Status)
166 166
 		}
... ...
@@ -91,9 +91,12 @@ func (t *Task) PID() uint32 {
91 91
 
92 92
 // Delete the task and return the exit status
93 93
 func (t *Task) Delete(ctx context.Context) (*runtime.Exit, error) {
94
-	rsp, err := t.shim.Delete(ctx, empty)
95
-	if err != nil && !errdefs.IsNotFound(err) {
96
-		return nil, errdefs.FromGRPC(err)
94
+	rsp, shimErr := t.shim.Delete(ctx, empty)
95
+	if shimErr != nil {
96
+		shimErr = errdefs.FromGRPC(shimErr)
97
+		if !errdefs.IsNotFound(shimErr) {
98
+			return nil, shimErr
99
+		}
97 100
 	}
98 101
 	t.tasks.Delete(ctx, t.id)
99 102
 	if err := t.shim.KillShim(ctx); err != nil {
... ...
@@ -102,6 +105,9 @@ func (t *Task) Delete(ctx context.Context) (*runtime.Exit, error) {
102 102
 	if err := t.bundle.Delete(); err != nil {
103 103
 		log.G(ctx).WithError(err).Error("failed to delete bundle")
104 104
 	}
105
+	if shimErr != nil {
106
+		return nil, shimErr
107
+	}
105 108
 	t.events.Publish(ctx, runtime.TaskDeleteEventTopic, &eventstypes.TaskDelete{
106 109
 		ContainerID: t.id,
107 110
 		ExitStatus:  rsp.ExitStatus,
... ...
@@ -55,7 +55,7 @@ var (
55 55
 	empty   = &ptypes.Empty{}
56 56
 	bufPool = sync.Pool{
57 57
 		New: func() interface{} {
58
-			buffer := make([]byte, 32<<10)
58
+			buffer := make([]byte, 4096)
59 59
 			return &buffer
60 60
 		},
61 61
 	}
... ...
@@ -217,7 +217,7 @@ func (s *Service) Delete(ctx context.Context, r *ptypes.Empty) (*shimapi.DeleteR
217 217
 		return nil, err
218 218
 	}
219 219
 	if err := p.Delete(ctx); err != nil {
220
-		return nil, err
220
+		return nil, errdefs.ToGRPC(err)
221 221
 	}
222 222
 	s.mu.Lock()
223 223
 	delete(s.processes, s.id)
... ...
@@ -240,7 +240,7 @@ func (s *Service) DeleteProcess(ctx context.Context, r *shimapi.DeleteProcessReq
240 240
 		return nil, err
241 241
 	}
242 242
 	if err := p.Delete(ctx); err != nil {
243
-		return nil, err
243
+		return nil, errdefs.ToGRPC(err)
244 244
 	}
245 245
 	s.mu.Lock()
246 246
 	delete(s.processes, r.ID)
... ...
@@ -55,6 +55,7 @@ func (p *linuxPlatform) CopyConsole(ctx context.Context, console console.Console
55 55
 			io.CopyBuffer(epollConsole, in, *bp)
56 56
 			// we need to shutdown epollConsole when pipe broken
57 57
 			epollConsole.Shutdown(p.epoller.CloseConsole)
58
+			epollConsole.Close()
58 59
 		}()
59 60
 	}
60 61
 
... ...
@@ -73,9 +74,8 @@ func (p *linuxPlatform) CopyConsole(ctx context.Context, console console.Console
73 73
 		p := bufPool.Get().(*[]byte)
74 74
 		defer bufPool.Put(p)
75 75
 		io.CopyBuffer(outw, epollConsole, *p)
76
-		epollConsole.Close()
77
-		outr.Close()
78 76
 		outw.Close()
77
+		outr.Close()
79 78
 		wg.Done()
80 79
 	}()
81 80
 	cwg.Wait()
... ...
@@ -1,6 +1,6 @@
1 1
 github.com/containerd/go-runc e029b79d8cda8374981c64eba71f28ec38e5526f
2 2
 github.com/containerd/console 0650fd9eeb50bab4fc99dceb9f2e14cf58f36e7f
3
-github.com/containerd/cgroups c4b9ac5c7601384c965b9646fc515884e091ebb9
3
+github.com/containerd/cgroups abd0b19954a6b05e0963f48427062d1481b7faad
4 4
 github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
5 5
 github.com/containerd/fifo bda0ff6ed73c67bfb5e62bc9c697f146b7fd7f13
6 6
 github.com/containerd/btrfs af5082808c833de0e79c1e72eea9fea239364877
... ...
@@ -20,7 +20,7 @@ github.com/gogo/protobuf v1.2.1
20 20
 github.com/gogo/googleapis v1.2.0
21 21
 github.com/golang/protobuf v1.2.0
22 22
 github.com/opencontainers/runtime-spec 29686dbc5559d93fb1ef402eeda3e35c38d75af4 # v1.0.1-59-g29686db
23
-github.com/opencontainers/runc 3e425f80a8c931f88e6d94a8c831b9d5aa481657 # v1.0.0-rc8+ CVE-2019-16884
23
+github.com/opencontainers/runc d736ef14f0288d6993a1845745d6756cfc9ddd5a # v1.0.0-rc9
24 24
 github.com/konsorten/go-windows-terminal-sequences v1.0.1
25 25
 github.com/sirupsen/logrus v1.4.1
26 26
 github.com/urfave/cli v1.22.0
... ...
@@ -51,8 +51,8 @@ github.com/cpuguy83/go-md2man v1.0.10
51 51
 github.com/russross/blackfriday v1.5.2
52 52
 
53 53
 # cri dependencies
54
-github.com/containerd/cri 5d49e7e51b43e36a6b9c4386257c7d08c602237f # release/1.3
55
-github.com/containerd/go-cni 49fbd9b210f3c8ee3b7fd3cd797aabaf364627c1
54
+github.com/containerd/cri 0ebf032aac5f6029f95a94e42161e9db7a7e84df # release/1.3+
55
+github.com/containerd/go-cni 0d360c50b10b350b6bb23863fd4dfb1c232b01c9
56 56
 github.com/containernetworking/cni v0.7.1
57 57
 github.com/containernetworking/plugins v0.7.6
58 58
 github.com/davecgh/go-spew v1.1.1
... ...
@@ -16,6 +16,8 @@
16 16
 
17 17
 package version
18 18
 
19
+import "runtime"
20
+
19 21
 var (
20 22
 	// Package is filled at linking time
21 23
 	Package = "github.com/containerd/containerd"
... ...
@@ -26,4 +28,7 @@ var (
26 26
 	// Revision is filled with the VCS (e.g. git) revision being used to build
27 27
 	// the program at linking time.
28 28
 	Revision = ""
29
+
30
+	// GoVersion is Go tree's version.
31
+	GoVersion = runtime.Version()
29 32
 )
... ...
@@ -6,6 +6,8 @@ import (
6 6
 	"fmt"
7 7
 	"io/ioutil"
8 8
 	"os"
9
+
10
+	"github.com/opencontainers/runc/libcontainer/utils"
9 11
 )
10 12
 
11 13
 // IsEnabled returns true if apparmor is enabled for the host.
... ...
@@ -19,7 +21,7 @@ func IsEnabled() bool {
19 19
 	return false
20 20
 }
21 21
 
22
-func setprocattr(attr, value string) error {
22
+func setProcAttr(attr, value string) error {
23 23
 	// Under AppArmor you can only change your own attr, so use /proc/self/
24 24
 	// instead of /proc/<tid>/ like libapparmor does
25 25
 	path := fmt.Sprintf("/proc/self/attr/%s", attr)
... ...
@@ -30,6 +32,10 @@ func setprocattr(attr, value string) error {
30 30
 	}
31 31
 	defer f.Close()
32 32
 
33
+	if err := utils.EnsureProcHandle(f); err != nil {
34
+		return err
35
+	}
36
+
33 37
 	_, err = fmt.Fprintf(f, "%s", value)
34 38
 	return err
35 39
 }
... ...
@@ -37,7 +43,7 @@ func setprocattr(attr, value string) error {
37 37
 // changeOnExec reimplements aa_change_onexec from libapparmor in Go
38 38
 func changeOnExec(name string) error {
39 39
 	value := "exec " + name
40
-	if err := setprocattr("exec", value); err != nil {
40
+	if err := setProcAttr("exec", value); err != nil {
41 41
 		return fmt.Errorf("apparmor failed to apply profile: %s", err)
42 42
 	}
43 43
 	return nil
... ...
@@ -44,6 +44,7 @@ const (
44 44
 	Trap
45 45
 	Allow
46 46
 	Trace
47
+	Log
47 48
 )
48 49
 
49 50
 // Operator is a comparison operator to be used when matching syscall arguments in Seccomp
50 51
new file mode 100644
... ...
@@ -0,0 +1,93 @@
0
+// +build linux
1
+
2
+package utils
3
+
4
+/*
5
+ * Copyright 2016, 2017 SUSE LLC
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the "License");
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ *     http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ */
19
+
20
+import (
21
+	"fmt"
22
+	"os"
23
+
24
+	"golang.org/x/sys/unix"
25
+)
26
+
27
+// MaxSendfdLen is the maximum length of the name of a file descriptor being
28
+// sent using SendFd. The name of the file handle returned by RecvFd will never
29
+// be larger than this value.
30
+const MaxNameLen = 4096
31
+
32
+// oobSpace is the size of the oob slice required to store a single FD. Note
33
+// that unix.UnixRights appears to make the assumption that fd is always int32,
34
+// so sizeof(fd) = 4.
35
+var oobSpace = unix.CmsgSpace(4)
36
+
37
+// RecvFd waits for a file descriptor to be sent over the given AF_UNIX
38
+// socket. The file name of the remote file descriptor will be recreated
39
+// locally (it is sent as non-auxiliary data in the same payload).
40
+func RecvFd(socket *os.File) (*os.File, error) {
41
+	// For some reason, unix.Recvmsg uses the length rather than the capacity
42
+	// when passing the msg_controllen and other attributes to recvmsg.  So we
43
+	// have to actually set the length.
44
+	name := make([]byte, MaxNameLen)
45
+	oob := make([]byte, oobSpace)
46
+
47
+	sockfd := socket.Fd()
48
+	n, oobn, _, _, err := unix.Recvmsg(int(sockfd), name, oob, 0)
49
+	if err != nil {
50
+		return nil, err
51
+	}
52
+
53
+	if n >= MaxNameLen || oobn != oobSpace {
54
+		return nil, fmt.Errorf("recvfd: incorrect number of bytes read (n=%d oobn=%d)", n, oobn)
55
+	}
56
+
57
+	// Truncate.
58
+	name = name[:n]
59
+	oob = oob[:oobn]
60
+
61
+	scms, err := unix.ParseSocketControlMessage(oob)
62
+	if err != nil {
63
+		return nil, err
64
+	}
65
+	if len(scms) != 1 {
66
+		return nil, fmt.Errorf("recvfd: number of SCMs is not 1: %d", len(scms))
67
+	}
68
+	scm := scms[0]
69
+
70
+	fds, err := unix.ParseUnixRights(&scm)
71
+	if err != nil {
72
+		return nil, err
73
+	}
74
+	if len(fds) != 1 {
75
+		return nil, fmt.Errorf("recvfd: number of fds is not 1: %d", len(fds))
76
+	}
77
+	fd := uintptr(fds[0])
78
+
79
+	return os.NewFile(fd, string(name)), nil
80
+}
81
+
82
+// SendFd sends a file descriptor over the given AF_UNIX socket. In
83
+// addition, the file.Name() of the given file will also be sent as
84
+// non-auxiliary data in the same payload (allowing to send contextual
85
+// information for a file descriptor).
86
+func SendFd(socket *os.File, name string, fd uintptr) error {
87
+	if len(name) >= MaxNameLen {
88
+		return fmt.Errorf("sendfd: filename too long: %s", name)
89
+	}
90
+	oob := unix.UnixRights(int(fd))
91
+	return unix.Sendmsg(int(socket.Fd()), []byte(name), oob, nil, 0)
92
+}
0 93
new file mode 100644
... ...
@@ -0,0 +1,112 @@
0
+package utils
1
+
2
+import (
3
+	"encoding/json"
4
+	"io"
5
+	"os"
6
+	"path/filepath"
7
+	"strings"
8
+	"unsafe"
9
+
10
+	"golang.org/x/sys/unix"
11
+)
12
+
13
+const (
14
+	exitSignalOffset = 128
15
+)
16
+
17
+// ResolveRootfs ensures that the current working directory is
18
+// not a symlink and returns the absolute path to the rootfs
19
+func ResolveRootfs(uncleanRootfs string) (string, error) {
20
+	rootfs, err := filepath.Abs(uncleanRootfs)
21
+	if err != nil {
22
+		return "", err
23
+	}
24
+	return filepath.EvalSymlinks(rootfs)
25
+}
26
+
27
+// ExitStatus returns the correct exit status for a process based on if it
28
+// was signaled or exited cleanly
29
+func ExitStatus(status unix.WaitStatus) int {
30
+	if status.Signaled() {
31
+		return exitSignalOffset + int(status.Signal())
32
+	}
33
+	return status.ExitStatus()
34
+}
35
+
36
+// WriteJSON writes the provided struct v to w using standard json marshaling
37
+func WriteJSON(w io.Writer, v interface{}) error {
38
+	data, err := json.Marshal(v)
39
+	if err != nil {
40
+		return err
41
+	}
42
+	_, err = w.Write(data)
43
+	return err
44
+}
45
+
46
+// CleanPath makes a path safe for use with filepath.Join. This is done by not
47
+// only cleaning the path, but also (if the path is relative) adding a leading
48
+// '/' and cleaning it (then removing the leading '/'). This ensures that a
49
+// path resulting from prepending another path will always resolve to lexically
50
+// be a subdirectory of the prefixed path. This is all done lexically, so paths
51
+// that include symlinks won't be safe as a result of using CleanPath.
52
+func CleanPath(path string) string {
53
+	// Deal with empty strings nicely.
54
+	if path == "" {
55
+		return ""
56
+	}
57
+
58
+	// Ensure that all paths are cleaned (especially problematic ones like
59
+	// "/../../../../../" which can cause lots of issues).
60
+	path = filepath.Clean(path)
61
+
62
+	// If the path isn't absolute, we need to do more processing to fix paths
63
+	// such as "../../../../<etc>/some/path". We also shouldn't convert absolute
64
+	// paths to relative ones.
65
+	if !filepath.IsAbs(path) {
66
+		path = filepath.Clean(string(os.PathSeparator) + path)
67
+		// This can't fail, as (by definition) all paths are relative to root.
68
+		path, _ = filepath.Rel(string(os.PathSeparator), path)
69
+	}
70
+
71
+	// Clean the path again for good measure.
72
+	return filepath.Clean(path)
73
+}
74
+
75
+// SearchLabels searches a list of key-value pairs for the provided key and
76
+// returns the corresponding value. The pairs must be separated with '='.
77
+func SearchLabels(labels []string, query string) string {
78
+	for _, l := range labels {
79
+		parts := strings.SplitN(l, "=", 2)
80
+		if len(parts) < 2 {
81
+			continue
82
+		}
83
+		if parts[0] == query {
84
+			return parts[1]
85
+		}
86
+	}
87
+	return ""
88
+}
89
+
90
+// Annotations returns the bundle path and user defined annotations from the
91
+// libcontainer state.  We need to remove the bundle because that is a label
92
+// added by libcontainer.
93
+func Annotations(labels []string) (bundle string, userAnnotations map[string]string) {
94
+	userAnnotations = make(map[string]string)
95
+	for _, l := range labels {
96
+		parts := strings.SplitN(l, "=", 2)
97
+		if len(parts) < 2 {
98
+			continue
99
+		}
100
+		if parts[0] == "bundle" {
101
+			bundle = parts[1]
102
+		} else {
103
+			userAnnotations[parts[0]] = parts[1]
104
+		}
105
+	}
106
+	return
107
+}
108
+
109
+func GetIntSize() int {
110
+	return int(unsafe.Sizeof(1))
111
+}
0 112
new file mode 100644
... ...
@@ -0,0 +1,68 @@
0
+// +build !windows
1
+
2
+package utils
3
+
4
+import (
5
+	"fmt"
6
+	"os"
7
+	"strconv"
8
+
9
+	"golang.org/x/sys/unix"
10
+)
11
+
12
+// EnsureProcHandle returns whether or not the given file handle is on procfs.
13
+func EnsureProcHandle(fh *os.File) error {
14
+	var buf unix.Statfs_t
15
+	if err := unix.Fstatfs(int(fh.Fd()), &buf); err != nil {
16
+		return fmt.Errorf("ensure %s is on procfs: %v", fh.Name(), err)
17
+	}
18
+	if buf.Type != unix.PROC_SUPER_MAGIC {
19
+		return fmt.Errorf("%s is not on procfs", fh.Name())
20
+	}
21
+	return nil
22
+}
23
+
24
+// CloseExecFrom applies O_CLOEXEC to all file descriptors currently open for
25
+// the process (except for those below the given fd value).
26
+func CloseExecFrom(minFd int) error {
27
+	fdDir, err := os.Open("/proc/self/fd")
28
+	if err != nil {
29
+		return err
30
+	}
31
+	defer fdDir.Close()
32
+
33
+	if err := EnsureProcHandle(fdDir); err != nil {
34
+		return err
35
+	}
36
+
37
+	fdList, err := fdDir.Readdirnames(-1)
38
+	if err != nil {
39
+		return err
40
+	}
41
+	for _, fdStr := range fdList {
42
+		fd, err := strconv.Atoi(fdStr)
43
+		// Ignore non-numeric file names.
44
+		if err != nil {
45
+			continue
46
+		}
47
+		// Ignore descriptors lower than our specified minimum.
48
+		if fd < minFd {
49
+			continue
50
+		}
51
+		// Intentionally ignore errors from unix.CloseOnExec -- the cases where
52
+		// this might fail are basically file descriptors that have already
53
+		// been closed (including and especially the one that was created when
54
+		// ioutil.ReadDir did the "opendir" syscall).
55
+		unix.CloseOnExec(fd)
56
+	}
57
+	return nil
58
+}
59
+
60
+// NewSockPair returns a new unix socket pair
61
+func NewSockPair(name string) (parent *os.File, child *os.File, err error) {
62
+	fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
63
+	if err != nil {
64
+		return nil, nil, err
65
+	}
66
+	return os.NewFile(uintptr(fds[1]), name+"-p"), os.NewFile(uintptr(fds[0]), name+"-c"), nil
67
+}
... ...
@@ -6,7 +6,7 @@ github.com/opencontainers/runtime-spec  29686dbc5559d93fb1ef402eeda3e35c38d75af4
6 6
 # Core libcontainer functionality.
7 7
 github.com/checkpoint-restore/go-criu   17b0214f6c48980c45dc47ecb0cfd6d9e02df723 # v3.11
8 8
 github.com/mrunalp/fileutils            7d4729fb36185a7c1719923406c9d40e54fb93c7
9
-github.com/opencontainers/selinux       3a1f366feb7aecbf7a0e71ac4cea88b31597de9e # v1.2.2
9
+github.com/opencontainers/selinux       5215b1806f52b1fcc2070a8826c542c9d33cd3cf # v1.3.0 (+ CVE-2019-16884)
10 10
 github.com/seccomp/libseccomp-golang    689e3c1541a84461afc49c1c87352a6cedf72e9c # v0.9.1
11 11
 github.com/sirupsen/logrus              8bdbc7bcc01dcbb8ec23dc8a28e332258d25251f # v1.4.1
12 12
 github.com/syndtr/gocapability          d98352740cb2c55f81556b63d4a1ec64c5a319c2