From 8cf07b2ad1ed8c6646c48a74e9ecbb2bfeecb322 Mon Sep 17 00:00:00 2001 From: baude Date: Wed, 1 Nov 2017 13:59:11 -0500 Subject: libpod create and run patched version of the same code that went into crio Signed-off-by: baude --- libpod/container.go | 31 +++++- libpod/container_attach.go | 142 +++++++++++++++++++++++++ libpod/runtime_ctr.go | 1 - libpod/runtime_img.go | 259 +++++++++++++++++++++++++++++++++++++++++++++ libpod/storage.go | 2 +- libpod/util.go | 34 ++++++ 6 files changed, 465 insertions(+), 4 deletions(-) create mode 100644 libpod/container_attach.go create mode 100644 libpod/util.go (limited to 'libpod') diff --git a/libpod/container.go b/libpod/container.go index 1f5be9477..50fe18939 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -15,6 +15,9 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/ulule/deepcopier" + "github.com/docker/docker/pkg/term" + "k8s.io/client-go/tools/remotecommand" + ) // ContainerState represents the current state of a container @@ -391,8 +394,32 @@ func (c *Container) Exec(cmd []string, tty bool, stdin bool) (string, error) { // Attach attaches to a container // Returns fully qualified URL of streaming server for the container -func (c *Container) Attach(stdin, tty bool) (string, error) { - return "", ErrNotImplemented +func (c *Container) Attach(noStdin bool, keys string) error { + // Check the validity of the provided keys first + var err error + detachKeys := []byte{} + if len(keys) > 0 { + detachKeys, err = term.ToBytes(keys) + if err != nil { + return errors.Wrapf(err, "invalid detach keys") + } + } + cStatus := c.state.State + + if !(cStatus == ContainerStateRunning || cStatus == ContainerStateCreated) { + return errors.Errorf("%s is not created or running", c.Name()) + } + resize := make(chan remotecommand.TerminalSize) + defer close(resize) + err = c.attachContainerSocket(resize, noStdin, detachKeys) + if err != nil { + return err + } + // TODO + // Re-enable this when mheon is done wth it + //c.ContainerStateToDisk(c) + + return nil } // Mount mounts a container's filesystem on the host diff --git a/libpod/container_attach.go b/libpod/container_attach.go new file mode 100644 index 000000000..b2a407027 --- /dev/null +++ b/libpod/container_attach.go @@ -0,0 +1,142 @@ +package libpod + +import ( + "fmt" + "io" + "net" + "os" + "path/filepath" + "strconv" + + "github.com/docker/docker/pkg/term" + "github.com/kubernetes-incubator/cri-o/utils" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" + "k8s.io/client-go/tools/remotecommand" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" +) + +/* Sync with stdpipe_t in conmon.c */ +const ( + AttachPipeStdin = 1 + AttachPipeStdout = 2 + AttachPipeStderr = 3 +) + +// attachContainerSocket connects to the container's attach socket and deals with the IO +func (c *Container) attachContainerSocket(resize <-chan remotecommand.TerminalSize, noStdIn bool, detachKeys []byte) error { + inputStream := os.Stdin + outputStream := os.Stdout + errorStream := os.Stderr + defer inputStream.Close() + tty, err := strconv.ParseBool(c.runningSpec.Annotations["io.kubernetes.cri-o.TTY"]) + if err != nil { + return errors.Wrapf(err, "unable to parse annotations in %s", c.ID) + } + if !tty { + return errors.Errorf("no tty available for %s", c.ID()) + } + + oldTermState, err := term.SaveState(inputStream.Fd()) + if err != nil { + return errors.Wrapf(err, "unable to save terminal state") + } + + defer term.RestoreTerminal(inputStream.Fd(), oldTermState) + + // Put both input and output into raw + if !noStdIn { + term.SetRawTerminal(inputStream.Fd()) + } + + controlPath := filepath.Join(c.state.RunDir, "ctl") + controlFile, err := os.OpenFile(controlPath, unix.O_WRONLY, 0) + if err != nil { + return errors.Wrapf(err, "failed to open container ctl file: %v") + } + + kubecontainer.HandleResizing(resize, func(size remotecommand.TerminalSize) { + logrus.Debugf("Received a resize event: %+v", size) + _, err := fmt.Fprintf(controlFile, "%d %d %d\n", 1, size.Height, size.Width) + if err != nil { + logrus.Warnf("Failed to write to control file to resize terminal: %v", err) + } + }) + logrus.Debug("connecting to socket ", c.attachSocketPath()) + + conn, err := net.DialUnix("unixpacket", nil, &net.UnixAddr{Name: c.attachSocketPath(), Net: "unixpacket"}) + if err != nil { + return errors.Wrapf(err, "failed to connect to container's attach socket: %v") + } + defer conn.Close() + + receiveStdoutError := make(chan error) + if outputStream != nil || errorStream != nil { + go func() { + receiveStdoutError <- redirectResponseToOutputStreams(outputStream, errorStream, conn) + }() + } + + stdinDone := make(chan error) + go func() { + var err error + if inputStream != nil && !noStdIn { + _, err = utils.CopyDetachable(conn, inputStream, detachKeys) + conn.CloseWrite() + } + stdinDone <- err + }() + + select { + case err := <-receiveStdoutError: + return err + case err := <-stdinDone: + if _, ok := err.(utils.DetachError); ok { + return nil + } + if outputStream != nil || errorStream != nil { + return <-receiveStdoutError + } + } + return nil +} + +func redirectResponseToOutputStreams(outputStream, errorStream io.Writer, conn io.Reader) error { + var err error + buf := make([]byte, 8192+1) /* Sync with conmon STDIO_BUF_SIZE */ + for { + nr, er := conn.Read(buf) + if nr > 0 { + var dst io.Writer + switch buf[0] { + case AttachPipeStdout: + dst = outputStream + case AttachPipeStderr: + dst = errorStream + default: + logrus.Infof("Received unexpected attach type %+d", buf[0]) + } + + if dst != nil { + nw, ew := dst.Write(buf[1:nr]) + if ew != nil { + err = ew + break + } + if nr != nw+1 { + err = io.ErrShortWrite + break + } + } + } + if er == io.EOF { + break + } + if er != nil { + err = er + break + } + } + return err +} diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 45990d2db..a1351e1d7 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -22,7 +22,6 @@ type ContainerFilter func(*Container) bool func (r *Runtime) NewContainer(spec *spec.Spec, options ...CtrCreateOption) (ctr *Container, err error) { r.lock.Lock() defer r.lock.Unlock() - if !r.valid { return nil, ErrRuntimeStopped } diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go index 278f0c186..a058380a1 100644 --- a/libpod/runtime_img.go +++ b/libpod/runtime_img.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "io" + "net" "os" "strings" "syscall" @@ -46,6 +47,8 @@ var ( // DirTransport is the transport for pushing and pulling // images to and from a directory DirTransport = "dir" + // TransportNames are the supported transports in string form + TransportNames = [...]string{DefaultRegistry, DockerArchive, OCIArchive, "ostree:", "dir:"} ) // CopyOptions contains the options given when pushing or pulling images @@ -94,6 +97,262 @@ type imageDecomposeStruct struct { transport string } +func (k *Image) assembleFqName() string { + return fmt.Sprintf("%s/%s:%s", k.Registry, k.ImageName, k.Tag) +} + +func (k *Image) assembleFqNameTransport() string { + return fmt.Sprintf("%s%s/%s:%s", k.Transport, k.Registry, k.ImageName, k.Tag) +} + +//Image describes basic attributes of an image +type Image struct { + Name string + ID string + fqname string + hasImageLocal bool + runtime *Runtime + Registry string + ImageName string + Tag string + HasRegistry bool + Transport string + beenDecomposed bool + PullName string +} + +// NewImage creates a new image object based on its name +func (r *Runtime) NewImage(name string) Image { + return Image{ + Name: name, + runtime: r, + } +} + +// GetImageID returns the image ID of the image +func (k *Image) GetImageID() (string, error) { + if k.ID != "" { + return k.ID, nil + } + image, _ := k.GetFQName() + img, err := k.runtime.GetImage(image) + if err != nil { + return "", err + } + return img.ID, nil +} + +// GetFQName returns the fully qualified image name if it can be determined +func (k *Image) GetFQName() (string, error) { + // Check if the fqname has already been found + if k.fqname != "" { + return k.fqname, nil + } + if err := k.Decompose(); err != nil { + return "", err + } + k.fqname = k.assembleFqName() + return k.fqname, nil +} + +func (k *Image) findImageOnRegistry() error { + searchRegistries, err := GetRegistries() + + if err != nil { + return errors.Wrapf(err, " the image name '%s' is incomplete.", k.Name) + } + + for _, searchRegistry := range searchRegistries { + k.Registry = searchRegistry + err = k.GetManifest() + if err == nil { + k.fqname = k.assembleFqName() + return nil + + } + } + return errors.Errorf("unable to find image on any configured registries") + +} + +// GetManifest tries to GET an images manifest, returns nil on success and err on failure +func (k *Image) GetManifest() error { + pullRef, err := alltransports.ParseImageName(k.assembleFqNameTransport()) + if err != nil { + return errors.Errorf("unable to parse1 '%s'", k.assembleFqName()) + } + imageSource, err := pullRef.NewImageSource(nil) + if err != nil { + return errors.Wrapf(err, "unable to create new image source") + } + _, _, err = imageSource.GetManifest() + if err == nil { + return nil + } + return err +} + +//Decompose breaks up an image name into its parts +func (k *Image) Decompose() error { + if k.beenDecomposed { + return nil + } + k.beenDecomposed = true + k.Transport = "docker://" + decomposeName := k.Name + for _, transport := range TransportNames { + if strings.HasPrefix(k.Name, transport) { + k.Transport = transport + decomposeName = strings.Replace(k.Name, transport, "", -1) + break + } + } + if k.Transport == "dir:" { + return nil + } + var imageError = fmt.Sprintf("unable to parse '%s'\n", decomposeName) + imgRef, err := reference.Parse(decomposeName) + if err != nil { + return errors.Wrapf(err, imageError) + } + tagged, isTagged := imgRef.(reference.NamedTagged) + k.Tag = "latest" + if isTagged { + k.Tag = tagged.Tag() + } + k.HasRegistry = true + registry := reference.Domain(imgRef.(reference.Named)) + if registry == "" { + k.HasRegistry = false + } + k.ImageName = reference.Path(imgRef.(reference.Named)) + + // account for image names with directories in them like + // umohnani/get-started:part1 + if k.HasRegistry { + k.Registry = registry + k.fqname = k.assembleFqName() + k.PullName = k.assembleFqName() + + registries, err := getRegistries() + if err != nil { + return nil + } + if StringInSlice(k.Registry, registries) { + return nil + } + // We need to check if the registry name is legit + _, err = net.LookupAddr(k.Registry) + if err == nil { + return nil + } + // Combine the Registry and Image Name together and blank out the Registry Name + k.ImageName = fmt.Sprintf("%s/%s", k.Registry, k.ImageName) + k.Registry = "" + + } + // No Registry means we check the globals registries configuration file + // and assemble a list of candidate sources to try + //searchRegistries, err := GetRegistries() + err = k.findImageOnRegistry() + k.PullName = k.assembleFqName() + if err != nil { + return errors.Wrapf(err, " the image name '%s' is incomplete.", k.Name) + } + return nil +} + +// HasImageLocal returns a bool true if the image is already pulled +func (k *Image) HasImageLocal() bool { + _, err := k.runtime.GetImage(k.Name) + if err == nil { + return true + } + fqname, _ := k.GetFQName() + + _, err = k.runtime.GetImage(fqname) + if err == nil { + return true + } + return false +} + +// HasLatest determines if we have the latest image local +func (k *Image) HasLatest() (bool, error) { + if !k.HasImageLocal() { + return false, nil + } + fqname, err := k.GetFQName() + if err != nil { + return false, err + } + pullRef, err := alltransports.ParseImageName(fqname) + if err != nil { + return false, err + } + _, _, err = pullRef.(types.ImageSource).GetManifest() + if err != nil { + return false, err + } + return false, nil +} + +// Pull is a wrapper function to pull and image +func (k *Image) Pull() error { + // If the image hasn't been decomposed yet + if !k.beenDecomposed { + err := k.Decompose() + if err != nil { + return err + } + } + k.runtime.PullImage(k.PullName, CopyOptions{Writer: os.Stdout, SignaturePolicyPath: k.runtime.config.SignaturePolicyPath}) + return nil +} + +// GetRegistries gets the searchable registries from the global registration file. +func GetRegistries() ([]string, error) { + registryConfigPath := "" + envOverride := os.Getenv("REGISTRIES_CONFIG_PATH") + if len(envOverride) > 0 { + registryConfigPath = envOverride + } + searchRegistries, err := sysregistries.GetRegistries(&types.SystemContext{SystemRegistriesConfPath: registryConfigPath}) + if err != nil { + return nil, errors.Errorf("unable to parse the registries.conf file") + } + return searchRegistries, nil +} + +// GetInsecureRegistries obtains the list of inseure registries from the global registration file. +func GetInsecureRegistries() ([]string, error) { + registryConfigPath := "" + envOverride := os.Getenv("REGISTRIES_CONFIG_PATH") + if len(envOverride) > 0 { + registryConfigPath = envOverride + } + registries, err := sysregistries.GetInsecureRegistries(&types.SystemContext{SystemRegistriesConfPath: registryConfigPath}) + if err != nil { + return nil, errors.Errorf("unable to parse the registries.conf file") + } + return registries, nil +} + +// getRegistries returns both searchable and insecure registries from the global conf file. +func getRegistries() ([]string, error) { + var r []string + registries, err := GetRegistries() + if err != nil { + return r, err + } + insecureRegistries, err := GetInsecureRegistries() + if err != nil { + return r, err + } + r = append(registries, insecureRegistries...) + return r, nil +} + // ImageFilter is a function to determine whether an image is included in // command output. Images to be outputted are tested using the function. A true // return will include the image, a false return will exclude it. diff --git a/libpod/storage.go b/libpod/storage.go index f0bf9e9cd..5e18aaf5c 100644 --- a/libpod/storage.go +++ b/libpod/storage.go @@ -57,7 +57,7 @@ func (metadata *RuntimeContainerMetadata) SetMountLabel(mountLabel string) { } // CreateContainerStorage creates the storage end of things. We already have the container spec created -// TO-DO We should be passing in an KpodImage object in the future. +// TO-DO We should be passing in an Image object in the future. func (r *storageService) CreateContainerStorage(systemContext *types.SystemContext, imageName, imageID, containerName, containerID, mountLabel string) (ContainerInfo, error) { var ref types.ImageReference if imageName == "" && imageID == "" { diff --git a/libpod/util.go b/libpod/util.go new file mode 100644 index 000000000..0270af07c --- /dev/null +++ b/libpod/util.go @@ -0,0 +1,34 @@ +package libpod + +import ( + "os" + "path/filepath" +) + +// WriteFile writes a provided string to a provided path +func WriteFile(content string, path string) error { + baseDir := filepath.Dir(path) + if baseDir != "" { + if _, err := os.Stat(path); err != nil { + return err + } + } + f, err := os.Create(path) + if err != nil { + return err + } + defer f.Close() + f.WriteString(content) + f.Sync() + return nil +} + +// StringInSlice determines if a string is in a string slice, returns bool +func StringInSlice(s string, sl []string) bool { + for _, i := range sl { + if i == s { + return true + } + } + return false +} -- cgit v1.2.3-54-g00ecf