mirror of
https://github.com/containers/podman.git
synced 2025-10-25 10:16:43 +08:00
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:
@ -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
142
libpod/container_attach.go
Normal 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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
34
libpod/util.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user