diff --git a/client_unix.go b/client_unix.go index f352eb4d3..d5a9f63ec 100644 --- a/client_unix.go +++ b/client_unix.go @@ -9,9 +9,6 @@ import ( "time" ) -// DefaultAddress is the default unix socket address -const DefaultAddress = "/run/containerd/containerd.sock" - func dialer(address string, timeout time.Duration) (net.Conn, error) { address = strings.TrimPrefix(address, "unix://") return net.DialTimeout("unix", address, timeout) diff --git a/client_windows.go b/client_windows.go index 998c15c69..548024e5b 100644 --- a/client_windows.go +++ b/client_windows.go @@ -7,9 +7,6 @@ import ( winio "github.com/Microsoft/go-winio" ) -// DefaultAddress is the default unix socket address -const DefaultAddress = `\\.\pipe\containerd-containerd` - func dialer(address string, timeout time.Duration) (net.Conn, error) { return winio.DialPipe(address, &timeout) } diff --git a/cmd/containerd/config_linux.go b/cmd/containerd/config_linux.go index 46956c524..ff2138909 100644 --- a/cmd/containerd/config_linux.go +++ b/cmd/containerd/config_linux.go @@ -1,7 +1,6 @@ package main import ( - "github.com/containerd/containerd" "github.com/containerd/containerd/server" ) @@ -9,7 +8,7 @@ func defaultConfig() *server.Config { return &server.Config{ Root: "/var/lib/containerd", GRPC: server.GRPCConfig{ - Address: containerd.DefaultAddress, + Address: server.DefaultAddress, }, Debug: server.Debug{ Level: "info", diff --git a/cmd/containerd/config_unix.go b/cmd/containerd/config_unix.go index da3b40f25..7d1263a4c 100644 --- a/cmd/containerd/config_unix.go +++ b/cmd/containerd/config_unix.go @@ -8,7 +8,7 @@ func defaultConfig() *server.Config { return &server.Config{ Root: "/var/lib/containerd", GRPC: server.GRPCConfig{ - Address: "/run/containerd/containerd.sock", + Address: server.DefaultAddress, }, Debug: server.Debug{ Level: "info", diff --git a/cmd/ctr/main.go b/cmd/ctr/main.go index f3d281d90..3fa292f76 100644 --- a/cmd/ctr/main.go +++ b/cmd/ctr/main.go @@ -5,8 +5,8 @@ import ( "os" "github.com/Sirupsen/logrus" - "github.com/containerd/containerd" "github.com/containerd/containerd/namespaces" + "github.com/containerd/containerd/server" "github.com/containerd/containerd/version" "github.com/urfave/cli" ) @@ -40,7 +40,7 @@ containerd CLI cli.StringFlag{ Name: "address, a", Usage: "address for containerd's GRPC server", - Value: containerd.DefaultAddress, + Value: server.DefaultAddress, }, cli.DurationFlag{ Name: "timeout", diff --git a/cmd/ctr/pprof.go b/cmd/ctr/pprof.go index 505e48fae..43be0a461 100644 --- a/cmd/ctr/pprof.go +++ b/cmd/ctr/pprof.go @@ -3,13 +3,12 @@ package main import ( "fmt" "io" - "net" "net/http" "os" "time" + "github.com/containerd/containerd/server" "github.com/pkg/errors" - "github.com/urfave/cli" ) @@ -25,7 +24,7 @@ var pprofCommand = cli.Command{ cli.StringFlag{ Name: "debug-socket, d", Usage: "socket path for containerd's debug server", - Value: "/run/containerd/debug.sock", + Value: server.DefaultDebugAddress, }, }, Subcommands: []cli.Command{ @@ -143,13 +142,8 @@ var pprofThreadcreateCommand = cli.Command{ }, } -func (d *pprofDialer) pprofDial(proto, addr string) (conn net.Conn, err error) { - return net.Dial(d.proto, d.addr) -} - func getPProfClient(context *cli.Context) *http.Client { - addr := context.GlobalString("debug-socket") - dialer := pprofDialer{"unix", addr} + dialer := getPProfDialer(context.GlobalString("debug-socket")) tr := &http.Transport{ Dial: dialer.pprofDial, diff --git a/cmd/ctr/pprof_unix.go b/cmd/ctr/pprof_unix.go new file mode 100644 index 000000000..009b5da5e --- /dev/null +++ b/cmd/ctr/pprof_unix.go @@ -0,0 +1,13 @@ +// +build !windows + +package main + +import "net" + +func (d *pprofDialer) pprofDial(proto, addr string) (conn net.Conn, err error) { + return net.Dial(d.proto, d.addr) +} + +func getPProfDialer(addr string) *pprofDialer { + return &pprofDialer{"unix", addr} +} diff --git a/cmd/ctr/pprof_windows.go b/cmd/ctr/pprof_windows.go new file mode 100644 index 000000000..c7bacebee --- /dev/null +++ b/cmd/ctr/pprof_windows.go @@ -0,0 +1,15 @@ +package main + +import ( + "net" + + winio "github.com/Microsoft/go-winio" +) + +func (d *pprofDialer) pprofDial(proto, addr string) (conn net.Conn, err error) { + return winio.DialPipe(d.addr, nil) +} + +func getPProfDialer(addr string) *pprofDialer { + return &pprofDialer{"winpipe", addr} +} diff --git a/cmd/ctr/run.go b/cmd/ctr/run.go index 5a4826150..bb42e9a75 100644 --- a/cmd/ctr/run.go +++ b/cmd/ctr/run.go @@ -25,7 +25,7 @@ func withEnv(context *cli.Context) containerd.SpecOpts { return func(s *specs.Spec) error { env := context.StringSlice("env") if len(env) > 0 { - s.Process.Env = append(s.Process.Env, env...) + s.Process.Env = replaceOrAppendEnvValues(s.Process.Env, env) } return nil } diff --git a/cmd/ctr/run_windows.go b/cmd/ctr/run_windows.go index be9f49720..896276531 100644 --- a/cmd/ctr/run_windows.go +++ b/cmd/ctr/run_windows.go @@ -2,22 +2,16 @@ package main import ( gocontext "context" - "encoding/json" - "fmt" - "io/ioutil" "time" "github.com/Sirupsen/logrus" "github.com/containerd/console" "github.com/containerd/containerd" - "github.com/containerd/containerd/api/services/tasks/v1" + "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/log" - "github.com/containerd/containerd/mount" - "github.com/containerd/containerd/windows" - "github.com/containerd/containerd/windows/hcs" digest "github.com/opencontainers/go-digest" - ocispec "github.com/opencontainers/image-spec/specs-go/v1" specs "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" "github.com/urfave/cli" ) @@ -25,126 +19,20 @@ const pipeRoot = `\\.\pipe` func init() { runCommand.Flags = append(runCommand.Flags, cli.StringSliceFlag{ - Name: "layers", + Name: "layer", Usage: "HCSSHIM Layers to be used", }) } -func spec(id string, config *ocispec.ImageConfig, context *cli.Context) *specs.Spec { - cmd := config.Cmd - if a := context.Args().First(); a != "" { - cmd = context.Args() - } - - var ( - // TODO: support overriding entrypoint - args = append(config.Entrypoint, cmd...) - tty = context.Bool("tty") - cwd = config.WorkingDir - ) - - if cwd == "" { - cwd = `C:\` - } - - // Some sane defaults for console - w := 80 - h := 20 - - if tty { - con := console.Current() - size, err := con.Size() - if err == nil { - w = int(size.Width) - h = int(size.Height) +func withLayers(context *cli.Context) containerd.SpecOpts { + return func(s *specs.Spec) error { + l := context.StringSlice("layer") + if l == nil { + return errors.Wrap(errdefs.ErrInvalidArgument, "base layers must be specified with `--layer`") } + s.Windows.LayerFolders = l + return nil } - - env := replaceOrAppendEnvValues(config.Env, context.StringSlice("env")) - - return &specs.Spec{ - Version: specs.Version, - Root: &specs.Root{ - Readonly: context.Bool("readonly"), - }, - Process: &specs.Process{ - Args: args, - Terminal: tty, - Cwd: cwd, - Env: env, - User: specs.User{ - Username: config.User, - }, - ConsoleSize: &specs.Box{ - Height: uint(w), - Width: uint(h), - }, - }, - Hostname: id, - } -} - -func customSpec(context *cli.Context, configPath, rootfs string) (*specs.Spec, error) { - b, err := ioutil.ReadFile(configPath) - if err != nil { - return nil, err - } - - var s specs.Spec - if err := json.Unmarshal(b, &s); err != nil { - return nil, err - } - - if rootfs != "" && s.Root.Path != rootfs { - logrus.Warnf("ignoring config Root.Path %q, setting %q forcibly", s.Root.Path, rootfs) - s.Root.Path = rootfs - } - return &s, nil -} - -func getConfig(context *cli.Context, imageConfig *ocispec.ImageConfig, rootfs string) (*specs.Spec, error) { - if config := context.String("runtime-config"); config != "" { - return customSpec(context, config, rootfs) - } - - s := spec(context.String("id"), imageConfig, context) - if rootfs != "" { - s.Root.Path = rootfs - } - - return s, nil -} - -func newContainerSpec(context *cli.Context, config *ocispec.ImageConfig, imageRef string) ([]byte, error) { - spec, err := getConfig(context, config, context.String("rootfs")) - if err != nil { - return nil, err - } - if spec.Annotations == nil { - spec.Annotations = make(map[string]string) - } - spec.Annotations["image"] = imageRef - rtSpec := windows.RuntimeSpec{ - OCISpec: *spec, - Configuration: hcs.Configuration{ - Layers: context.StringSlice("layers"), - IgnoreFlushesDuringBoot: true, - AllowUnqualifiedDNSQuery: true}, - } - return json.Marshal(rtSpec) -} - -func newCreateTaskRequest(context *cli.Context, id, tmpDir string, checkpoint *ocispec.Descriptor, mounts []mount.Mount) (*tasks.CreateTaskRequest, error) { - create := &tasks.CreateTaskRequest{ - ContainerID: id, - Terminal: context.Bool("tty"), - Stdin: fmt.Sprintf(`%s\ctr-%s-stdin`, pipeRoot, id), - Stdout: fmt.Sprintf(`%s\ctr-%s-stdout`, pipeRoot, id), - } - if !create.Terminal { - create.Stderr = fmt.Sprintf(`%s\ctr-%s-stderr`, pipeRoot, id) - } - return create, nil } func handleConsoleResize(ctx gocontext.Context, task resizer, con console.Console) error { @@ -175,7 +63,14 @@ func handleConsoleResize(ctx gocontext.Context, task resizer, con console.Consol return nil } -func withTTY() containerd.SpecOpts { +func withTTY(terminal bool) containerd.SpecOpts { + if !terminal { + return func(s *specs.Spec) error { + s.Process.Terminal = false + return nil + } + } + con := console.Current() size, err := con.Size() if err != nil { @@ -192,43 +87,37 @@ func newContainer(ctx gocontext.Context, client *containerd.Client, context *cli var ( err error - ref = context.Args().First() - id = context.Args().Get(1) - args = context.Args()[2:] - tty = context.Bool("tty") + // ref = context.Args().First() + id = context.Args().Get(1) + args = context.Args()[2:] + tty = context.Bool("tty") + labelStrings = context.StringSlice("label") ) - image, err := client.GetImage(ctx, ref) - if err != nil { - return nil, err - } + + labels := labelArgs(labelStrings) + + // TODO(mlaventure): get base image once we have a snapshotter + opts := []containerd.SpecOpts{ - containerd.WithImageConfig(ctx, image), + // TODO(mlaventure): use containerd.WithImageConfig once we have a snapshotter + withLayers(context), withEnv(context), withMounts(context), + withTTY(tty), } if len(args) > 0 { opts = append(opts, containerd.WithProcessArgs(args...)) } - if tty { - opts = append(opts, withTTY()) - } - if context.Bool("net-host") { - opts = append(opts, setHostNetworking()) - } + spec, err := containerd.GenerateSpec(opts...) if err != nil { return nil, err } - var rootfs containerd.NewContainerOpts - if context.Bool("readonly") { - rootfs = containerd.WithNewReadonlyRootFS(id, image) - } else { - rootfs = containerd.WithNewRootFS(id, image) - } + return client.NewContainer(ctx, id, containerd.WithSpec(spec), - containerd.WithImage(image), - rootfs, + containerd.WithContainerLabels(labels), + // TODO(mlaventure): containerd.WithImage(image), ) } diff --git a/container.go b/container.go index 96c4eead2..04be3ad29 100644 --- a/container.go +++ b/container.go @@ -157,7 +157,7 @@ type NewTaskOpts func(context.Context, *Client, *TaskInfo) error func (c *container) NewTask(ctx context.Context, ioCreate IOCreation, opts ...NewTaskOpts) (Task, error) { c.mu.Lock() defer c.mu.Unlock() - i, err := ioCreate() + i, err := ioCreate(c.c.ID) if err != nil { return nil, err } diff --git a/io.go b/io.go index e4fce203c..d144d2879 100644 --- a/io.go +++ b/io.go @@ -4,9 +4,7 @@ import ( "context" "fmt" "io" - "io/ioutil" "os" - "path/filepath" "sync" ) @@ -40,7 +38,7 @@ func (i *IO) Close() error { return i.closer.Close() } -type IOCreation func() (*IO, error) +type IOCreation func(id string) (*IO, error) type IOAttach func(*FIFOSet) (*IO, error) @@ -49,8 +47,8 @@ func NewIO(stdin io.Reader, stdout, stderr io.Writer) IOCreation { } func NewIOWithTerminal(stdin io.Reader, stdout, stderr io.Writer, terminal bool) IOCreation { - return func() (*IO, error) { - paths, err := NewFifos() + return func(id string) (*IO, error) { + paths, err := NewFifos(id) if err != nil { return nil, err } @@ -72,7 +70,6 @@ func NewIOWithTerminal(stdin io.Reader, stdout, stderr io.Writer, terminal bool) i.closer = closer return i, nil } - } func WithAttach(stdin io.Reader, stdout, stderr io.Writer) IOAttach { @@ -102,31 +99,13 @@ func WithAttach(stdin io.Reader, stdout, stderr io.Writer) IOAttach { // Stdio returns an IO implementation to be used for a task // that outputs the container's IO as the current processes Stdio -func Stdio() (*IO, error) { - return NewIO(os.Stdin, os.Stdout, os.Stderr)() +func Stdio(id string) (*IO, error) { + return NewIO(os.Stdin, os.Stdout, os.Stderr)(id) } // StdioTerminal will setup the IO for the task to use a terminal -func StdioTerminal() (*IO, error) { - return NewIOWithTerminal(os.Stdin, os.Stdout, os.Stderr, true)() -} - -// NewFifos returns a new set of fifos for the task -func NewFifos() (*FIFOSet, error) { - root := filepath.Join(os.TempDir(), "containerd") - if err := os.MkdirAll(root, 0700); err != nil { - return nil, err - } - dir, err := ioutil.TempDir(root, "") - if err != nil { - return nil, err - } - return &FIFOSet{ - Dir: dir, - In: filepath.Join(dir, "stdin"), - Out: filepath.Join(dir, "stdout"), - Err: filepath.Join(dir, "stderr"), - }, nil +func StdioTerminal(id string) (*IO, error) { + return NewIOWithTerminal(os.Stdin, os.Stdout, os.Stderr, true)(id) } type FIFOSet struct { diff --git a/io_unix.go b/io_unix.go index ea05f251a..cde30d452 100644 --- a/io_unix.go +++ b/io_unix.go @@ -5,12 +5,33 @@ package containerd import ( "context" "io" + "io/ioutil" + "os" + "path/filepath" "sync" "syscall" "github.com/containerd/fifo" ) +// NewFifos returns a new set of fifos for the task +func NewFifos(id string) (*FIFOSet, error) { + root := filepath.Join(os.TempDir(), "containerd") + if err := os.MkdirAll(root, 0700); err != nil { + return nil, err + } + dir, err := ioutil.TempDir(root, "") + if err != nil { + return nil, err + } + return &FIFOSet{ + Dir: dir, + In: filepath.Join(dir, id+"-stdin"), + Out: filepath.Join(dir, id+"-stdout"), + Err: filepath.Join(dir, id+"-stderr"), + }, nil +} + func copyIO(fifos *FIFOSet, ioset *ioSet, tty bool) (_ *wgCloser, err error) { var ( f io.ReadWriteCloser diff --git a/io_windows.go b/io_windows.go index 58c6a8be3..e37568c26 100644 --- a/io_windows.go +++ b/io_windows.go @@ -1,6 +1,7 @@ package containerd import ( + "fmt" "io" "net" "sync" @@ -10,8 +11,22 @@ import ( "github.com/pkg/errors" ) +const pipeRoot = `\\.\pipe` + +// NewFifos returns a new set of fifos for the task +func NewFifos(id string) (*FIFOSet, error) { + return &FIFOSet{ + In: fmt.Sprintf(`%s\ctr-%s-stdin`, pipeRoot, id), + Out: fmt.Sprintf(`%s\ctr-%s-stdout`, pipeRoot, id), + Err: fmt.Sprintf(`%s\ctr-%s-stderr`, pipeRoot, id), + }, nil +} + func copyIO(fifos *FIFOSet, ioset *ioSet, tty bool) (_ *wgCloser, err error) { - var wg sync.WaitGroup + var ( + wg sync.WaitGroup + set []io.Closer + ) if fifos.In != "" { l, err := winio.ListenPipe(fifos.In, nil) @@ -23,6 +38,7 @@ func copyIO(fifos *FIFOSet, ioset *ioSet, tty bool) (_ *wgCloser, err error) { l.Close() } }(l) + set = append(set, l) go func() { c, err := l.Accept() @@ -46,6 +62,7 @@ func copyIO(fifos *FIFOSet, ioset *ioSet, tty bool) (_ *wgCloser, err error) { l.Close() } }(l) + set = append(set, l) wg.Add(1) go func() { @@ -71,6 +88,7 @@ func copyIO(fifos *FIFOSet, ioset *ioSet, tty bool) (_ *wgCloser, err error) { l.Close() } }(l) + set = append(set, l) wg.Add(1) go func() { @@ -89,5 +107,11 @@ func copyIO(fifos *FIFOSet, ioset *ioSet, tty bool) (_ *wgCloser, err error) { return &wgCloser{ wg: &wg, dir: fifos.Dir, + set: set, + cancel: func() { + for _, l := range set { + l.Close() + } + }, }, nil } diff --git a/server/server_linux.go b/server/server_linux.go index ad63f49ac..58001cca0 100644 --- a/server/server_linux.go +++ b/server/server_linux.go @@ -8,6 +8,13 @@ import ( "github.com/containerd/containerd/sys" ) +const ( + // DefaultAddress is the default unix socket address + DefaultAddress = "/run/containerd/containerd.sock" + // DefaultDebuggAddress is the default unix socket address for pprof data + DefaultDebugAddress = "/run/containerd/debug.sock" +) + // apply sets config settings on the server process func apply(ctx context.Context, config *Config) error { if config.Subreaper { diff --git a/server/server_unsupported.go b/server/server_unsupported.go index da43ff49d..197bfa029 100644 --- a/server/server_unsupported.go +++ b/server/server_unsupported.go @@ -1,9 +1,16 @@ -// +build !linux +// +build !linux,!windows package server import "context" +const ( + // DefaultAddress is the default unix socket address + DefaultAddress = "/run/containerd/containerd.sock" + // DefaultDebuggAddress is the default unix socket address for pprof data + DefaultDebugAddress = "/run/containerd/debug.sock" +) + func apply(_ context.Context, _ *Config) error { return nil } diff --git a/server/server_windows.go b/server/server_windows.go new file mode 100644 index 000000000..0fb9f64be --- /dev/null +++ b/server/server_windows.go @@ -0,0 +1,16 @@ +// +build windows + +package server + +import "context" + +const ( + // DefaultAddress is the default winpipe address + DefaultAddress = `\\.\pipe\containerd-containerd` + // DefaultDebugAddress is the default winpipe address for pprof data + DefaultDebugAddress = `\\.\pipe\containerd-debug` +) + +func apply(_ context.Context, _ *Config) error { + return nil +} diff --git a/spec_windows.go b/spec_windows.go index 9341384b1..7e0f5cfd8 100644 --- a/spec_windows.go +++ b/spec_windows.go @@ -12,18 +12,23 @@ import ( specs "github.com/opencontainers/runtime-spec/specs-go" ) -const pipeRoot = `\\.\pipe` - func createDefaultSpec() (*specs.Spec, error) { return &specs.Spec{ Version: specs.Version, Root: &specs.Root{}, Process: &specs.Process{ + Cwd: `C:\`, ConsoleSize: &specs.Box{ Width: 80, Height: 20, }, }, + Windows: &specs.Windows{ + IgnoreFlushesDuringBoot: true, + Network: &specs.WindowsNetwork{ + AllowUnqualifiedDNSQuery: true, + }, + }, }, nil } diff --git a/task.go b/task.go index 036f89d5e..a780ed8bd 100644 --- a/task.go +++ b/task.go @@ -204,7 +204,7 @@ func (t *task) Exec(ctx context.Context, id string, spec *specs.Process, ioCreat if id == "" { return nil, errors.Wrapf(errdefs.ErrInvalidArgument, "exec id must not be empty") } - i, err := ioCreate() + i, err := ioCreate(id) if err != nil { return nil, err }