libpod create and run

patched version of the same code that went into crio

Signed-off-by: baude <bbaude@redhat.com>
This commit is contained in:
baude
2017-11-01 13:59:11 -05:00
parent f5019df3f5
commit 8cf07b2ad1
22 changed files with 5194 additions and 8 deletions

View File

@ -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

142
libpod/container_attach.go Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -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.

View File

@ -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 == "" {

34
libpod/util.go Normal file
View File

@ -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
}