mirror of https://github.com/docker/docs.git
Merge
This commit is contained in:
commit
2d5a1abf79
|
@ -16,6 +16,12 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var sysInitPath string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
sysInitPath = SelfPath()
|
||||||
|
}
|
||||||
|
|
||||||
type Container struct {
|
type Container struct {
|
||||||
Id string
|
Id string
|
||||||
Root string
|
Root string
|
||||||
|
@ -29,6 +35,7 @@ type Container struct {
|
||||||
Filesystem *Filesystem
|
Filesystem *Filesystem
|
||||||
State *State
|
State *State
|
||||||
|
|
||||||
|
SysInitPath string
|
||||||
lxcConfigPath string
|
lxcConfigPath string
|
||||||
cmd *exec.Cmd
|
cmd *exec.Cmd
|
||||||
stdout *writeBroadcaster
|
stdout *writeBroadcaster
|
||||||
|
@ -58,6 +65,7 @@ func createContainer(id string, root string, command string, args []string, laye
|
||||||
Filesystem: newFilesystem(path.Join(root, "rootfs"), path.Join(root, "rw"), layers),
|
Filesystem: newFilesystem(path.Join(root, "rootfs"), path.Join(root, "rw"), layers),
|
||||||
State: newState(),
|
State: newState(),
|
||||||
|
|
||||||
|
SysInitPath: sysInitPath,
|
||||||
lxcConfigPath: path.Join(root, "config.lxc"),
|
lxcConfigPath: path.Join(root, "config.lxc"),
|
||||||
stdout: newWriteBroadcaster(),
|
stdout: newWriteBroadcaster(),
|
||||||
stderr: newWriteBroadcaster(),
|
stderr: newWriteBroadcaster(),
|
||||||
|
@ -261,6 +269,7 @@ func (container *Container) Start() error {
|
||||||
"-n", container.Id,
|
"-n", container.Id,
|
||||||
"-f", container.lxcConfigPath,
|
"-f", container.lxcConfigPath,
|
||||||
"--",
|
"--",
|
||||||
|
"/sbin/init",
|
||||||
container.Path,
|
container.Path,
|
||||||
}
|
}
|
||||||
params = append(params, container.Args...)
|
params = append(params, container.Args...)
|
||||||
|
|
|
@ -6,6 +6,13 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Hack to run sys init during unit testing
|
||||||
|
func init() {
|
||||||
|
if SelfPath() == "/sbin/init" {
|
||||||
|
SysInit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func newTestDocker() (*Docker, error) {
|
func newTestDocker() (*Docker, error) {
|
||||||
root, err := ioutil.TempDir("", "docker-test")
|
root, err := ioutil.TempDir("", "docker-test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,26 +1,26 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/dotcloud/docker"
|
|
||||||
"github.com/dotcloud/docker/rcli"
|
|
||||||
"github.com/dotcloud/docker/image"
|
|
||||||
"github.com/dotcloud/docker/future"
|
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
|
||||||
"io"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"github.com/dotcloud/docker"
|
||||||
"text/tabwriter"
|
"github.com/dotcloud/docker/future"
|
||||||
"os"
|
"github.com/dotcloud/docker/image"
|
||||||
"time"
|
"github.com/dotcloud/docker/rcli"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"encoding/json"
|
|
||||||
"bytes"
|
|
||||||
"sync"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"text/tabwriter"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const VERSION = "0.0.1"
|
const VERSION = "0.0.1"
|
||||||
|
@ -176,7 +176,6 @@ func (srv *Server) CmdWrite(stdin io.ReadCloser, stdout io.Writer, args ...strin
|
||||||
return errors.New("No such container: " + name)
|
return errors.New("No such container: " + name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (srv *Server) CmdLs(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
func (srv *Server) CmdLs(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||||
cmd := rcli.Subcmd(stdout, "ls", "[OPTIONS] CONTAINER PATH", "List the contents of a container's directory")
|
cmd := rcli.Subcmd(stdout, "ls", "[OPTIONS] CONTAINER PATH", "List the contents of a container's directory")
|
||||||
if err := cmd.Parse(args); err != nil {
|
if err := cmd.Parse(args); err != nil {
|
||||||
|
@ -268,7 +267,7 @@ func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
||||||
return errors.New("No such container: " + name)
|
return errors.New("No such container: " + name)
|
||||||
}
|
}
|
||||||
if err := srv.containers.Destroy(container); err != nil {
|
if err := srv.containers.Destroy(container); err != nil {
|
||||||
fmt.Fprintln(stdout, "Error destroying container " + name + ": " + err.Error())
|
fmt.Fprintln(stdout, "Error destroying container "+name+": "+err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -286,7 +285,7 @@ func (srv *Server) CmdKill(stdin io.ReadCloser, stdout io.Writer, args ...string
|
||||||
return errors.New("No such container: " + name)
|
return errors.New("No such container: " + name)
|
||||||
}
|
}
|
||||||
if err := container.Kill(); err != nil {
|
if err := container.Kill(); err != nil {
|
||||||
fmt.Fprintln(stdout, "Error killing container " + name + ": " + err.Error())
|
fmt.Fprintln(stdout, "Error killing container "+name+": "+err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -373,7 +372,7 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri
|
||||||
nameFilter = cmd.Arg(0)
|
nameFilter = cmd.Arg(0)
|
||||||
}
|
}
|
||||||
w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0)
|
w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0)
|
||||||
if (!*quiet) {
|
if !*quiet {
|
||||||
fmt.Fprintf(w, "NAME\tID\tCREATED\tPARENT\n")
|
fmt.Fprintf(w, "NAME\tID\tCREATED\tPARENT\n")
|
||||||
}
|
}
|
||||||
for _, name := range srv.images.Names() {
|
for _, name := range srv.images.Names() {
|
||||||
|
@ -407,7 +406,7 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!*quiet) {
|
if !*quiet {
|
||||||
w.Flush()
|
w.Flush()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -424,7 +423,7 @@ func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
w := tabwriter.NewWriter(stdout, 12, 1, 3, ' ', 0)
|
w := tabwriter.NewWriter(stdout, 12, 1, 3, ' ', 0)
|
||||||
if (!*quiet) {
|
if !*quiet {
|
||||||
fmt.Fprintf(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tCOMMENT\n")
|
fmt.Fprintf(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tCOMMENT\n")
|
||||||
}
|
}
|
||||||
for _, container := range srv.containers.List() {
|
for _, container := range srv.containers.List() {
|
||||||
|
@ -437,7 +436,7 @@ func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
||||||
if !*fl_full {
|
if !*fl_full {
|
||||||
command = docker.Trunc(command, 20)
|
command = docker.Trunc(command, 20)
|
||||||
}
|
}
|
||||||
for idx, field := range[]string {
|
for idx, field := range []string{
|
||||||
/* ID */ container.Id,
|
/* ID */ container.Id,
|
||||||
/* IMAGE */ container.GetUserData("image"),
|
/* IMAGE */ container.GetUserData("image"),
|
||||||
/* COMMAND */ command,
|
/* COMMAND */ command,
|
||||||
|
@ -456,7 +455,7 @@ func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
||||||
stdout.Write([]byte(container.Id + "\n"))
|
stdout.Write([]byte(container.Id + "\n"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!*quiet) {
|
if !*quiet {
|
||||||
w.Flush()
|
w.Flush()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -475,7 +474,6 @@ func (srv *Server) CmdLayers(stdin io.ReadCloser, stdout io.Writer, args ...stri
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (srv *Server) CmdCp(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
func (srv *Server) CmdCp(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||||
cmd := rcli.Subcmd(stdout,
|
cmd := rcli.Subcmd(stdout,
|
||||||
"cp", "[OPTIONS] IMAGE NAME",
|
"cp", "[OPTIONS] IMAGE NAME",
|
||||||
|
@ -521,7 +519,6 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri
|
||||||
return errors.New("No such container: " + containerName)
|
return errors.New("No such container: " + containerName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (srv *Server) CmdTar(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
func (srv *Server) CmdTar(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||||
cmd := rcli.Subcmd(stdout,
|
cmd := rcli.Subcmd(stdout,
|
||||||
"tar", "CONTAINER",
|
"tar", "CONTAINER",
|
||||||
|
@ -592,7 +589,6 @@ func (srv *Server) CmdReset(stdin io.ReadCloser, stdout io.Writer, args ...strin
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
|
||||||
cmd := rcli.Subcmd(stdout, "logs", "[OPTIONS] CONTAINER", "Fetch the logs of a container")
|
cmd := rcli.Subcmd(stdout, "logs", "[OPTIONS] CONTAINER", "Fetch the logs of a container")
|
||||||
if err := cmd.Parse(args); err != nil {
|
if err := cmd.Parse(args); err != nil {
|
||||||
|
@ -615,7 +611,6 @@ func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string
|
||||||
return errors.New("No such container: " + cmd.Arg(0))
|
return errors.New("No such container: " + cmd.Arg(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (srv *Server) CreateContainer(img *image.Image, tty bool, openStdin bool, comment string, cmd string, args ...string) (*docker.Container, error) {
|
func (srv *Server) CreateContainer(img *image.Image, tty bool, openStdin bool, comment string, cmd string, args ...string) (*docker.Container, error) {
|
||||||
id := future.RandomId()[:8]
|
id := future.RandomId()[:8]
|
||||||
container, err := srv.containers.Create(id, cmd, args, img.Layers,
|
container, err := srv.containers.Create(id, cmd, args, img.Layers,
|
||||||
|
@ -658,7 +653,7 @@ func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout io.Writer, args ...stri
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() { io.Copy(c_stdin, stdin); wg.Add(-1); }()
|
go func() { io.Copy(c_stdin, stdin); wg.Add(-1) }()
|
||||||
}
|
}
|
||||||
if *fl_o {
|
if *fl_o {
|
||||||
c_stdout, err := container.StdoutPipe()
|
c_stdout, err := container.StdoutPipe()
|
||||||
|
@ -666,7 +661,7 @@ func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout io.Writer, args ...stri
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() { io.Copy(stdout, c_stdout); wg.Add(-1); }()
|
go func() { io.Copy(stdout, c_stdout); wg.Add(-1) }()
|
||||||
}
|
}
|
||||||
if *fl_e {
|
if *fl_e {
|
||||||
c_stderr, err := container.StderrPipe()
|
c_stderr, err := container.StderrPipe()
|
||||||
|
@ -674,7 +669,7 @@ func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout io.Writer, args ...stri
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() { io.Copy(stdout, c_stderr); wg.Add(-1); }()
|
go func() { io.Copy(stdout, c_stderr); wg.Add(-1) }()
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
return nil
|
return nil
|
||||||
|
@ -690,7 +685,7 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
name := cmd.Arg(0)
|
name := cmd.Arg(0)
|
||||||
var cmdline[]string
|
var cmdline []string
|
||||||
if len(cmd.Args()) >= 2 {
|
if len(cmd.Args()) >= 2 {
|
||||||
cmdline = cmd.Args()[1:]
|
cmdline = cmd.Args()[1:]
|
||||||
}
|
}
|
||||||
|
@ -723,7 +718,7 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
||||||
if *fl_attach {
|
if *fl_attach {
|
||||||
future.Go(func() error {
|
future.Go(func() error {
|
||||||
log.Printf("CmdRun(): start receiving stdin\n")
|
log.Printf("CmdRun(): start receiving stdin\n")
|
||||||
_, err := io.Copy(cmd_stdin, stdin);
|
_, err := io.Copy(cmd_stdin, stdin)
|
||||||
log.Printf("CmdRun(): done receiving stdin\n")
|
log.Printf("CmdRun(): done receiving stdin\n")
|
||||||
cmd_stdin.Close()
|
cmd_stdin.Close()
|
||||||
return err
|
return err
|
||||||
|
@ -744,11 +739,11 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
sending_stdout := future.Go(func() error {
|
sending_stdout := future.Go(func() error {
|
||||||
_, err := io.Copy(stdout, cmd_stdout);
|
_, err := io.Copy(stdout, cmd_stdout)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
sending_stderr := future.Go(func() error {
|
sending_stderr := future.Go(func() error {
|
||||||
_, err := io.Copy(stdout, cmd_stderr);
|
_, err := io.Copy(stdout, cmd_stderr)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
err_sending_stdout := <-sending_stdout
|
err_sending_stdout := <-sending_stdout
|
||||||
|
@ -770,6 +765,11 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
if docker.SelfPath() == "/sbin/init" {
|
||||||
|
// Running in init mode
|
||||||
|
docker.SysInit()
|
||||||
|
return
|
||||||
|
}
|
||||||
future.Seed()
|
future.Seed()
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
d, err := New()
|
d, err := New()
|
||||||
|
@ -845,9 +845,7 @@ func (srv *Server) CmdWeb(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
containers *docker.Docker
|
containers *docker.Docker
|
||||||
images *image.Store
|
images *image.Store
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,8 @@ lxc.utsname = {{.Id}}
|
||||||
#lxc.network.ipv4 = {ip_address}/{ip_prefix_len}
|
#lxc.network.ipv4 = {ip_address}/{ip_prefix_len}
|
||||||
|
|
||||||
# root filesystem
|
# root filesystem
|
||||||
lxc.rootfs = {{.Filesystem.RootFS}}
|
{{$ROOTFS := .Filesystem.RootFS}}
|
||||||
|
lxc.rootfs = {{$ROOTFS}}
|
||||||
|
|
||||||
# use a dedicated pts for the container (and limit the number of pseudo terminal
|
# use a dedicated pts for the container (and limit the number of pseudo terminal
|
||||||
# available)
|
# available)
|
||||||
|
@ -66,15 +67,18 @@ lxc.cgroup.devices.allow = c 10:200 rwm
|
||||||
|
|
||||||
|
|
||||||
# standard mount point
|
# standard mount point
|
||||||
lxc.mount.entry = proc {{.Filesystem.RootFS}}/proc proc nosuid,nodev,noexec 0 0
|
lxc.mount.entry = proc {{$ROOTFS}}/proc proc nosuid,nodev,noexec 0 0
|
||||||
lxc.mount.entry = sysfs {{.Filesystem.RootFS}}/sys sysfs nosuid,nodev,noexec 0 0
|
lxc.mount.entry = sysfs {{$ROOTFS}}/sys sysfs nosuid,nodev,noexec 0 0
|
||||||
lxc.mount.entry = devpts {{.Filesystem.RootFS}}/dev/pts devpts newinstance,ptmxmode=0666,nosuid,noexec 0 0
|
lxc.mount.entry = devpts {{$ROOTFS}}/dev/pts devpts newinstance,ptmxmode=0666,nosuid,noexec 0 0
|
||||||
#lxc.mount.entry = varrun {{.Filesystem.RootFS}}/var/run tmpfs mode=755,size=4096k,nosuid,nodev,noexec 0 0
|
#lxc.mount.entry = varrun {{$ROOTFS}}/var/run tmpfs mode=755,size=4096k,nosuid,nodev,noexec 0 0
|
||||||
#lxc.mount.entry = varlock {{.Filesystem.RootFS}}/var/lock tmpfs size=1024k,nosuid,nodev,noexec 0 0
|
#lxc.mount.entry = varlock {{$ROOTFS}}/var/lock tmpfs size=1024k,nosuid,nodev,noexec 0 0
|
||||||
#lxc.mount.entry = shm {{.Filesystem.RootFS}}/dev/shm tmpfs size=65536k,nosuid,nodev,noexec 0 0
|
#lxc.mount.entry = shm {{$ROOTFS}}/dev/shm tmpfs size=65536k,nosuid,nodev,noexec 0 0
|
||||||
|
|
||||||
|
# Inject docker-init
|
||||||
|
lxc.mount.entry = {{.SysInitPath}} {{$ROOTFS}}/sbin/init none bind,ro 0 0
|
||||||
|
|
||||||
# In order to get a working DNS environment, mount bind (ro) the host's /etc/resolv.conf into the container
|
# In order to get a working DNS environment, mount bind (ro) the host's /etc/resolv.conf into the container
|
||||||
lxc.mount.entry = /etc/resolv.conf {{.Filesystem.RootFS}}/etc/resolv.conf none bind,ro 0 0
|
lxc.mount.entry = /etc/resolv.conf {{$ROOTFS}}/etc/resolv.conf none bind,ro 0 0
|
||||||
|
|
||||||
|
|
||||||
# drop linux capabilities (apply mainly to the user root in the container)
|
# drop linux capabilities (apply mainly to the user root in the container)
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sys Init code
|
||||||
|
// This code is run INSIDE the container and is responsible for setting
|
||||||
|
// up the environment before running the actual process
|
||||||
|
func SysInit() {
|
||||||
|
if len(os.Args) <= 1 {
|
||||||
|
fmt.Println("You should not invoke docker-init manually")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
path, err := exec.LookPath(os.Args[1])
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to locate %v", os.Args[1])
|
||||||
|
os.Exit(127)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := syscall.Exec(path, os.Args[1:], os.Environ()); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
15
utils.go
15
utils.go
|
@ -4,7 +4,9 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"container/list"
|
"container/list"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -34,6 +36,19 @@ func Tar(path string) (io.Reader, error) {
|
||||||
return output, nil
|
return output, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Figure out the absolute path of our own binary
|
||||||
|
func SelfPath() string {
|
||||||
|
path, err := exec.LookPath(os.Args[0])
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
path, err = filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
type nopWriteCloser struct {
|
type nopWriteCloser struct {
|
||||||
io.Writer
|
io.Writer
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue