Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
| ... | ... |
@@ -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(®istryErr); 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 |
| 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 |