aboutsummaryrefslogtreecommitdiff
path: root/libpod
diff options
context:
space:
mode:
authorMatthew Heon <matthew.heon@gmail.com>2017-11-01 15:30:35 -0400
committerGitHub <noreply@github.com>2017-11-01 15:30:35 -0400
commitd7782cc31e809d8e1551f47a0ddb0ff8073005fc (patch)
tree625fdeaf51ffced387b4ba2e48d93288f3f1b9b2 /libpod
parentf5019df3f5da9030ce21e5c8ad3d3921a6585e7f (diff)
parent8cf07b2ad1ed8c6646c48a74e9ecbb2bfeecb322 (diff)
downloadpodman-d7782cc31e809d8e1551f47a0ddb0ff8073005fc.tar.gz
podman-d7782cc31e809d8e1551f47a0ddb0ff8073005fc.tar.bz2
podman-d7782cc31e809d8e1551f47a0ddb0ff8073005fc.zip
Merge pull request #2 from baude/create
libpod create and run
Diffstat (limited to 'libpod')
-rw-r--r--libpod/container.go31
-rw-r--r--libpod/container_attach.go142
-rw-r--r--libpod/runtime_ctr.go1
-rw-r--r--libpod/runtime_img.go259
-rw-r--r--libpod/storage.go2
-rw-r--r--libpod/util.go34
6 files changed, 465 insertions, 4 deletions
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
+}