mirror of
https://github.com/podman-container-tools/podman.git
synced 2026-11-03 09:37:34 +00:00
Merge pull request #9163 from mheon/backports_rc2
Backports for v3.0 RC2
This commit is contained in:
commit
745fa4ac94
33 changed files with 628 additions and 286 deletions
14
.cirrus.yml
14
.cirrus.yml
|
|
@ -274,17 +274,19 @@ swagger_task:
|
|||
|
||||
|
||||
# Check that all included go modules from other sources match
|
||||
# what is expected in `vendor/modules.txt` vs `go.mod`.
|
||||
vendor_task:
|
||||
name: "Test Vendoring"
|
||||
alias: vendor
|
||||
# what is expected in `vendor/modules.txt` vs `go.mod`. Also
|
||||
# make sure that the generated bindings in pkg/bindings/...
|
||||
# are in sync with the code.
|
||||
consistency_task:
|
||||
name: "Test Code Consistency"
|
||||
alias: consistency
|
||||
skip: *tags
|
||||
depends_on:
|
||||
- build
|
||||
container: *smallcontainer
|
||||
env:
|
||||
<<: *stdenvars
|
||||
TEST_FLAVOR: vendor
|
||||
TEST_FLAVOR: consistency
|
||||
TEST_ENVIRON: container
|
||||
CTR_FQIN: ${FEDORA_CONTAINER_FQIN}
|
||||
clone_script: *full_clone # build-cache not available to container tasks
|
||||
|
|
@ -642,7 +644,7 @@ success_task:
|
|||
- validate
|
||||
- bindings
|
||||
- swagger
|
||||
- vendor
|
||||
- consistency
|
||||
- alt_build
|
||||
- static_alt_build
|
||||
- osx_alt_build
|
||||
|
|
|
|||
22
Makefile
22
Makefile
|
|
@ -86,7 +86,7 @@ PODMAN_SERVER_LOG ?=
|
|||
|
||||
# If GOPATH not specified, use one in the local directory
|
||||
ifeq ($(GOPATH),)
|
||||
export GOPATH := $(CURDIR)/_output
|
||||
export GOPATH := $(HOME)/go
|
||||
unexport GOBIN
|
||||
endif
|
||||
FIRST_GOPATH := $(firstword $(subst :, ,$(GOPATH)))
|
||||
|
|
@ -98,6 +98,8 @@ ifeq ($(GOBIN),)
|
|||
GOBIN := $(FIRST_GOPATH)/bin
|
||||
endif
|
||||
|
||||
export PATH := $(PATH):$(GOBIN)
|
||||
|
||||
GOMD2MAN ?= $(shell command -v go-md2man || echo '$(GOBIN)/go-md2man')
|
||||
|
||||
CROSS_BUILD_TARGETS := \
|
||||
|
|
@ -206,7 +208,7 @@ endif
|
|||
podman: bin/podman
|
||||
|
||||
.PHONY: bin/podman-remote
|
||||
bin/podman-remote: .gopathok .generate-bindings $(SOURCES) go.mod go.sum ## Build with podman on remote environment
|
||||
bin/podman-remote: .gopathok $(SOURCES) go.mod go.sum ## Build with podman on remote environment
|
||||
$(GO) build $(BUILDFLAGS) -gcflags '$(GCFLAGS)' -asmflags '$(ASMFLAGS)' -ldflags '$(LDFLAGS_PODMAN)' -tags "${REMOTETAGS}" -o $@ ./cmd/podman
|
||||
|
||||
.PHONY: bin/podman-remote-static
|
||||
|
|
@ -277,7 +279,6 @@ clean: ## Clean artifacts
|
|||
libpod/container_easyjson.go \
|
||||
libpod/pod_easyjson.go \
|
||||
.install.goimports \
|
||||
.generate-bindings \
|
||||
docs/build
|
||||
make -C docs clean
|
||||
|
||||
|
|
@ -459,20 +460,11 @@ podman-remote-%-release:
|
|||
rm -f release.txt
|
||||
$(MAKE) podman-remote-release-$*.zip
|
||||
|
||||
BINDINGS_SOURCE = $(wildcard pkg/bindings/**/types.go)
|
||||
.generate-bindings: $(BINDINGS_SOURCE)
|
||||
.PHONY: generate-bindings
|
||||
generate-bindings:
|
||||
ifneq ($(shell uname -s), Darwin)
|
||||
for i in $(BINDINGS_SOURCE); do \
|
||||
dirname=$$(dirname $${i}); \
|
||||
shortname=$$(basename $${dirname}); \
|
||||
pushd $${dirname}>/dev/null; \
|
||||
echo $${dirname}; \
|
||||
echo $(GO) generate; \
|
||||
$(GO) generate; \
|
||||
popd > /dev/null; \
|
||||
done;
|
||||
GO111MODULE=off $(GO) generate ./pkg/bindings/... ;
|
||||
endif
|
||||
touch .generate-bindings
|
||||
|
||||
.PHONY: docker-docs
|
||||
docker-docs: docs
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package common
|
|||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
|
@ -383,8 +384,29 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroup
|
|||
}
|
||||
|
||||
// volumes
|
||||
if volumes := cc.HostConfig.Binds; len(volumes) > 0 {
|
||||
cliOpts.Volume = volumes
|
||||
volDestinations := make(map[string]bool)
|
||||
for _, vol := range cc.HostConfig.Binds {
|
||||
cliOpts.Volume = append(cliOpts.Volume, vol)
|
||||
// Extract the destination so we don't add duplicate mounts in
|
||||
// the volumes phase.
|
||||
splitVol := strings.SplitN(vol, ":", 3)
|
||||
switch len(splitVol) {
|
||||
case 1:
|
||||
volDestinations[vol] = true
|
||||
default:
|
||||
volDestinations[splitVol[1]] = true
|
||||
}
|
||||
}
|
||||
// Anonymous volumes are added differently from other volumes, in their
|
||||
// own special field, for reasons known only to Docker. Still use the
|
||||
// format of `-v` so we can just append them in there.
|
||||
// Unfortunately, these may be duplicates of existing mounts in Binds.
|
||||
// So... We need to catch that.
|
||||
for vol := range cc.Volumes {
|
||||
if _, ok := volDestinations[filepath.Clean(vol)]; ok {
|
||||
continue
|
||||
}
|
||||
cliOpts.Volume = append(cliOpts.Volume, vol)
|
||||
}
|
||||
if len(cc.HostConfig.BlkioWeightDevice) > 0 {
|
||||
devices := make([]string, 0, len(cc.HostConfig.BlkioWeightDevice))
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ func (h historyReporter) Size() string {
|
|||
}
|
||||
|
||||
func (h historyReporter) CreatedBy() string {
|
||||
if len(h.ImageHistoryLayer.CreatedBy) > 45 {
|
||||
if !opts.noTrunc && len(h.ImageHistoryLayer.CreatedBy) > 45 {
|
||||
return h.ImageHistoryLayer.CreatedBy[:45-3] + "..."
|
||||
}
|
||||
return h.ImageHistoryLayer.CreatedBy
|
||||
|
|
|
|||
|
|
@ -142,9 +142,11 @@ function _run_swagger() {
|
|||
cp -v $GOSRC/pkg/api/swagger.yaml $GOSRC/
|
||||
}
|
||||
|
||||
function _run_vendor() {
|
||||
function _run_consistency() {
|
||||
make vendor
|
||||
./hack/tree_status.sh
|
||||
SUGGESTION="run 'make vendor' and commit all changes" ./hack/tree_status.sh
|
||||
make generate-bindings
|
||||
SUGGESTION="run 'make generate-bindings' and commit all changes" ./hack/tree_status.sh
|
||||
}
|
||||
|
||||
function _run_build() {
|
||||
|
|
|
|||
|
|
@ -214,7 +214,7 @@ case "$TEST_FLAVOR" in
|
|||
|
||||
install_test_configs
|
||||
;;
|
||||
vendor) make clean ;;
|
||||
consistency) make clean ;;
|
||||
release) ;;
|
||||
*) die_unknown TEST_FLAVOR
|
||||
esac
|
||||
|
|
|
|||
|
|
@ -776,3 +776,32 @@ func (c *Container) ShouldRestart(ctx context.Context) bool {
|
|||
}
|
||||
return c.shouldRestart()
|
||||
}
|
||||
|
||||
// ResolvePath resolves the specified path on the root for the container. The
|
||||
// root must either be the mounted image of the container or the already
|
||||
// mounted container storage.
|
||||
//
|
||||
// It returns the resolved root and the resolved path. Note that the path may
|
||||
// resolve to the container's mount point or to a volume or bind mount.
|
||||
func (c *Container) ResolvePath(ctx context.Context, root string, path string) (string, string, error) {
|
||||
logrus.Debugf("Resolving path %q (root %q) on container %s", path, root, c.ID())
|
||||
|
||||
// Minimal sanity checks.
|
||||
if len(root)*len(path) == 0 {
|
||||
return "", "", errors.Wrapf(define.ErrInternal, "ResolvePath: root (%q) and path (%q) must be non empty", root, path)
|
||||
}
|
||||
if _, err := os.Stat(root); err != nil {
|
||||
return "", "", errors.Wrapf(err, "cannot locate root to resolve path on container %s", c.ID())
|
||||
}
|
||||
|
||||
if !c.batched {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if err := c.syncContainer(); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
}
|
||||
|
||||
return c.resolvePath(root, path)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -174,25 +174,60 @@ func (c *Container) prepare() error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Ensure container entrypoint is created (if required)
|
||||
if c.config.CreateWorkingDir {
|
||||
workdir, err := securejoin.SecureJoin(c.state.Mountpoint, c.WorkingDir())
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error creating path to container %s working dir", c.ID())
|
||||
}
|
||||
rootUID := c.RootUID()
|
||||
rootGID := c.RootGID()
|
||||
// Make sure the workdir exists
|
||||
if err := c.resolveWorkDir(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(workdir, 0755); err != nil {
|
||||
if os.IsExist(err) {
|
||||
return nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveWorkDir resolves the container's workdir and, depending on the
|
||||
// configuration, will create it, or error out if it does not exist.
|
||||
// Note that the container must be mounted before.
|
||||
func (c *Container) resolveWorkDir() error {
|
||||
workdir := c.WorkingDir()
|
||||
|
||||
// If the specified workdir is a subdir of a volume or mount,
|
||||
// we don't need to do anything. The runtime is taking care of
|
||||
// that.
|
||||
if isPathOnVolume(c, workdir) || isPathOnBindMount(c, workdir) {
|
||||
logrus.Debugf("Workdir %q resolved to a volume or mount", workdir)
|
||||
return nil
|
||||
}
|
||||
|
||||
_, resolvedWorkdir, err := c.resolvePath(c.state.Mountpoint, workdir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.Debugf("Workdir %q resolved to host path %q", workdir, resolvedWorkdir)
|
||||
|
||||
// No need to create it (e.g., `--workdir=/foo`), so let's make sure
|
||||
// the path exists on the container.
|
||||
if !c.config.CreateWorkingDir {
|
||||
if _, err := os.Stat(resolvedWorkdir); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return errors.Errorf("workdir %q does not exist on container %s", workdir, c.ID())
|
||||
}
|
||||
return errors.Wrapf(err, "error creating container %s working dir", c.ID())
|
||||
// This might be a serious error (e.g., permission), so
|
||||
// we need to return the full error.
|
||||
return errors.Wrapf(err, "error detecting workdir %q on container %s", workdir, c.ID())
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.Chown(workdir, rootUID, rootGID); err != nil {
|
||||
return errors.Wrapf(err, "error chowning container %s working directory to container root", c.ID())
|
||||
// Ensure container entrypoint is created (if required).
|
||||
rootUID := c.RootUID()
|
||||
rootGID := c.RootGID()
|
||||
|
||||
if err := os.MkdirAll(resolvedWorkdir, 0755); err != nil {
|
||||
if os.IsExist(err) {
|
||||
return nil
|
||||
}
|
||||
return errors.Wrapf(err, "error creating container %s workdir", c.ID())
|
||||
}
|
||||
|
||||
if err := os.Chown(resolvedWorkdir, rootUID, rootGID); err != nil {
|
||||
return errors.Wrapf(err, "error chowning container %s workdir to container root", c.ID())
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
166
libpod/container_path_resolution.go
Normal file
166
libpod/container_path_resolution.go
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
package libpod
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
securejoin "github.com/cyphar/filepath-securejoin"
|
||||
"github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// resolveContainerPaths resolves the container's mount point and the container
|
||||
// path as specified by the user. Both may resolve to paths outside of the
|
||||
// container's mount point when the container path hits a volume or bind mount.
|
||||
//
|
||||
// It returns a bool, indicating whether containerPath resolves outside of
|
||||
// mountPoint (e.g., via a mount or volume), the resolved root (e.g., container
|
||||
// mount, bind mount or volume) and the resolved path on the root (absolute to
|
||||
// the host).
|
||||
func (container *Container) resolvePath(mountPoint string, containerPath string) (string, string, error) {
|
||||
// Let's first make sure we have a path relative to the mount point.
|
||||
pathRelativeToContainerMountPoint := containerPath
|
||||
if !filepath.IsAbs(containerPath) {
|
||||
// If the containerPath is not absolute, it's relative to the
|
||||
// container's working dir. To be extra careful, let's first
|
||||
// join the working dir with "/", and the add the containerPath
|
||||
// to it.
|
||||
pathRelativeToContainerMountPoint = filepath.Join(filepath.Join("/", container.WorkingDir()), containerPath)
|
||||
}
|
||||
resolvedPathOnTheContainerMountPoint := filepath.Join(mountPoint, pathRelativeToContainerMountPoint)
|
||||
pathRelativeToContainerMountPoint = strings.TrimPrefix(pathRelativeToContainerMountPoint, mountPoint)
|
||||
pathRelativeToContainerMountPoint = filepath.Join("/", pathRelativeToContainerMountPoint)
|
||||
|
||||
// Now we have an "absolute container Path" but not yet resolved on the
|
||||
// host (e.g., "/foo/bar/file.txt"). As mentioned above, we need to
|
||||
// check if "/foo/bar/file.txt" is on a volume or bind mount. To do
|
||||
// that, we need to walk *down* the paths to the root. Assuming
|
||||
// volume-1 is mounted to "/foo" and volume-2 is mounted to "/foo/bar",
|
||||
// we must select "/foo/bar". Once selected, we need to rebase the
|
||||
// remainder (i.e, "/file.txt") on the volume's mount point on the
|
||||
// host. Same applies to bind mounts.
|
||||
|
||||
searchPath := pathRelativeToContainerMountPoint
|
||||
for {
|
||||
volume, err := findVolume(container, searchPath)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if volume != nil {
|
||||
logrus.Debugf("Container path %q resolved to volume %q on path %q", containerPath, volume.Name(), searchPath)
|
||||
|
||||
// TODO: We really need to force the volume to mount
|
||||
// before doing this, but that API is not exposed
|
||||
// externally right now and doing so is beyond the scope
|
||||
// of this commit.
|
||||
mountPoint, err := volume.MountPoint()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if mountPoint == "" {
|
||||
return "", "", errors.Errorf("volume %s is not mounted, cannot copy into it", volume.Name())
|
||||
}
|
||||
|
||||
// We found a matching volume for searchPath. We now
|
||||
// need to first find the relative path of our input
|
||||
// path to the searchPath, and then join it with the
|
||||
// volume's mount point.
|
||||
pathRelativeToVolume := strings.TrimPrefix(pathRelativeToContainerMountPoint, searchPath)
|
||||
absolutePathOnTheVolumeMount, err := securejoin.SecureJoin(mountPoint, pathRelativeToVolume)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return mountPoint, absolutePathOnTheVolumeMount, nil
|
||||
}
|
||||
|
||||
if mount := findBindMount(container, searchPath); mount != nil {
|
||||
logrus.Debugf("Container path %q resolved to bind mount %q:%q on path %q", containerPath, mount.Source, mount.Destination, searchPath)
|
||||
// We found a matching bind mount for searchPath. We
|
||||
// now need to first find the relative path of our
|
||||
// input path to the searchPath, and then join it with
|
||||
// the source of the bind mount.
|
||||
pathRelativeToBindMount := strings.TrimPrefix(pathRelativeToContainerMountPoint, searchPath)
|
||||
absolutePathOnTheBindMount, err := securejoin.SecureJoin(mount.Source, pathRelativeToBindMount)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return mount.Source, absolutePathOnTheBindMount, nil
|
||||
|
||||
}
|
||||
|
||||
if searchPath == "/" {
|
||||
// Cannot go beyond "/", so we're done.
|
||||
break
|
||||
}
|
||||
|
||||
// Walk *down* the path (e.g., "/foo/bar/x" -> "/foo/bar").
|
||||
searchPath = filepath.Dir(searchPath)
|
||||
}
|
||||
|
||||
// No volume, no bind mount but just a normal path on the container.
|
||||
return mountPoint, resolvedPathOnTheContainerMountPoint, nil
|
||||
}
|
||||
|
||||
// findVolume checks if the specified containerPath matches the destination
|
||||
// path of a Volume. Returns a matching Volume or nil.
|
||||
func findVolume(c *Container, containerPath string) (*Volume, error) {
|
||||
runtime := c.Runtime()
|
||||
cleanedContainerPath := filepath.Clean(containerPath)
|
||||
for _, vol := range c.Config().NamedVolumes {
|
||||
if cleanedContainerPath == filepath.Clean(vol.Dest) {
|
||||
return runtime.GetVolume(vol.Name)
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// isPathOnVolume returns true if the specified containerPath is a subdir of any
|
||||
// Volume's destination.
|
||||
func isPathOnVolume(c *Container, containerPath string) bool {
|
||||
cleanedContainerPath := filepath.Clean(containerPath)
|
||||
for _, vol := range c.Config().NamedVolumes {
|
||||
if cleanedContainerPath == filepath.Clean(vol.Dest) {
|
||||
return true
|
||||
}
|
||||
for dest := vol.Dest; dest != "/"; dest = filepath.Dir(dest) {
|
||||
if cleanedContainerPath == dest {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// findBindMounts checks if the specified containerPath matches the destination
|
||||
// path of a Mount. Returns a matching Mount or nil.
|
||||
func findBindMount(c *Container, containerPath string) *specs.Mount {
|
||||
cleanedPath := filepath.Clean(containerPath)
|
||||
for _, m := range c.Config().Spec.Mounts {
|
||||
if m.Type != "bind" {
|
||||
continue
|
||||
}
|
||||
if cleanedPath == filepath.Clean(m.Destination) {
|
||||
mount := m
|
||||
return &mount
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/// isPathOnBindMount returns true if the specified containerPath is a subdir of any
|
||||
// Mount's destination.
|
||||
func isPathOnBindMount(c *Container, containerPath string) bool {
|
||||
cleanedContainerPath := filepath.Clean(containerPath)
|
||||
for _, m := range c.Config().Spec.Mounts {
|
||||
if cleanedContainerPath == filepath.Clean(m.Destination) {
|
||||
return true
|
||||
}
|
||||
for dest := m.Destination; dest != "/"; dest = filepath.Dir(dest) {
|
||||
if cleanedContainerPath == dest {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
@ -955,7 +955,7 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e
|
|||
if c.state.NetNS == nil {
|
||||
// We still want to make dummy configurations for each CNI net
|
||||
// the container joined.
|
||||
if len(networks) > 0 && !isDefault {
|
||||
if len(networks) > 0 {
|
||||
settings.Networks = make(map[string]*define.InspectAdditionalNetwork, len(networks))
|
||||
for _, net := range networks {
|
||||
cniNet := new(define.InspectAdditionalNetwork)
|
||||
|
|
@ -976,7 +976,7 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e
|
|||
}
|
||||
|
||||
// If we have CNI networks - handle that here
|
||||
if len(networks) > 0 && !isDefault {
|
||||
if len(networks) > 0 {
|
||||
if len(networks) != len(c.state.NetworkStatus) {
|
||||
return nil, errors.Wrapf(define.ErrInternal, "network inspection mismatch: asked to join %d CNI network(s) %v, but have information on %d network(s)", len(networks), networks, len(c.state.NetworkStatus))
|
||||
}
|
||||
|
|
@ -1006,7 +1006,9 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e
|
|||
settings.Networks[name] = addedNet
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
if !isDefault {
|
||||
return settings, nil
|
||||
}
|
||||
}
|
||||
|
||||
// If not joining networks, we should have at most 1 result
|
||||
|
|
|
|||
|
|
@ -126,20 +126,25 @@ func (r *ConmonOCIRuntime) ExecContainerHTTP(ctr *Container, sessionID string, o
|
|||
}()
|
||||
|
||||
attachChan := make(chan error)
|
||||
conmonPipeDataChan := make(chan conmonPipeData)
|
||||
go func() {
|
||||
// attachToExec is responsible for closing pipes
|
||||
attachChan <- attachExecHTTP(ctr, sessionID, req, w, streams, pipes, detachKeys, options.Terminal, cancel, hijackDone, holdConnOpen)
|
||||
attachChan <- attachExecHTTP(ctr, sessionID, req, w, streams, pipes, detachKeys, options.Terminal, cancel, hijackDone, holdConnOpen, execCmd, conmonPipeDataChan, ociLog)
|
||||
close(attachChan)
|
||||
}()
|
||||
|
||||
// Wait for conmon to succeed, when return.
|
||||
if err := execCmd.Wait(); err != nil {
|
||||
return -1, nil, errors.Wrapf(err, "cannot run conmon")
|
||||
}
|
||||
// NOTE: the channel is needed to communicate conmon's data. In case
|
||||
// of an error, the error will be written on the hijacked http
|
||||
// connection such that remote clients will receive the error.
|
||||
pipeData := <-conmonPipeDataChan
|
||||
|
||||
pid, err := readConmonPipeData(pipes.syncPipe, ociLog)
|
||||
return pipeData.pid, attachChan, pipeData.err
|
||||
}
|
||||
|
||||
return pid, attachChan, err
|
||||
// conmonPipeData contains the data when reading from conmon's pipe.
|
||||
type conmonPipeData struct {
|
||||
pid int
|
||||
err error
|
||||
}
|
||||
|
||||
// ExecContainerDetached executes a command in a running container, but does
|
||||
|
|
@ -488,9 +493,16 @@ func (r *ConmonOCIRuntime) startExec(c *Container, sessionID string, options *Ex
|
|||
}
|
||||
|
||||
// Attach to a container over HTTP
|
||||
func attachExecHTTP(c *Container, sessionID string, r *http.Request, w http.ResponseWriter, streams *HTTPAttachStreams, pipes *execPipes, detachKeys []byte, isTerminal bool, cancel <-chan bool, hijackDone chan<- bool, holdConnOpen <-chan bool) (deferredErr error) {
|
||||
func attachExecHTTP(c *Container, sessionID string, r *http.Request, w http.ResponseWriter, streams *HTTPAttachStreams, pipes *execPipes, detachKeys []byte, isTerminal bool, cancel <-chan bool, hijackDone chan<- bool, holdConnOpen <-chan bool, execCmd *exec.Cmd, conmonPipeDataChan chan<- conmonPipeData, ociLog string) (deferredErr error) {
|
||||
// NOTE: As you may notice, the attach code is quite complex.
|
||||
// Many things happen concurrently and yet are interdependent.
|
||||
// If you ever change this function, make sure to write to the
|
||||
// conmonPipeDataChan in case of an error.
|
||||
|
||||
if pipes == nil || pipes.startPipe == nil || pipes.attachPipe == nil {
|
||||
return errors.Wrapf(define.ErrInvalidArg, "must provide a start and attach pipe to finish an exec attach")
|
||||
err := errors.Wrapf(define.ErrInvalidArg, "must provide a start and attach pipe to finish an exec attach")
|
||||
conmonPipeDataChan <- conmonPipeData{-1, err}
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
|
|
@ -509,17 +521,20 @@ func attachExecHTTP(c *Container, sessionID string, r *http.Request, w http.Resp
|
|||
// set up the socket path, such that it is the correct length and location for exec
|
||||
sockPath, err := c.execAttachSocketPath(sessionID)
|
||||
if err != nil {
|
||||
conmonPipeDataChan <- conmonPipeData{-1, err}
|
||||
return err
|
||||
}
|
||||
|
||||
// 2: read from attachFd that the parent process has set up the console socket
|
||||
if _, err := readConmonPipeData(pipes.attachPipe, ""); err != nil {
|
||||
conmonPipeDataChan <- conmonPipeData{-1, err}
|
||||
return err
|
||||
}
|
||||
|
||||
// 2: then attach
|
||||
conn, err := openUnixSocket(sockPath)
|
||||
if err != nil {
|
||||
conmonPipeDataChan <- conmonPipeData{-1, err}
|
||||
return errors.Wrapf(err, "failed to connect to container's attach socket: %v", sockPath)
|
||||
}
|
||||
defer func() {
|
||||
|
|
@ -540,11 +555,13 @@ func attachExecHTTP(c *Container, sessionID string, r *http.Request, w http.Resp
|
|||
// Perform hijack
|
||||
hijacker, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
conmonPipeDataChan <- conmonPipeData{-1, err}
|
||||
return errors.Errorf("unable to hijack connection")
|
||||
}
|
||||
|
||||
httpCon, httpBuf, err := hijacker.Hijack()
|
||||
if err != nil {
|
||||
conmonPipeDataChan <- conmonPipeData{-1, err}
|
||||
return errors.Wrapf(err, "error hijacking connection")
|
||||
}
|
||||
|
||||
|
|
@ -555,10 +572,23 @@ func attachExecHTTP(c *Container, sessionID string, r *http.Request, w http.Resp
|
|||
|
||||
// Force a flush after the header is written.
|
||||
if err := httpBuf.Flush(); err != nil {
|
||||
conmonPipeDataChan <- conmonPipeData{-1, err}
|
||||
return errors.Wrapf(err, "error flushing HTTP hijack header")
|
||||
}
|
||||
|
||||
go func() {
|
||||
// Wait for conmon to succeed, when return.
|
||||
if err := execCmd.Wait(); err != nil {
|
||||
conmonPipeDataChan <- conmonPipeData{-1, err}
|
||||
} else {
|
||||
pid, err := readConmonPipeData(pipes.syncPipe, ociLog)
|
||||
if err != nil {
|
||||
hijackWriteError(err, c.ID(), isTerminal, httpBuf)
|
||||
conmonPipeDataChan <- conmonPipeData{pid, err}
|
||||
} else {
|
||||
conmonPipeDataChan <- conmonPipeData{pid, err}
|
||||
}
|
||||
}
|
||||
// We need to hold the connection open until the complete exec
|
||||
// function has finished. This channel will be closed in a defer
|
||||
// in that function, so we can wait for it here.
|
||||
|
|
|
|||
|
|
@ -180,6 +180,13 @@ func newRuntimeFromConfig(ctx context.Context, conf *config.Config, options ...R
|
|||
}
|
||||
}
|
||||
|
||||
if err := shutdown.Register("libpod", func(sig os.Signal) error {
|
||||
os.Exit(1)
|
||||
return nil
|
||||
}); err != nil && errors.Cause(err) != shutdown.ErrHandlerExists {
|
||||
logrus.Errorf("Error registering shutdown handler for libpod: %v", err)
|
||||
}
|
||||
|
||||
if err := shutdown.Start(); err != nil {
|
||||
return nil, errors.Wrapf(err, "error starting shutdown signal handler")
|
||||
}
|
||||
|
|
@ -188,13 +195,6 @@ func newRuntimeFromConfig(ctx context.Context, conf *config.Config, options ...R
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if err := shutdown.Register("libpod", func(sig os.Signal) error {
|
||||
os.Exit(1)
|
||||
return nil
|
||||
}); err != nil && errors.Cause(err) != shutdown.ErrHandlerExists {
|
||||
logrus.Errorf("Error registering shutdown handler for libpod: %v", err)
|
||||
}
|
||||
|
||||
return runtime, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ var (
|
|||
stopped bool
|
||||
sigChan chan os.Signal
|
||||
cancelChan chan bool
|
||||
// Syncronize accesses to the map
|
||||
handlerLock sync.Mutex
|
||||
// Definitions of all on-shutdown handlers
|
||||
handlers map[string]func(os.Signal) error
|
||||
// Ordering that on-shutdown handlers will be invoked.
|
||||
|
|
@ -50,6 +52,7 @@ func Start() error {
|
|||
case sig := <-sigChan:
|
||||
logrus.Infof("Received shutdown signal %v, terminating!", sig)
|
||||
shutdownInhibit.Lock()
|
||||
handlerLock.Lock()
|
||||
for _, name := range handlerOrder {
|
||||
handler, ok := handlers[name]
|
||||
if !ok {
|
||||
|
|
@ -61,6 +64,7 @@ func Start() error {
|
|||
logrus.Errorf("Error running shutdown handler %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
handlerLock.Unlock()
|
||||
shutdownInhibit.Unlock()
|
||||
return
|
||||
}
|
||||
|
|
@ -97,6 +101,9 @@ func Uninhibit() {
|
|||
// by a signal. Handlers are invoked LIFO - the last handler registered is the
|
||||
// first run.
|
||||
func Register(name string, handler func(os.Signal) error) error {
|
||||
handlerLock.Lock()
|
||||
defer handlerLock.Unlock()
|
||||
|
||||
if handlers == nil {
|
||||
handlers = make(map[string]func(os.Signal) error)
|
||||
}
|
||||
|
|
@ -113,6 +120,9 @@ func Register(name string, handler func(os.Signal) error) error {
|
|||
|
||||
// Unregister un-registers a given shutdown handler.
|
||||
func Unregister(name string) error {
|
||||
handlerLock.Lock()
|
||||
defer handlerLock.Unlock()
|
||||
|
||||
if handlers == nil {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -235,20 +235,16 @@ func checkDependencyContainer(depCtr, ctr *Container) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// hijackWriteErrorAndClose writes an error to a hijacked HTTP session and
|
||||
// closes it. Intended to HTTPAttach function.
|
||||
// If error is nil, it will not be written; we'll only close the connection.
|
||||
func hijackWriteErrorAndClose(toWrite error, cid string, terminal bool, httpCon io.Closer, httpBuf *bufio.ReadWriter) {
|
||||
// hijackWriteError writes an error to a hijacked HTTP session.
|
||||
func hijackWriteError(toWrite error, cid string, terminal bool, httpBuf *bufio.ReadWriter) {
|
||||
if toWrite != nil {
|
||||
errString := []byte(fmt.Sprintf("%v\n", toWrite))
|
||||
errString := []byte(fmt.Sprintf("Error: %v\n", toWrite))
|
||||
if !terminal {
|
||||
// We need a header.
|
||||
header := makeHTTPAttachHeader(2, uint32(len(errString)))
|
||||
if _, err := httpBuf.Write(header); err != nil {
|
||||
logrus.Errorf("Error writing header for container %s attach connection error: %v", cid, err)
|
||||
}
|
||||
// TODO: May want to return immediately here to avoid
|
||||
// writing garbage to the socket?
|
||||
}
|
||||
if _, err := httpBuf.Write(errString); err != nil {
|
||||
logrus.Errorf("Error writing error to container %s HTTP attach connection: %v", cid, err)
|
||||
|
|
@ -257,6 +253,13 @@ func hijackWriteErrorAndClose(toWrite error, cid string, terminal bool, httpCon
|
|||
logrus.Errorf("Error flushing HTTP buffer for container %s HTTP attach connection: %v", cid, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// hijackWriteErrorAndClose writes an error to a hijacked HTTP session and
|
||||
// closes it. Intended to HTTPAttach function.
|
||||
// If error is nil, it will not be written; we'll only close the connection.
|
||||
func hijackWriteErrorAndClose(toWrite error, cid string, terminal bool, httpCon io.Closer, httpBuf *bufio.ReadWriter) {
|
||||
hijackWriteError(toWrite, cid, terminal, httpBuf)
|
||||
|
||||
if err := httpCon.Close(); err != nil {
|
||||
logrus.Errorf("Error closing container %s HTTP attach connection: %v", cid, err)
|
||||
|
|
|
|||
|
|
@ -179,15 +179,15 @@ func setupSystemd() {
|
|||
func (s *APIServer) Serve() error {
|
||||
setupSystemd()
|
||||
|
||||
// Start the shutdown signal handler.
|
||||
if err := shutdown.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := shutdown.Register("server", func(sig os.Signal) error {
|
||||
return s.Shutdown()
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
// Start the shutdown signal handler.
|
||||
if err := shutdown.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
errChan := make(chan error, 1)
|
||||
|
||||
|
|
|
|||
|
|
@ -26,13 +26,18 @@ func (ic *ContainerEngine) ContainerCopyFromArchive(ctx context.Context, nameOrI
|
|||
return nil, err
|
||||
}
|
||||
|
||||
containerMountPoint, err := container.Mount()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unmount := func() {
|
||||
if err := container.Unmount(false); err != nil {
|
||||
logrus.Errorf("Error unmounting container: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
_, resolvedRoot, resolvedContainerPath, err := ic.containerStat(container, containerPath)
|
||||
_, resolvedRoot, resolvedContainerPath, err := ic.containerStat(container, containerMountPoint, containerPath)
|
||||
if err != nil {
|
||||
unmount()
|
||||
return nil, err
|
||||
|
|
@ -71,6 +76,11 @@ func (ic *ContainerEngine) ContainerCopyToArchive(ctx context.Context, nameOrID
|
|||
return nil, err
|
||||
}
|
||||
|
||||
containerMountPoint, err := container.Mount()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unmount := func() {
|
||||
if err := container.Unmount(false); err != nil {
|
||||
logrus.Errorf("Error unmounting container: %v", err)
|
||||
|
|
@ -83,7 +93,7 @@ func (ic *ContainerEngine) ContainerCopyToArchive(ctx context.Context, nameOrID
|
|||
containerPath = "/."
|
||||
}
|
||||
|
||||
_, resolvedRoot, resolvedContainerPath, err := ic.containerStat(container, containerPath)
|
||||
_, resolvedRoot, resolvedContainerPath, err := ic.containerStat(container, containerMountPoint, containerPath)
|
||||
if err != nil {
|
||||
unmount()
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -10,18 +10,11 @@ import (
|
|||
"github.com/containers/podman/v2/libpod"
|
||||
"github.com/containers/podman/v2/pkg/copy"
|
||||
"github.com/containers/podman/v2/pkg/domain/entities"
|
||||
securejoin "github.com/cyphar/filepath-securejoin"
|
||||
"github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (ic *ContainerEngine) containerStat(container *libpod.Container, containerPath string) (*entities.ContainerStatReport, string, string, error) {
|
||||
containerMountPoint, err := container.Mount()
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
}
|
||||
|
||||
func (ic *ContainerEngine) containerStat(container *libpod.Container, containerMountPoint string, containerPath string) (*entities.ContainerStatReport, string, string, error) {
|
||||
// Make sure that "/" copies the *contents* of the mount point and not
|
||||
// the directory.
|
||||
if containerPath == "/" {
|
||||
|
|
@ -30,7 +23,7 @@ func (ic *ContainerEngine) containerStat(container *libpod.Container, containerP
|
|||
|
||||
// Now resolve the container's path. It may hit a volume, it may hit a
|
||||
// bind mount, it may be relative.
|
||||
resolvedRoot, resolvedContainerPath, err := resolveContainerPaths(container, containerMountPoint, containerPath)
|
||||
resolvedRoot, resolvedContainerPath, err := container.ResolvePath(context.Background(), containerMountPoint, containerPath)
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
}
|
||||
|
|
@ -94,138 +87,21 @@ func (ic *ContainerEngine) ContainerStat(ctx context.Context, nameOrID string, c
|
|||
return nil, err
|
||||
}
|
||||
|
||||
containerMountPoint, err := container.Mount()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := container.Unmount(false); err != nil {
|
||||
logrus.Errorf("Error unmounting container: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
statReport, _, _, err := ic.containerStat(container, containerPath)
|
||||
statReport, _, _, err := ic.containerStat(container, containerMountPoint, containerPath)
|
||||
return statReport, err
|
||||
}
|
||||
|
||||
// resolveContainerPaths resolves the container's mount point and the container
|
||||
// path as specified by the user. Both may resolve to paths outside of the
|
||||
// container's mount point when the container path hits a volume or bind mount.
|
||||
//
|
||||
// NOTE: We must take volumes and bind mounts into account as, regrettably, we
|
||||
// can copy to/from stopped containers. In that case, the volumes and bind
|
||||
// mounts are not present. For running containers, the runtime (e.g., runc or
|
||||
// crun) takes care of these mounts. For stopped ones, we need to do quite
|
||||
// some dance, as done below.
|
||||
func resolveContainerPaths(container *libpod.Container, mountPoint string, containerPath string) (string, string, error) {
|
||||
// Let's first make sure we have a path relative to the mount point.
|
||||
pathRelativeToContainerMountPoint := containerPath
|
||||
if !filepath.IsAbs(containerPath) {
|
||||
// If the containerPath is not absolute, it's relative to the
|
||||
// container's working dir. To be extra careful, let's first
|
||||
// join the working dir with "/", and the add the containerPath
|
||||
// to it.
|
||||
pathRelativeToContainerMountPoint = filepath.Join(filepath.Join("/", container.WorkingDir()), containerPath)
|
||||
}
|
||||
resolvedPathOnTheContainerMountPoint := filepath.Join(mountPoint, pathRelativeToContainerMountPoint)
|
||||
pathRelativeToContainerMountPoint = strings.TrimPrefix(pathRelativeToContainerMountPoint, mountPoint)
|
||||
pathRelativeToContainerMountPoint = filepath.Join("/", pathRelativeToContainerMountPoint)
|
||||
|
||||
// Now we have an "absolute container Path" but not yet resolved on the
|
||||
// host (e.g., "/foo/bar/file.txt"). As mentioned above, we need to
|
||||
// check if "/foo/bar/file.txt" is on a volume or bind mount. To do
|
||||
// that, we need to walk *down* the paths to the root. Assuming
|
||||
// volume-1 is mounted to "/foo" and volume-2 is mounted to "/foo/bar",
|
||||
// we must select "/foo/bar". Once selected, we need to rebase the
|
||||
// remainder (i.e, "/file.txt") on the volume's mount point on the
|
||||
// host. Same applies to bind mounts.
|
||||
|
||||
searchPath := pathRelativeToContainerMountPoint
|
||||
for {
|
||||
volume, err := findVolume(container, searchPath)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if volume != nil {
|
||||
logrus.Debugf("Container path %q resolved to volume %q on path %q", containerPath, volume.Name(), searchPath)
|
||||
|
||||
// TODO: We really need to force the volume to mount
|
||||
// before doing this, but that API is not exposed
|
||||
// externally right now and doing so is beyond the scope
|
||||
// of this commit.
|
||||
mountPoint, err := volume.MountPoint()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if mountPoint == "" {
|
||||
return "", "", errors.Errorf("volume %s is not mounted, cannot copy into it", volume.Name())
|
||||
}
|
||||
|
||||
// We found a matching volume for searchPath. We now
|
||||
// need to first find the relative path of our input
|
||||
// path to the searchPath, and then join it with the
|
||||
// volume's mount point.
|
||||
pathRelativeToVolume := strings.TrimPrefix(pathRelativeToContainerMountPoint, searchPath)
|
||||
absolutePathOnTheVolumeMount, err := securejoin.SecureJoin(mountPoint, pathRelativeToVolume)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return mountPoint, absolutePathOnTheVolumeMount, nil
|
||||
}
|
||||
|
||||
if mount := findBindMount(container, searchPath); mount != nil {
|
||||
logrus.Debugf("Container path %q resolved to bind mount %q:%q on path %q", containerPath, mount.Source, mount.Destination, searchPath)
|
||||
// We found a matching bind mount for searchPath. We
|
||||
// now need to first find the relative path of our
|
||||
// input path to the searchPath, and then join it with
|
||||
// the source of the bind mount.
|
||||
pathRelativeToBindMount := strings.TrimPrefix(pathRelativeToContainerMountPoint, searchPath)
|
||||
absolutePathOnTheBindMount, err := securejoin.SecureJoin(mount.Source, pathRelativeToBindMount)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return mount.Source, absolutePathOnTheBindMount, nil
|
||||
|
||||
}
|
||||
|
||||
if searchPath == "/" {
|
||||
// Cannot go beyond "/", so we're done.
|
||||
break
|
||||
}
|
||||
|
||||
// Walk *down* the path (e.g., "/foo/bar/x" -> "/foo/bar").
|
||||
searchPath = filepath.Dir(searchPath)
|
||||
}
|
||||
|
||||
// No volume, no bind mount but just a normal path on the container.
|
||||
return mountPoint, resolvedPathOnTheContainerMountPoint, nil
|
||||
}
|
||||
|
||||
// findVolume checks if the specified container path matches a volume inside
|
||||
// the container. It returns a matching volume or nil.
|
||||
func findVolume(c *libpod.Container, containerPath string) (*libpod.Volume, error) {
|
||||
runtime := c.Runtime()
|
||||
cleanedContainerPath := filepath.Clean(containerPath)
|
||||
for _, vol := range c.Config().NamedVolumes {
|
||||
if cleanedContainerPath == filepath.Clean(vol.Dest) {
|
||||
return runtime.GetVolume(vol.Name)
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// findBindMount checks if the specified container path matches a bind mount
|
||||
// inside the container. It returns a matching mount or nil.
|
||||
func findBindMount(c *libpod.Container, containerPath string) *specs.Mount {
|
||||
cleanedPath := filepath.Clean(containerPath)
|
||||
for _, m := range c.Config().Spec.Mounts {
|
||||
if m.Type != "bind" {
|
||||
continue
|
||||
}
|
||||
if cleanedPath == filepath.Clean(m.Destination) {
|
||||
mount := m
|
||||
return &mount
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// secureStat extracts file info for path in a chroot'ed environment in root.
|
||||
func secureStat(root string, path string) (*buildahCopiah.StatForItem, error) {
|
||||
var glob string
|
||||
|
|
|
|||
|
|
@ -203,20 +203,6 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat
|
|||
}
|
||||
s.Annotations = annotations
|
||||
|
||||
// workdir
|
||||
if s.WorkDir == "" {
|
||||
if newImage != nil {
|
||||
workingDir, err := newImage.WorkingDir(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.WorkDir = workingDir
|
||||
}
|
||||
}
|
||||
if s.WorkDir == "" {
|
||||
s.WorkDir = "/"
|
||||
}
|
||||
|
||||
if len(s.SeccompProfilePath) < 1 {
|
||||
p, err := libpod.DefaultSeccompPath()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -272,10 +272,18 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.
|
|||
if s.Entrypoint != nil {
|
||||
options = append(options, libpod.WithEntrypoint(s.Entrypoint))
|
||||
}
|
||||
// If the user did not set an workdir but the image did, ensure it is
|
||||
// created.
|
||||
// If the user did not specify a workdir on the CLI, let's extract it
|
||||
// from the image.
|
||||
if s.WorkDir == "" && img != nil {
|
||||
options = append(options, libpod.WithCreateWorkingDir())
|
||||
wd, err := img.WorkingDir(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.WorkDir = wd
|
||||
}
|
||||
if s.WorkDir == "" {
|
||||
s.WorkDir = "/"
|
||||
}
|
||||
if s.StopSignal != nil {
|
||||
options = append(options, libpod.WithStopSignal(*s.StopSignal))
|
||||
|
|
|
|||
|
|
@ -30,14 +30,14 @@ func validateRestartPolicy(restart string) error {
|
|||
return errors.Errorf("%s is not a valid restart policy", restart)
|
||||
}
|
||||
|
||||
const headerTemplate = `# {{.ServiceName}}.service
|
||||
# autogenerated by Podman {{.PodmanVersion}}
|
||||
{{- if .TimeStamp}}
|
||||
# {{.TimeStamp}}
|
||||
{{- end}}
|
||||
const headerTemplate = `# {{{{.ServiceName}}}}.service
|
||||
# autogenerated by Podman {{{{.PodmanVersion}}}}
|
||||
{{{{- if .TimeStamp}}}}
|
||||
# {{{{.TimeStamp}}}}
|
||||
{{{{- end}}}}
|
||||
|
||||
[Unit]
|
||||
Description=Podman {{.ServiceName}}.service
|
||||
Description=Podman {{{{.ServiceName}}}}.service
|
||||
Documentation=man:podman-generate-systemd(1)
|
||||
Wants=network.target
|
||||
After=network-online.target
|
||||
|
|
|
|||
|
|
@ -72,22 +72,22 @@ type containerInfo struct {
|
|||
}
|
||||
|
||||
const containerTemplate = headerTemplate + `
|
||||
{{- if .BoundToServices}}
|
||||
BindsTo={{- range $index, $value := .BoundToServices -}}{{if $index}} {{end}}{{ $value }}.service{{end}}
|
||||
After={{- range $index, $value := .BoundToServices -}}{{if $index}} {{end}}{{ $value }}.service{{end}}
|
||||
{{- end}}
|
||||
{{{{- if .BoundToServices}}}}
|
||||
BindsTo={{{{- range $index, $value := .BoundToServices -}}}}{{{{if $index}}}} {{{{end}}}}{{{{ $value }}}}.service{{{{end}}}}
|
||||
After={{{{- range $index, $value := .BoundToServices -}}}}{{{{if $index}}}} {{{{end}}}}{{{{ $value }}}}.service{{{{end}}}}
|
||||
{{{{- end}}}}
|
||||
|
||||
[Service]
|
||||
Environment={{.EnvVariable}}=%n
|
||||
Restart={{.RestartPolicy}}
|
||||
TimeoutStopSec={{.TimeoutStopSec}}
|
||||
{{- if .ExecStartPre}}
|
||||
ExecStartPre={{.ExecStartPre}}
|
||||
{{- end}}
|
||||
ExecStart={{.ExecStart}}
|
||||
ExecStop={{.ExecStop}}
|
||||
ExecStopPost={{.ExecStopPost}}
|
||||
PIDFile={{.PIDFile}}
|
||||
Environment={{{{.EnvVariable}}}}=%n
|
||||
Restart={{{{.RestartPolicy}}}}
|
||||
TimeoutStopSec={{{{.TimeoutStopSec}}}}
|
||||
{{{{- if .ExecStartPre}}}}
|
||||
ExecStartPre={{{{.ExecStartPre}}}}
|
||||
{{{{- end}}}}
|
||||
ExecStart={{{{.ExecStart}}}}
|
||||
ExecStop={{{{.ExecStop}}}}
|
||||
ExecStopPost={{{{.ExecStopPost}}}}
|
||||
PIDFile={{{{.PIDFile}}}}
|
||||
Type=forking
|
||||
|
||||
[Install]
|
||||
|
|
@ -173,9 +173,9 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst
|
|||
}
|
||||
|
||||
info.EnvVariable = EnvVariable
|
||||
info.ExecStart = "{{.Executable}} start {{.ContainerNameOrID}}"
|
||||
info.ExecStop = "{{.Executable}} stop {{if (ge .StopTimeout 0)}}-t {{.StopTimeout}}{{end}} {{.ContainerNameOrID}}"
|
||||
info.ExecStopPost = "{{.Executable}} stop {{if (ge .StopTimeout 0)}}-t {{.StopTimeout}}{{end}} {{.ContainerNameOrID}}"
|
||||
info.ExecStart = "{{{{.Executable}}}} start {{{{.ContainerNameOrID}}}}"
|
||||
info.ExecStop = "{{{{.Executable}}}} stop {{{{if (ge .StopTimeout 0)}}}}-t {{{{.StopTimeout}}}}{{{{end}}}} {{{{.ContainerNameOrID}}}}"
|
||||
info.ExecStopPost = "{{{{.Executable}}}} stop {{{{if (ge .StopTimeout 0)}}}}-t {{{{.StopTimeout}}}}{{{{end}}}} {{{{.ContainerNameOrID}}}}"
|
||||
|
||||
// Assemble the ExecStart command when creating a new container.
|
||||
//
|
||||
|
|
@ -209,8 +209,8 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst
|
|||
}
|
||||
startCommand = append(startCommand,
|
||||
"run",
|
||||
"--conmon-pidfile", "{{.PIDFile}}",
|
||||
"--cidfile", "{{.ContainerIDFile}}",
|
||||
"--conmon-pidfile", "{{{{.PIDFile}}}}",
|
||||
"--cidfile", "{{{{.ContainerIDFile}}}}",
|
||||
"--cgroups=no-conmon",
|
||||
)
|
||||
// If the container is in a pod, make sure that the
|
||||
|
|
@ -281,10 +281,10 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst
|
|||
startCommand = append(startCommand, remainingCmd...)
|
||||
startCommand = quoteArguments(startCommand)
|
||||
|
||||
info.ExecStartPre = "/bin/rm -f {{.PIDFile}} {{.ContainerIDFile}}"
|
||||
info.ExecStartPre = "/bin/rm -f {{{{.PIDFile}}}} {{{{.ContainerIDFile}}}}"
|
||||
info.ExecStart = strings.Join(startCommand, " ")
|
||||
info.ExecStop = "{{.Executable}} {{if .RootFlags}}{{ .RootFlags}} {{end}}stop --ignore --cidfile {{.ContainerIDFile}} {{if (ge .StopTimeout 0)}}-t {{.StopTimeout}}{{end}}"
|
||||
info.ExecStopPost = "{{.Executable}} {{if .RootFlags}}{{ .RootFlags}} {{end}}rm --ignore -f --cidfile {{.ContainerIDFile}}"
|
||||
info.ExecStop = "{{{{.Executable}}}} {{{{if .RootFlags}}}}{{{{ .RootFlags}}}} {{{{end}}}}stop --ignore --cidfile {{{{.ContainerIDFile}}}} {{{{if (ge .StopTimeout 0)}}}}-t {{{{.StopTimeout}}}}{{{{end}}}}"
|
||||
info.ExecStopPost = "{{{{.Executable}}}} {{{{if .RootFlags}}}}{{{{ .RootFlags}}}} {{{{end}}}}rm --ignore -f --cidfile {{{{.ContainerIDFile}}}}"
|
||||
}
|
||||
|
||||
info.TimeoutStopSec = minTimeoutStopSec + info.StopTimeout
|
||||
|
|
@ -307,7 +307,7 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst
|
|||
// generation. That's especially needed for embedding the PID and ID
|
||||
// files in other fields which will eventually get replaced in the 2nd
|
||||
// template execution.
|
||||
templ, err := template.New("container_template").Parse(containerTemplate)
|
||||
templ, err := template.New("container_template").Delims("{{{{", "}}}}").Parse(containerTemplate)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "error parsing systemd service template")
|
||||
}
|
||||
|
|
@ -318,7 +318,7 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst
|
|||
}
|
||||
|
||||
// Now parse the generated template (i.e., buf) and execute it.
|
||||
templ, err = template.New("container_template").Parse(buf.String())
|
||||
templ, err = template.New("container_template").Delims("{{{{", "}}}}").Parse(buf.String())
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "error parsing systemd service template")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -329,6 +329,29 @@ Type=forking
|
|||
WantedBy=multi-user.target default.target
|
||||
`
|
||||
|
||||
goodNewWithJournaldTag := `# jadda-jadda.service
|
||||
# autogenerated by Podman CI
|
||||
|
||||
[Unit]
|
||||
Description=Podman jadda-jadda.service
|
||||
Documentation=man:podman-generate-systemd(1)
|
||||
Wants=network.target
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Environment=PODMAN_SYSTEMD_UNIT=%n
|
||||
Restart=always
|
||||
TimeoutStopSec=70
|
||||
ExecStartPre=/bin/rm -f %t/jadda-jadda.pid %t/jadda-jadda.ctr-id
|
||||
ExecStart=/usr/bin/podman run --conmon-pidfile %t/jadda-jadda.pid --cidfile %t/jadda-jadda.ctr-id --cgroups=no-conmon -d --replace --name test --log-driver=journald --log-opt=tag={{.Name}} awesome-image:latest
|
||||
ExecStop=/usr/bin/podman stop --ignore --cidfile %t/jadda-jadda.ctr-id -t 10
|
||||
ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/jadda-jadda.ctr-id
|
||||
PIDFile=%t/jadda-jadda.pid
|
||||
Type=forking
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target default.target
|
||||
`
|
||||
tests := []struct {
|
||||
name string
|
||||
info containerInfo
|
||||
|
|
@ -608,6 +631,22 @@ WantedBy=multi-user.target default.target
|
|||
true,
|
||||
false,
|
||||
},
|
||||
{"good with journald log tag (see #9034)",
|
||||
containerInfo{
|
||||
Executable: "/usr/bin/podman",
|
||||
ServiceName: "jadda-jadda",
|
||||
ContainerNameOrID: "jadda-jadda",
|
||||
RestartPolicy: "always",
|
||||
PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid",
|
||||
StopTimeout: 10,
|
||||
PodmanVersion: "CI",
|
||||
CreateCommand: []string{"I'll get stripped", "create", "--name", "test", "--log-driver=journald", "--log-opt=tag={{.Name}}", "awesome-image:latest"},
|
||||
EnvVariable: EnvVariable,
|
||||
},
|
||||
goodNewWithJournaldTag,
|
||||
true,
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
test := tt
|
||||
|
|
|
|||
|
|
@ -72,23 +72,23 @@ type podInfo struct {
|
|||
ExecStopPost string
|
||||
}
|
||||
|
||||
const podTemplate = headerTemplate + `Requires={{- range $index, $value := .RequiredServices -}}{{if $index}} {{end}}{{ $value }}.service{{end}}
|
||||
Before={{- range $index, $value := .RequiredServices -}}{{if $index}} {{end}}{{ $value }}.service{{end}}
|
||||
const podTemplate = headerTemplate + `Requires={{{{- range $index, $value := .RequiredServices -}}}}{{{{if $index}}}} {{{{end}}}}{{{{ $value }}}}.service{{{{end}}}}
|
||||
Before={{{{- range $index, $value := .RequiredServices -}}}}{{{{if $index}}}} {{{{end}}}}{{{{ $value }}}}.service{{{{end}}}}
|
||||
|
||||
[Service]
|
||||
Environment={{.EnvVariable}}=%n
|
||||
Restart={{.RestartPolicy}}
|
||||
TimeoutStopSec={{.TimeoutStopSec}}
|
||||
{{- if .ExecStartPre1}}
|
||||
ExecStartPre={{.ExecStartPre1}}
|
||||
{{- end}}
|
||||
{{- if .ExecStartPre2}}
|
||||
ExecStartPre={{.ExecStartPre2}}
|
||||
{{- end}}
|
||||
ExecStart={{.ExecStart}}
|
||||
ExecStop={{.ExecStop}}
|
||||
ExecStopPost={{.ExecStopPost}}
|
||||
PIDFile={{.PIDFile}}
|
||||
Environment={{{{.EnvVariable}}}}=%n
|
||||
Restart={{{{.RestartPolicy}}}}
|
||||
TimeoutStopSec={{{{.TimeoutStopSec}}}}
|
||||
{{{{- if .ExecStartPre1}}}}
|
||||
ExecStartPre={{{{.ExecStartPre1}}}}
|
||||
{{{{- end}}}}
|
||||
{{{{- if .ExecStartPre2}}}}
|
||||
ExecStartPre={{{{.ExecStartPre2}}}}
|
||||
{{{{- end}}}}
|
||||
ExecStart={{{{.ExecStart}}}}
|
||||
ExecStop={{{{.ExecStop}}}}
|
||||
ExecStopPost={{{{.ExecStopPost}}}}
|
||||
PIDFile={{{{.PIDFile}}}}
|
||||
Type=forking
|
||||
|
||||
[Install]
|
||||
|
|
@ -236,9 +236,9 @@ func executePodTemplate(info *podInfo, options entities.GenerateSystemdOptions)
|
|||
}
|
||||
|
||||
info.EnvVariable = EnvVariable
|
||||
info.ExecStart = "{{.Executable}} start {{.InfraNameOrID}}"
|
||||
info.ExecStop = "{{.Executable}} stop {{if (ge .StopTimeout 0)}}-t {{.StopTimeout}}{{end}} {{.InfraNameOrID}}"
|
||||
info.ExecStopPost = "{{.Executable}} stop {{if (ge .StopTimeout 0)}}-t {{.StopTimeout}}{{end}} {{.InfraNameOrID}}"
|
||||
info.ExecStart = "{{{{.Executable}}}} start {{{{.InfraNameOrID}}}}"
|
||||
info.ExecStop = "{{{{.Executable}}}} stop {{{{if (ge .StopTimeout 0)}}}}-t {{{{.StopTimeout}}}}{{{{end}}}} {{{{.InfraNameOrID}}}}"
|
||||
info.ExecStopPost = "{{{{.Executable}}}} stop {{{{if (ge .StopTimeout 0)}}}}-t {{{{.StopTimeout}}}}{{{{end}}}} {{{{.InfraNameOrID}}}}"
|
||||
|
||||
// Assemble the ExecStart command when creating a new pod.
|
||||
//
|
||||
|
|
@ -278,8 +278,8 @@ func executePodTemplate(info *podInfo, options entities.GenerateSystemdOptions)
|
|||
startCommand = append(startCommand, podRootArgs...)
|
||||
startCommand = append(startCommand,
|
||||
[]string{"pod", "create",
|
||||
"--infra-conmon-pidfile", "{{.PIDFile}}",
|
||||
"--pod-id-file", "{{.PodIDFile}}"}...)
|
||||
"--infra-conmon-pidfile", "{{{{.PIDFile}}}}",
|
||||
"--pod-id-file", "{{{{.PodIDFile}}}}"}...)
|
||||
|
||||
// Presence check for certain flags/options.
|
||||
fs := pflag.NewFlagSet("args", pflag.ContinueOnError)
|
||||
|
|
@ -308,11 +308,11 @@ func executePodTemplate(info *podInfo, options entities.GenerateSystemdOptions)
|
|||
startCommand = append(startCommand, podCreateArgs...)
|
||||
startCommand = quoteArguments(startCommand)
|
||||
|
||||
info.ExecStartPre1 = "/bin/rm -f {{.PIDFile}} {{.PodIDFile}}"
|
||||
info.ExecStartPre1 = "/bin/rm -f {{{{.PIDFile}}}} {{{{.PodIDFile}}}}"
|
||||
info.ExecStartPre2 = strings.Join(startCommand, " ")
|
||||
info.ExecStart = "{{.Executable}} {{if .RootFlags}}{{ .RootFlags}} {{end}}pod start --pod-id-file {{.PodIDFile}}"
|
||||
info.ExecStop = "{{.Executable}} {{if .RootFlags}}{{ .RootFlags}} {{end}}pod stop --ignore --pod-id-file {{.PodIDFile}} {{if (ge .StopTimeout 0)}}-t {{.StopTimeout}}{{end}}"
|
||||
info.ExecStopPost = "{{.Executable}} {{if .RootFlags}}{{ .RootFlags}} {{end}}pod rm --ignore -f --pod-id-file {{.PodIDFile}}"
|
||||
info.ExecStart = "{{{{.Executable}}}} {{{{if .RootFlags}}}}{{{{ .RootFlags}}}} {{{{end}}}}pod start --pod-id-file {{{{.PodIDFile}}}}"
|
||||
info.ExecStop = "{{{{.Executable}}}} {{{{if .RootFlags}}}}{{{{ .RootFlags}}}} {{{{end}}}}pod stop --ignore --pod-id-file {{{{.PodIDFile}}}} {{{{if (ge .StopTimeout 0)}}}}-t {{{{.StopTimeout}}}}{{{{end}}}}"
|
||||
info.ExecStopPost = "{{{{.Executable}}}} {{{{if .RootFlags}}}}{{{{ .RootFlags}}}} {{{{end}}}}pod rm --ignore -f --pod-id-file {{{{.PodIDFile}}}}"
|
||||
}
|
||||
info.TimeoutStopSec = minTimeoutStopSec + info.StopTimeout
|
||||
|
||||
|
|
@ -334,7 +334,7 @@ func executePodTemplate(info *podInfo, options entities.GenerateSystemdOptions)
|
|||
// generation. That's especially needed for embedding the PID and ID
|
||||
// files in other fields which will eventually get replaced in the 2nd
|
||||
// template execution.
|
||||
templ, err := template.New("pod_template").Parse(podTemplate)
|
||||
templ, err := template.New("pod_template").Delims("{{{{", "}}}}").Parse(podTemplate)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "error parsing systemd service template")
|
||||
}
|
||||
|
|
@ -345,7 +345,7 @@ func executePodTemplate(info *podInfo, options entities.GenerateSystemdOptions)
|
|||
}
|
||||
|
||||
// Now parse the generated template (i.e., buf) and execute it.
|
||||
templ, err = template.New("pod_template").Parse(buf.String())
|
||||
templ, err = template.New("pod_template").Delims("{{{{", "}}}}").Parse(buf.String())
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "error parsing systemd service template")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,6 +139,33 @@ ExecStopPost=/usr/bin/podman pod rm --ignore -f --pod-id-file %t/pod-123abc.pod-
|
|||
PIDFile=%t/pod-123abc.pid
|
||||
Type=forking
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target default.target
|
||||
`
|
||||
|
||||
podNewLabelWithCurlyBraces := `# pod-123abc.service
|
||||
# autogenerated by Podman CI
|
||||
|
||||
[Unit]
|
||||
Description=Podman pod-123abc.service
|
||||
Documentation=man:podman-generate-systemd(1)
|
||||
Wants=network.target
|
||||
After=network-online.target
|
||||
Requires=container-1.service container-2.service
|
||||
Before=container-1.service container-2.service
|
||||
|
||||
[Service]
|
||||
Environment=PODMAN_SYSTEMD_UNIT=%n
|
||||
Restart=on-failure
|
||||
TimeoutStopSec=70
|
||||
ExecStartPre=/bin/rm -f %t/pod-123abc.pid %t/pod-123abc.pod-id
|
||||
ExecStartPre=/usr/bin/podman pod create --infra-conmon-pidfile %t/pod-123abc.pid --pod-id-file %t/pod-123abc.pod-id --name foo --label key={{someval}} --replace
|
||||
ExecStart=/usr/bin/podman pod start --pod-id-file %t/pod-123abc.pod-id
|
||||
ExecStop=/usr/bin/podman pod stop --ignore --pod-id-file %t/pod-123abc.pod-id -t 10
|
||||
ExecStopPost=/usr/bin/podman pod rm --ignore -f --pod-id-file %t/pod-123abc.pod-id
|
||||
PIDFile=%t/pod-123abc.pid
|
||||
Type=forking
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target default.target
|
||||
`
|
||||
|
|
@ -230,6 +257,22 @@ WantedBy=multi-user.target default.target
|
|||
true,
|
||||
false,
|
||||
},
|
||||
{"pod --new with double curly braces",
|
||||
podInfo{
|
||||
Executable: "/usr/bin/podman",
|
||||
ServiceName: "pod-123abc",
|
||||
InfraNameOrID: "jadda-jadda-infra",
|
||||
RestartPolicy: "on-failure",
|
||||
PIDFile: "/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid",
|
||||
StopTimeout: 10,
|
||||
PodmanVersion: "CI",
|
||||
RequiredServices: []string{"container-1", "container-2"},
|
||||
CreateCommand: []string{"podman", "pod", "create", "--name", "foo", "--label", "key={{someval}}"},
|
||||
},
|
||||
podNewLabelWithCurlyBraces,
|
||||
true,
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
|
|
|||
|
|
@ -237,3 +237,12 @@ t GET containers/$cid/json 200 \
|
|||
t DELETE containers/$cid 204
|
||||
t DELETE images/${MultiTagName}?force=true 200
|
||||
# vim: filetype=sh
|
||||
|
||||
# Test Volumes field adds an anonymous volume
|
||||
t POST containers/create '"Image":"'$IMAGE'","Volumes":{"/test":{}}' 201 \
|
||||
.Id~[0-9a-f]\\{64\\}
|
||||
cid=$(jq -r '.Id' <<<"$output")
|
||||
t GET containers/$cid/json 200 \
|
||||
.Mounts[0].Destination="/test"
|
||||
|
||||
t DELETE containers/$cid?v=true 204
|
||||
|
|
|
|||
|
|
@ -355,4 +355,25 @@ var _ = Describe("Podman generate systemd", func() {
|
|||
Expect(session.ExitCode()).To(Equal(0))
|
||||
Expect(session.IsJSONOutputValid()).To(BeTrue())
|
||||
})
|
||||
|
||||
It("podman generate systemd --new create command with double curly braces", func() {
|
||||
// Regression test for #9034
|
||||
session := podmanTest.Podman([]string{"create", "--name", "foo", "--log-driver=journald", "--log-opt=tag={{.Name}}", ALPINE})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Equal(0))
|
||||
|
||||
session = podmanTest.Podman([]string{"generate", "systemd", "--new", "foo"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Equal(0))
|
||||
Expect(session.OutputToString()).To(ContainSubstring(" --log-opt=tag={{.Name}} "))
|
||||
|
||||
session = podmanTest.Podman([]string{"pod", "create", "--name", "pod", "--label", "key={{someval}}"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Equal(0))
|
||||
|
||||
session = podmanTest.Podman([]string{"generate", "systemd", "--new", "pod"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Equal(0))
|
||||
Expect(session.OutputToString()).To(ContainSubstring(" --label key={{someval}}"))
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -65,6 +65,23 @@ var _ = Describe("Podman history", func() {
|
|||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Equal(0))
|
||||
Expect(len(session.OutputToStringArray())).To(BeNumerically(">", 0))
|
||||
|
||||
session = podmanTest.Podman([]string{"history", "--no-trunc", "--format", "{{.ID}}", ALPINE})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Equal(0))
|
||||
lines := session.OutputToStringArray()
|
||||
Expect(len(lines)).To(BeNumerically(">", 0))
|
||||
// the image id must be 64 chars long
|
||||
Expect(len(lines[0])).To(BeNumerically("==", 64))
|
||||
|
||||
session = podmanTest.Podman([]string{"history", "--no-trunc", "--format", "{{.CreatedBy}}", ALPINE})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Equal(0))
|
||||
lines = session.OutputToStringArray()
|
||||
Expect(len(lines)).To(BeNumerically(">", 0))
|
||||
Expect(session.OutputToString()).ToNot(ContainSubstring("..."))
|
||||
// the second line in the alpine history contains a command longer than 45 chars
|
||||
Expect(len(lines[1])).To(BeNumerically(">", 45))
|
||||
})
|
||||
|
||||
It("podman history with json flag", func() {
|
||||
|
|
|
|||
|
|
@ -443,4 +443,27 @@ var _ = Describe("Podman inspect", func() {
|
|||
Expect(inspect.OutputToString()).To(Equal(`"{"80/tcp":[{"HostIp":"","HostPort":"8080"}]}"`))
|
||||
})
|
||||
|
||||
It("Verify container inspect has default network", func() {
|
||||
SkipIfRootless("Requires root CNI networking")
|
||||
ctrName := "testctr"
|
||||
session := podmanTest.Podman([]string{"run", "-d", "--name", ctrName, ALPINE, "top"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(BeZero())
|
||||
|
||||
inspect := podmanTest.InspectContainer(ctrName)
|
||||
Expect(len(inspect)).To(Equal(1))
|
||||
Expect(len(inspect[0].NetworkSettings.Networks)).To(Equal(1))
|
||||
})
|
||||
|
||||
It("Verify stopped container still has default network in inspect", func() {
|
||||
SkipIfRootless("Requires root CNI networking")
|
||||
ctrName := "testctr"
|
||||
session := podmanTest.Podman([]string{"create", "--name", ctrName, ALPINE, "top"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(BeZero())
|
||||
|
||||
inspect := podmanTest.InspectContainer(ctrName)
|
||||
Expect(len(inspect)).To(Equal(1))
|
||||
Expect(len(inspect[0].NetworkSettings.Networks)).To(Equal(1))
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package integration
|
|||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
. "github.com/containers/podman/v2/test/utils"
|
||||
. "github.com/onsi/ginkgo"
|
||||
|
|
@ -41,12 +40,9 @@ var _ = Describe("Podman run", func() {
|
|||
})
|
||||
|
||||
It("podman run a container using non existing --workdir", func() {
|
||||
if !strings.Contains(podmanTest.OCIRuntime, "crun") {
|
||||
Skip("Test only works on crun")
|
||||
}
|
||||
session := podmanTest.Podman([]string{"run", "--workdir", "/home/foobar", ALPINE, "pwd"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Equal(127))
|
||||
Expect(session.ExitCode()).To(Equal(126))
|
||||
})
|
||||
|
||||
It("podman run a container on an image with a workdir", func() {
|
||||
|
|
|
|||
|
|
@ -589,4 +589,25 @@ json-file | f
|
|||
is "${lines[1]}" "$rand" "Container runs successfully despite warning"
|
||||
}
|
||||
|
||||
@test "podman run - check workdir" {
|
||||
# Workdirs specified via the CLI are not created on the root FS.
|
||||
run_podman 126 run --rm --workdir /i/do/not/exist $IMAGE pwd
|
||||
# Note: remote error prepends an attach error.
|
||||
is "$output" "Error: .*workdir \"/i/do/not/exist\" does not exist on container.*"
|
||||
|
||||
testdir=$PODMAN_TMPDIR/volume
|
||||
mkdir -p $testdir
|
||||
randomcontent=$(random_string 10)
|
||||
echo "$randomcontent" > $testdir/content
|
||||
|
||||
# Workdir does not exist on the image but is volume mounted.
|
||||
run_podman run --rm --workdir /IamNotOnTheImage -v $testdir:/IamNotOnTheImage $IMAGE cat content
|
||||
is "$output" "$randomcontent" "cat random content"
|
||||
|
||||
# Workdir does not exist on the image but is created by the runtime as it's
|
||||
# a subdir of a volume.
|
||||
run_podman run --rm --workdir /IamNotOntheImage -v $testdir/content:/IamNotOntheImage/foo $IMAGE cat foo
|
||||
is "$output" "$randomcontent" "cat random content"
|
||||
}
|
||||
|
||||
# vim: filetype=sh
|
||||
|
|
|
|||
|
|
@ -145,10 +145,12 @@ EOF
|
|||
https_proxy=https-proxy-in-env-file
|
||||
EOF
|
||||
|
||||
# NOTE: it's important to not create the workdir.
|
||||
# Podman will make sure to create a missing workdir
|
||||
# if needed. See #9040.
|
||||
cat >$tmpdir/Containerfile <<EOF
|
||||
FROM $IMAGE
|
||||
LABEL $label_name=$label_value
|
||||
RUN mkdir $workdir
|
||||
WORKDIR $workdir
|
||||
|
||||
# Test for #7094 - chowning of invalid symlinks
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@
|
|||
load helpers
|
||||
|
||||
@test "podman exec - basic test" {
|
||||
skip_if_remote "FIXME: pending #7241"
|
||||
|
||||
rand_filename=$(random_string 20)
|
||||
rand_content=$(random_string 50)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
// NOTE: remember to bump the version at the top
|
||||
// of the top-level README.md file when this is
|
||||
// bumped.
|
||||
var Version = semver.MustParse("3.0.0-rc1")
|
||||
var Version = semver.MustParse("3.0.0-dev")
|
||||
|
||||
// APIVersion is the version for the remote
|
||||
// client API. It is used to determine compatibility
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue