aboutsummaryrefslogtreecommitdiff
path: root/libpod
diff options
context:
space:
mode:
Diffstat (limited to 'libpod')
-rw-r--r--libpod/boltdb_state.go18
-rw-r--r--libpod/container.go69
-rw-r--r--libpod/container_internal_linux.go22
-rw-r--r--libpod/define/pod_inspect.go2
-rw-r--r--libpod/filters/pods.go2
-rw-r--r--libpod/image/image.go28
-rw-r--r--libpod/image/pull.go129
-rw-r--r--libpod/image/pull_test.go67
-rw-r--r--libpod/network/create.go43
-rw-r--r--libpod/networking_linux.go138
-rw-r--r--libpod/pod_api.go2
11 files changed, 310 insertions, 210 deletions
diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go
index be0adfe6a..dcb2ff751 100644
--- a/libpod/boltdb_state.go
+++ b/libpod/boltdb_state.go
@@ -1296,10 +1296,6 @@ func (s *BoltState) NetworkDisconnect(ctr *Container, network string) error {
}
ctrAliasesBkt := dbCtr.Bucket(aliasesBkt)
- if ctrAliasesBkt == nil {
- return errors.Wrapf(define.ErrNoAliases, "container %s has no network aliases", ctr.ID())
- }
-
ctrNetworksBkt := dbCtr.Bucket(networksBkt)
if ctrNetworksBkt == nil {
return errors.Wrapf(define.ErrNoSuchNetwork, "container %s is not connected to any CNI networks, so cannot disconnect", ctr.ID())
@@ -1313,13 +1309,15 @@ func (s *BoltState) NetworkDisconnect(ctr *Container, network string) error {
return errors.Wrapf(err, "error removing container %s from network %s", ctr.ID(), network)
}
- bktExists := ctrAliasesBkt.Bucket([]byte(network))
- if bktExists == nil {
- return nil
- }
+ if ctrAliasesBkt != nil {
+ bktExists := ctrAliasesBkt.Bucket([]byte(network))
+ if bktExists == nil {
+ return nil
+ }
- if err := ctrAliasesBkt.DeleteBucket([]byte(network)); err != nil {
- return errors.Wrapf(err, "error removing container %s network aliases for network %s", ctr.ID(), network)
+ if err := ctrAliasesBkt.DeleteBucket([]byte(network)); err != nil {
+ return errors.Wrapf(err, "error removing container %s network aliases for network %s", ctr.ID(), network)
+ }
}
return nil
diff --git a/libpod/container.go b/libpod/container.go
index 580fa7b3d..9009a4ec8 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -1,22 +1,18 @@
package libpod
import (
+ "bytes"
"fmt"
"io/ioutil"
"net"
"os"
- "path/filepath"
- "strings"
"time"
"github.com/containernetworking/cni/pkg/types"
cnitypes "github.com/containernetworking/cni/pkg/types/current"
- "github.com/containers/common/pkg/config"
"github.com/containers/image/v5/manifest"
"github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/libpod/lock"
- "github.com/containers/podman/v2/pkg/rootless"
- "github.com/containers/podman/v2/utils"
"github.com/containers/storage"
"github.com/cri-o/ocicni/pkg/ocicni"
spec "github.com/opencontainers/runtime-spec/specs-go"
@@ -912,44 +908,23 @@ func (c *Container) CgroupManager() string {
// CGroupPath returns a cgroups "path" for a given container.
func (c *Container) CGroupPath() (string, error) {
- cgroupManager := c.CgroupManager()
-
- switch {
- case c.config.NoCgroups || c.config.CgroupsMode == "disabled":
+ if c.config.NoCgroups || c.config.CgroupsMode == "disabled" {
return "", errors.Wrapf(define.ErrNoCgroups, "this container is not creating cgroups")
- case c.config.CgroupsMode == cgroupSplit:
- if c.config.CgroupParent != "" {
- return "", errors.Errorf("cannot specify cgroup-parent with cgroup-mode %q", cgroupSplit)
- }
- cg, err := utils.GetCgroupProcess(c.state.ConmonPID)
- if err != nil {
- return "", err
- }
- // Use the conmon cgroup for two reasons: we validate the container
- // delegation was correct, and the conmon cgroup doesn't change at runtime
- // while we are not sure about the container that can create sub cgroups.
- if !strings.HasSuffix(cg, "supervisor") {
- return "", errors.Errorf("invalid cgroup for conmon %q", cg)
- }
- return strings.TrimSuffix(cg, "/supervisor") + "/container", nil
- case cgroupManager == config.CgroupfsCgroupsManager:
- return filepath.Join(c.config.CgroupParent, fmt.Sprintf("libpod-%s", c.ID())), nil
- case cgroupManager == config.SystemdCgroupsManager:
- if rootless.IsRootless() {
- uid := rootless.GetRootlessUID()
- parts := strings.SplitN(c.config.CgroupParent, "/", 2)
-
- dir := ""
- if len(parts) > 1 {
- dir = parts[1]
- }
+ }
- return filepath.Join(parts[0], fmt.Sprintf("user-%d.slice/user@%d.service/user.slice/%s", uid, uid, dir), createUnitName("libpod", c.ID())), nil
- }
- return filepath.Join(c.config.CgroupParent, createUnitName("libpod", c.ID())), nil
- default:
- return "", errors.Wrapf(define.ErrInvalidArg, "unsupported CGroup manager %s in use", cgroupManager)
+ // Read /proc/[PID]/cgroup and look at the first line. cgroups(7)
+ // nails it down to three fields with the 3rd pointing to the cgroup's
+ // path which works both on v1 and v2.
+ procPath := fmt.Sprintf("/proc/%d/cgroup", c.state.PID)
+ lines, err := ioutil.ReadFile(procPath)
+ if err != nil {
+ return "", err
}
+ fields := bytes.Split(bytes.Split(lines, []byte("\n"))[0], []byte(":"))
+ if len(fields) != 3 {
+ return "", errors.Errorf("expected 3 fields but got %d: %s", len(fields), procPath)
+ }
+ return string(fields[2]), nil
}
// RootFsSize returns the root FS size of the container
@@ -1113,3 +1088,17 @@ func (c *Container) networks() ([]string, error) {
return networks, err
}
+
+// networksByNameIndex provides us with a map of container networks where key
+// is network name and value is the index position
+func (c *Container) networksByNameIndex() (map[string]int, error) {
+ networks, err := c.networks()
+ if err != nil {
+ return nil, err
+ }
+ networkNamesByIndex := make(map[string]int, len(networks))
+ for index, name := range networks {
+ networkNamesByIndex[name] = index
+ }
+ return networkNamesByIndex, nil
+}
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index 83d5c20cb..b81f3f716 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -1354,6 +1354,14 @@ func (c *Container) makeBindMounts() error {
return err
}
}
+ } else {
+ if !c.config.UseImageHosts && c.state.BindMounts["/etc/hosts"] == "" {
+ newHosts, err := c.generateHosts("/etc/hosts")
+ if err != nil {
+ return errors.Wrapf(err, "error creating hosts file for container %s", c.ID())
+ }
+ c.state.BindMounts["/etc/hosts"] = newHosts
+ }
}
// SHM is always added when we mount the container
@@ -1614,14 +1622,11 @@ func (c *Container) getHosts() string {
}
if !hasNetNS {
// 127.0.1.1 and host's hostname to match Docker
- osHostname, err := os.Hostname()
- if err != nil {
- osHostname = c.Hostname()
- }
- hosts += fmt.Sprintf("127.0.1.1 %s\n", osHostname)
+ osHostname, _ := os.Hostname()
+ hosts += fmt.Sprintf("127.0.1.1 %s %s %s\n", osHostname, c.Hostname(), c.config.Name)
}
if netNone {
- hosts += fmt.Sprintf("127.0.1.1 %s\n", c.Hostname())
+ hosts += fmt.Sprintf("127.0.1.1 %s %s\n", c.Hostname(), c.config.Name)
}
}
}
@@ -2091,10 +2096,7 @@ func (c *Container) getOCICgroupPath() (string, error) {
logrus.Debugf("Setting CGroups for container %s to %s", c.ID(), systemdCgroups)
return systemdCgroups, nil
case cgroupManager == config.CgroupfsCgroupsManager:
- cgroupPath, err := c.CGroupPath()
- if err != nil {
- return "", err
- }
+ cgroupPath := filepath.Join(c.config.CgroupParent, fmt.Sprintf("libpod-%s", c.ID()))
logrus.Debugf("Setting CGroup path for container %s to %s", c.ID(), cgroupPath)
return cgroupPath, nil
default:
diff --git a/libpod/define/pod_inspect.go b/libpod/define/pod_inspect.go
index a4115eb92..2fa91166f 100644
--- a/libpod/define/pod_inspect.go
+++ b/libpod/define/pod_inspect.go
@@ -67,7 +67,7 @@ type InspectPodInfraConfig struct {
StaticIP net.IP
// StaticMAC is a static MAC address that will be assigned to the infra
// container and then used by the pod.
- StaticMAC net.HardwareAddr
+ StaticMAC string
// NoManageResolvConf indicates that the pod will not manage resolv.conf
// and instead each container will handle their own.
NoManageResolvConf bool
diff --git a/libpod/filters/pods.go b/libpod/filters/pods.go
index 7d12eefa6..3cd97728f 100644
--- a/libpod/filters/pods.go
+++ b/libpod/filters/pods.go
@@ -88,7 +88,7 @@ func GeneratePodFilterFunc(filter, filterValue string) (
return match
}, nil
case "status":
- if !util.StringInSlice(filterValue, []string{"stopped", "running", "paused", "exited", "dead", "created"}) {
+ if !util.StringInSlice(filterValue, []string{"stopped", "running", "paused", "exited", "dead", "created", "degraded"}) {
return nil, errors.Errorf("%s is not a valid pod status", filterValue)
}
return func(p *libpod.Pod) bool {
diff --git a/libpod/image/image.go b/libpod/image/image.go
index 301954703..cecd64eb7 100644
--- a/libpod/image/image.go
+++ b/libpod/image/image.go
@@ -24,6 +24,7 @@ import (
"github.com/containers/image/v5/manifest"
ociarchive "github.com/containers/image/v5/oci/archive"
"github.com/containers/image/v5/oci/layout"
+ "github.com/containers/image/v5/pkg/shortnames"
is "github.com/containers/image/v5/storage"
"github.com/containers/image/v5/tarball"
"github.com/containers/image/v5/transports"
@@ -164,7 +165,7 @@ func (ir *Runtime) New(ctx context.Context, name, signaturePolicyPath, authfile
}
imageName, err := ir.pullImageFromHeuristicSource(ctx, name, writer, authfile, signaturePolicyPath, signingoptions, dockeroptions, &retry.RetryOptions{MaxRetry: maxRetry}, label)
if err != nil {
- return nil, errors.Wrapf(err, "unable to pull %s", name)
+ return nil, err
}
newImage, err := ir.NewFromLocal(imageName[0])
@@ -318,10 +319,8 @@ func (ir *Runtime) LoadAllImagesFromDockerArchive(ctx context.Context, fileName
}
goal := pullGoal{
- pullAllPairs: true,
- usedSearchRegistries: false,
- refPairs: refPairs,
- searchedRegistries: nil,
+ pullAllPairs: true,
+ refPairs: refPairs,
}
defer goal.cleanUp()
@@ -456,22 +455,19 @@ func (ir *Runtime) getLocalImage(inputName string) (string, *storage.Image, erro
return "", nil, errors.Wrapf(ErrNoSuchImage, imageError)
}
- // "Short-name image", so let's try out certain prefixes:
- // 1) DefaultLocalRegistry (i.e., "localhost/)
- // 2) Unqualified-search registries from registries.conf
- unqualifiedSearchRegistries, err := registries.GetRegistries()
+ sys := &types.SystemContext{
+ SystemRegistriesConfPath: registries.SystemRegistriesConfPath(),
+ }
+
+ candidates, err := shortnames.ResolveLocally(sys, inputName)
if err != nil {
return "", nil, err
}
- for _, candidate := range append([]string{DefaultLocalRegistry}, unqualifiedSearchRegistries...) {
- ref, err := decomposedImage.referenceWithRegistry(candidate)
- if err != nil {
- return "", nil, err
- }
- img, err := ir.store.Image(reference.TagNameOnly(ref).String())
+ for _, candidate := range candidates {
+ img, err := ir.store.Image(candidate.String())
if err == nil {
- return ref.String(), img, nil
+ return candidate.String(), img, nil
}
}
diff --git a/libpod/image/pull.go b/libpod/image/pull.go
index 65acdf427..2a2d16252 100644
--- a/libpod/image/pull.go
+++ b/libpod/image/pull.go
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"io"
+ "os"
"path/filepath"
"strings"
@@ -15,13 +16,14 @@ import (
dockerarchive "github.com/containers/image/v5/docker/archive"
ociarchive "github.com/containers/image/v5/oci/archive"
oci "github.com/containers/image/v5/oci/layout"
+ "github.com/containers/image/v5/pkg/shortnames"
is "github.com/containers/image/v5/storage"
"github.com/containers/image/v5/transports"
"github.com/containers/image/v5/transports/alltransports"
"github.com/containers/image/v5/types"
"github.com/containers/podman/v2/libpod/events"
+ "github.com/containers/podman/v2/pkg/errorhandling"
"github.com/containers/podman/v2/pkg/registries"
- "github.com/hashicorp/go-multierror"
"github.com/opentracing/opentracing-go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -56,9 +58,10 @@ var (
// pullRefPair records a pair of prepared image references to pull.
type pullRefPair struct {
- image string
- srcRef types.ImageReference
- dstRef types.ImageReference
+ image string
+ srcRef types.ImageReference
+ dstRef types.ImageReference
+ resolvedShortname *shortnames.PullCandidate // if set, must be recorded after successful pull
}
// cleanUpFunc is a function prototype for clean-up functions.
@@ -66,11 +69,11 @@ type cleanUpFunc func() error
// pullGoal represents the prepared image references and decided behavior to be executed by imagePull
type pullGoal struct {
- refPairs []pullRefPair
- pullAllPairs bool // Pull all refPairs instead of stopping on first success.
- usedSearchRegistries bool // refPairs construction has depended on registries.GetRegistries()
- searchedRegistries []string // The list of search registries used; set only if usedSearchRegistries
- cleanUpFuncs []cleanUpFunc // Mainly used to close long-lived objects (e.g., an archive.Reader)
+ refPairs []pullRefPair
+ pullAllPairs bool // Pull all refPairs instead of stopping on first success.
+ cleanUpFuncs []cleanUpFunc // Mainly used to close long-lived objects (e.g., an archive.Reader)
+ shortName string // Set when pulling a short name
+ resolved *shortnames.Resolved // Set when pulling a short name
}
// cleanUp invokes all cleanUpFuncs. Certain resources may not be available
@@ -86,10 +89,8 @@ func (p *pullGoal) cleanUp() {
// singlePullRefPairGoal returns a no-frills pull goal for the specified reference pair.
func singlePullRefPairGoal(rp pullRefPair) *pullGoal {
return &pullGoal{
- refPairs: []pullRefPair{rp},
- pullAllPairs: false, // Does not really make a difference.
- usedSearchRegistries: false,
- searchedRegistries: nil,
+ refPairs: []pullRefPair{rp},
+ pullAllPairs: false, // Does not really make a difference.
}
}
@@ -193,11 +194,9 @@ func (ir *Runtime) pullGoalFromImageReference(ctx context.Context, srcRef types.
}
return &pullGoal{
- pullAllPairs: true,
- usedSearchRegistries: false,
- refPairs: pairs,
- searchedRegistries: nil,
- cleanUpFuncs: []cleanUpFunc{reader.Close},
+ pullAllPairs: true,
+ refPairs: pairs,
+ cleanUpFuncs: []cleanUpFunc{reader.Close},
}, nil
case OCIArchive:
@@ -267,7 +266,7 @@ func (ir *Runtime) pullImageFromHeuristicSource(ctx context.Context, inputName s
if srcTransport != nil && srcTransport.Name() != DockerTransport {
return nil, err
}
- goal, err = ir.pullGoalFromPossiblyUnqualifiedName(inputName)
+ goal, err = ir.pullGoalFromPossiblyUnqualifiedName(sc, writer, inputName)
if err != nil {
return nil, errors.Wrap(err, "error getting default registries to try")
}
@@ -325,7 +324,7 @@ func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goa
var (
images []string
- pullErrors *multierror.Error
+ pullErrors []error
)
for _, imageInfo := range goal.refPairs {
@@ -348,12 +347,17 @@ func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goa
_, err = cp.Image(ctx, policyContext, imageInfo.dstRef, imageInfo.srcRef, copyOptions)
return err
}, retryOptions); err != nil {
- pullErrors = multierror.Append(pullErrors, err)
+ pullErrors = append(pullErrors, err)
logrus.Debugf("Error pulling image ref %s: %v", imageInfo.srcRef.StringWithinTransport(), err)
if writer != nil {
_, _ = io.WriteString(writer, cleanErrorMessage(err))
}
} else {
+ if imageInfo.resolvedShortname != nil {
+ if err := imageInfo.resolvedShortname.Record(); err != nil {
+ logrus.Errorf("Error recording short-name alias %q: %v", imageInfo.resolvedShortname.Value.String(), err)
+ }
+ }
if !goal.pullAllPairs {
ir.newImageEvent(events.Pull, "")
return []string{imageInfo.image}, nil
@@ -361,68 +365,75 @@ func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goa
images = append(images, imageInfo.image)
}
}
- // If no image was found, we should handle. Lets be nicer to the user and see if we can figure out why.
+ // If no image was found, we should handle. Lets be nicer to the user
+ // and see if we can figure out why.
if len(images) == 0 {
- if goal.usedSearchRegistries && len(goal.searchedRegistries) == 0 {
- return nil, errors.Errorf("image name provided is a short name and no search registries are defined in the registries config file.")
- }
- // If the image passed in was fully-qualified, we will have 1 refpair. Bc the image is fq'd, we don't need to yap about registries.
- if !goal.usedSearchRegistries {
- if pullErrors != nil && len(pullErrors.Errors) > 0 { // this should always be true
- return nil, pullErrors.Errors[0]
- }
- return nil, errors.Errorf("unable to pull image, or you do not have pull access")
+ if goal.resolved != nil {
+ return nil, goal.resolved.FormatPullErrors(pullErrors)
}
- return nil, errors.Cause(pullErrors)
- }
- if len(images) > 0 {
- ir.newImageEvent(events.Pull, images[0])
+ return nil, errorhandling.JoinErrors(pullErrors)
}
+
+ ir.newImageEvent(events.Pull, images[0])
return images, nil
}
+// getShortNameMode looks up the `CONTAINERS_SHORT_NAME_ALIASING` environment
+// variable. If it's "on", return `nil` to use the defaults from
+// containers/image and the registries.conf files on the system. If it's
+// "off", empty or unset, return types.ShortNameModeDisabled to turn off
+// short-name aliasing by default.
+//
+// TODO: remove this function once we want to default to short-name aliasing.
+func getShortNameMode() *types.ShortNameMode {
+ env := os.Getenv("CONTAINERS_SHORT_NAME_ALIASING")
+ if strings.ToLower(env) == "on" {
+ return nil // default to whatever registries.conf and c/image decide
+ }
+ mode := types.ShortNameModeDisabled
+ return &mode
+}
+
// pullGoalFromPossiblyUnqualifiedName looks at inputName and determines the possible
// image references to try pulling in combination with the registries.conf file as well
-func (ir *Runtime) pullGoalFromPossiblyUnqualifiedName(inputName string) (*pullGoal, error) {
- decomposedImage, err := decompose(inputName)
+func (ir *Runtime) pullGoalFromPossiblyUnqualifiedName(sys *types.SystemContext, writer io.Writer, inputName string) (*pullGoal, error) {
+ if sys == nil {
+ sys = &types.SystemContext{}
+ }
+ sys.ShortNameMode = getShortNameMode()
+
+ resolved, err := shortnames.Resolve(sys, inputName)
if err != nil {
return nil, err
}
- if decomposedImage.hasRegistry {
- srcRef, err := docker.ParseReference("//" + inputName)
- if err != nil {
- return nil, errors.Wrapf(err, "unable to parse '%s'", inputName)
+ if desc := resolved.Description(); len(desc) > 0 {
+ logrus.Debug(desc)
+ if writer != nil {
+ if _, err := writer.Write([]byte(desc + "\n")); err != nil {
+ return nil, err
+ }
}
- return ir.getSinglePullRefPairGoal(srcRef, inputName)
}
- searchRegistries, err := registries.GetRegistries()
- if err != nil {
- return nil, err
- }
- refPairs := make([]pullRefPair, 0, len(searchRegistries))
- for _, registry := range searchRegistries {
- ref, err := decomposedImage.referenceWithRegistry(registry)
+ refPairs := []pullRefPair{}
+ for i, candidate := range resolved.PullCandidates {
+ srcRef, err := docker.NewReference(candidate.Value)
if err != nil {
return nil, err
}
- imageName := ref.String()
- srcRef, err := docker.ParseReference("//" + imageName)
- if err != nil {
- return nil, errors.Wrapf(err, "unable to parse '%s'", imageName)
- }
- ps, err := ir.getPullRefPair(srcRef, imageName)
+ ps, err := ir.getPullRefPair(srcRef, candidate.Value.String())
if err != nil {
return nil, err
}
+ ps.resolvedShortname = &resolved.PullCandidates[i]
refPairs = append(refPairs, ps)
}
return &pullGoal{
- refPairs: refPairs,
- pullAllPairs: false,
- usedSearchRegistries: true,
- searchedRegistries: searchRegistries,
+ refPairs: refPairs,
+ pullAllPairs: false,
+ shortName: inputName,
+ resolved: resolved,
}, nil
}
diff --git a/libpod/image/pull_test.go b/libpod/image/pull_test.go
index 6cb80e8b5..2e1464ad3 100644
--- a/libpod/image/pull_test.go
+++ b/libpod/image/pull_test.go
@@ -278,15 +278,11 @@ func TestPullGoalFromImageReference(t *testing.T) {
assert.Equal(t, e.dstName, storageReferenceWithoutLocation(res.refPairs[i].dstRef), testDescription)
}
assert.Equal(t, c.expectedPullAllPairs, res.pullAllPairs, c.srcName)
- assert.False(t, res.usedSearchRegistries, c.srcName)
- assert.Nil(t, res.searchedRegistries, c.srcName)
}
}
}
-const registriesConfWithSearch = `[registries.search]
-registries = ['example.com', 'docker.io']
-`
+const registriesConfWithSearch = `unqualified-search-registries = ['example.com', 'docker.io']`
func TestPullGoalFromPossiblyUnqualifiedName(t *testing.T) {
const digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
@@ -303,69 +299,58 @@ func TestPullGoalFromPossiblyUnqualifiedName(t *testing.T) {
ir, cleanup := newTestRuntime(t)
defer cleanup()
- // Environment is per-process, so this looks very unsafe; actually it seems fine because tests are not
- // run in parallel unless they opt in by calling t.Parallel(). So don’t do that.
- oldRCP, hasRCP := os.LookupEnv("REGISTRIES_CONFIG_PATH")
- defer func() {
- if hasRCP {
- os.Setenv("REGISTRIES_CONFIG_PATH", oldRCP)
- } else {
- os.Unsetenv("REGISTRIES_CONFIG_PATH")
- }
- }()
- os.Setenv("REGISTRIES_CONFIG_PATH", registriesConf.Name())
+ sc := GetSystemContext("", "", false)
+
+ aliasesConf, err := ioutil.TempFile("", "short-name-aliases.conf")
+ require.NoError(t, err)
+ defer aliasesConf.Close()
+ defer os.Remove(aliasesConf.Name())
+ sc.UserShortNameAliasConfPath = aliasesConf.Name()
+ sc.SystemRegistriesConfPath = registriesConf.Name()
for _, c := range []struct {
- input string
- expected []pullRefStrings
- expectedUsedSearchRegistries bool
+ input string
+ expected []pullRefStrings
}{
- {"#", nil, false}, // Clearly invalid.
+ {"#", nil}, // Clearly invalid.
{ // Fully-explicit docker.io, name-only.
"docker.io/library/busybox",
// (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".)
- []pullRefStrings{{"docker.io/library/busybox", "docker://busybox:latest", "docker.io/library/busybox:latest"}},
- false,
+ []pullRefStrings{{"docker.io/library/busybox:latest", "docker://busybox:latest", "docker.io/library/busybox:latest"}},
},
{ // docker.io with implied /library/, name-only.
"docker.io/busybox",
// (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".)
- []pullRefStrings{{"docker.io/busybox", "docker://busybox:latest", "docker.io/library/busybox:latest"}},
- false,
+ []pullRefStrings{{"docker.io/library/busybox:latest", "docker://busybox:latest", "docker.io/library/busybox:latest"}},
},
{ // Qualified example.com, name-only.
"example.com/ns/busybox",
- []pullRefStrings{{"example.com/ns/busybox", "docker://example.com/ns/busybox:latest", "example.com/ns/busybox:latest"}},
- false,
+ []pullRefStrings{{"example.com/ns/busybox:latest", "docker://example.com/ns/busybox:latest", "example.com/ns/busybox:latest"}},
},
{ // Qualified example.com, name:tag.
"example.com/ns/busybox:notlatest",
[]pullRefStrings{{"example.com/ns/busybox:notlatest", "docker://example.com/ns/busybox:notlatest", "example.com/ns/busybox:notlatest"}},
- false,
},
{ // Qualified example.com, name@digest.
"example.com/ns/busybox" + digestSuffix,
[]pullRefStrings{{"example.com/ns/busybox" + digestSuffix, "docker://example.com/ns/busybox" + digestSuffix,
"example.com/ns/busybox" + digestSuffix}},
- false,
},
// Qualified example.com, name:tag@digest. This code is happy to try, but .srcRef parsing currently rejects such input.
- {"example.com/ns/busybox:notlatest" + digestSuffix, nil, false},
+ {"example.com/ns/busybox:notlatest" + digestSuffix, nil},
{ // Unqualified, single-name, name-only
"busybox",
[]pullRefStrings{
- {"example.com/busybox", "docker://example.com/busybox:latest", "example.com/busybox:latest"},
+ {"example.com/busybox:latest", "docker://example.com/busybox:latest", "example.com/busybox:latest"},
// (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".)
- {"docker.io/library/busybox", "docker://busybox:latest", "docker.io/library/busybox:latest"},
+ {"docker.io/library/busybox:latest", "docker://busybox:latest", "docker.io/library/busybox:latest"},
},
- true,
},
{ // Unqualified, namespaced, name-only
"ns/busybox",
[]pullRefStrings{
- {"example.com/ns/busybox", "docker://example.com/ns/busybox:latest", "example.com/ns/busybox:latest"},
+ {"example.com/ns/busybox:latest", "docker://example.com/ns/busybox:latest", "example.com/ns/busybox:latest"},
},
- true,
},
{ // Unqualified, name:tag
"busybox:notlatest",
@@ -374,7 +359,6 @@ func TestPullGoalFromPossiblyUnqualifiedName(t *testing.T) {
// (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".)
{"docker.io/library/busybox:notlatest", "docker://busybox:notlatest", "docker.io/library/busybox:notlatest"},
},
- true,
},
{ // Unqualified, name@digest
"busybox" + digestSuffix,
@@ -383,29 +367,22 @@ func TestPullGoalFromPossiblyUnqualifiedName(t *testing.T) {
// (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".)
{"docker.io/library/busybox" + digestSuffix, "docker://busybox" + digestSuffix, "docker.io/library/busybox" + digestSuffix},
},
- true,
},
// Unqualified, name:tag@digest. This code is happy to try, but .srcRef parsing currently rejects such input.
- {"busybox:notlatest" + digestSuffix, nil, false},
+ {"busybox:notlatest" + digestSuffix, nil},
} {
- res, err := ir.pullGoalFromPossiblyUnqualifiedName(c.input)
+ res, err := ir.pullGoalFromPossiblyUnqualifiedName(sc, nil, c.input)
if len(c.expected) == 0 {
assert.Error(t, err, c.input)
} else {
assert.NoError(t, err, c.input)
for i, e := range c.expected {
- testDescription := fmt.Sprintf("%s #%d", c.input, i)
+ testDescription := fmt.Sprintf("%s #%d (%v)", c.input, i, res.refPairs)
assert.Equal(t, e.image, res.refPairs[i].image, testDescription)
assert.Equal(t, e.srcRef, transports.ImageName(res.refPairs[i].srcRef), testDescription)
assert.Equal(t, e.dstName, storageReferenceWithoutLocation(res.refPairs[i].dstRef), testDescription)
}
assert.False(t, res.pullAllPairs, c.input)
- assert.Equal(t, c.expectedUsedSearchRegistries, res.usedSearchRegistries, c.input)
- if !c.expectedUsedSearchRegistries {
- assert.Nil(t, res.searchedRegistries, c.input)
- } else {
- assert.Equal(t, []string{"example.com", "docker.io"}, res.searchedRegistries, c.input)
- }
}
}
}
diff --git a/libpod/network/create.go b/libpod/network/create.go
index c11904ecf..387f4fcd3 100644
--- a/libpod/network/create.go
+++ b/libpod/network/create.go
@@ -8,7 +8,7 @@ import (
"path/filepath"
"github.com/containernetworking/cni/pkg/version"
- "github.com/containers/podman/v2/libpod"
+ "github.com/containers/common/pkg/config"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/containers/podman/v2/pkg/rootless"
"github.com/containers/podman/v2/pkg/util"
@@ -16,25 +16,21 @@ import (
)
// Create the CNI network
-func Create(name string, options entities.NetworkCreateOptions, r *libpod.Runtime) (*entities.NetworkCreateReport, error) {
+func Create(name string, options entities.NetworkCreateOptions, runtimeConfig *config.Config) (*entities.NetworkCreateReport, error) {
var fileName string
if err := isSupportedDriver(options.Driver); err != nil {
return nil, err
}
- config, err := r.GetConfig()
- if err != nil {
- return nil, err
- }
// Acquire a lock for CNI
- l, err := acquireCNILock(filepath.Join(config.Engine.TmpDir, LockFileName))
+ l, err := acquireCNILock(filepath.Join(runtimeConfig.Engine.TmpDir, LockFileName))
if err != nil {
return nil, err
}
defer l.releaseCNILock()
if len(options.MacVLAN) > 0 {
- fileName, err = createMacVLAN(r, name, options)
+ fileName, err = createMacVLAN(name, options, runtimeConfig)
} else {
- fileName, err = createBridge(r, name, options)
+ fileName, err = createBridge(name, options, runtimeConfig)
}
if err != nil {
return nil, err
@@ -81,17 +77,17 @@ func validateBridgeOptions(options entities.NetworkCreateOptions) error {
}
// createBridge creates a CNI network
-func createBridge(r *libpod.Runtime, name string, options entities.NetworkCreateOptions) (string, error) {
+func createBridge(name string, options entities.NetworkCreateOptions, runtimeConfig *config.Config) (string, error) {
+ var (
+ ipamRanges [][]IPAMLocalHostRangeConf
+ err error
+ routes []IPAMRoute
+ )
isGateway := true
ipMasq := true
- runtimeConfig, err := r.GetConfig()
- if err != nil {
- return "", err
- }
// validate options
- err = validateBridgeOptions(options)
- if err != nil {
+ if err := validateBridgeOptions(options); err != nil {
return "", err
}
@@ -102,8 +98,6 @@ func createBridge(r *libpod.Runtime, name string, options entities.NetworkCreate
subnet := &options.Subnet
ipRange := &options.Range
gateway := options.Gateway
- var ipamRanges [][]IPAMLocalHostRangeConf
- var routes []IPAMRoute
if subnet.IP != nil {
// if network is provided, does it conflict with existing CNI or live networks
err = ValidateUserNetworkIsAvailable(runtimeConfig, subnet)
@@ -201,7 +195,7 @@ func createBridge(r *libpod.Runtime, name string, options entities.NetworkCreate
return cniPathName, err
}
-func createMacVLAN(r *libpod.Runtime, name string, options entities.NetworkCreateOptions) (string, error) {
+func createMacVLAN(name string, options entities.NetworkCreateOptions, runtimeConfig *config.Config) (string, error) {
var (
plugins []CNIPlugins
)
@@ -210,17 +204,12 @@ func createMacVLAN(r *libpod.Runtime, name string, options entities.NetworkCreat
return "", err
}
- config, err := r.GetConfig()
- if err != nil {
- return "", err
- }
-
// Make sure the host-device exists
if !util.StringInSlice(options.MacVLAN, liveNetNames) {
return "", errors.Errorf("failed to find network interface %q", options.MacVLAN)
}
if len(name) > 0 {
- netNames, err := GetNetworkNamesFromFileSystem(config)
+ netNames, err := GetNetworkNamesFromFileSystem(runtimeConfig)
if err != nil {
return "", err
}
@@ -228,7 +217,7 @@ func createMacVLAN(r *libpod.Runtime, name string, options entities.NetworkCreat
return "", errors.Errorf("the network name %s is already used", name)
}
} else {
- name, err = GetFreeDeviceName(config)
+ name, err = GetFreeDeviceName(runtimeConfig)
if err != nil {
return "", err
}
@@ -241,7 +230,7 @@ func createMacVLAN(r *libpod.Runtime, name string, options entities.NetworkCreat
if err != nil {
return "", err
}
- cniPathName := filepath.Join(GetCNIConfDir(config), fmt.Sprintf("%s.conflist", name))
+ cniPathName := filepath.Join(GetCNIConfDir(runtimeConfig), fmt.Sprintf("%s.conflist", name))
err = ioutil.WriteFile(cniPathName, b, 0644)
return cniPathName, err
}
diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go
index fed90cfc3..3882e095a 100644
--- a/libpod/networking_linux.go
+++ b/libpod/networking_linux.go
@@ -13,6 +13,7 @@ import (
"os"
"os/exec"
"path/filepath"
+ "sort"
"strings"
"syscall"
"time"
@@ -20,6 +21,7 @@ import (
cnitypes "github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containers/podman/v2/libpod/define"
+ "github.com/containers/podman/v2/libpod/network"
"github.com/containers/podman/v2/pkg/errorhandling"
"github.com/containers/podman/v2/pkg/netns"
"github.com/containers/podman/v2/pkg/rootless"
@@ -981,3 +983,139 @@ func (w *logrusDebugWriter) Write(p []byte) (int, error) {
logrus.Debugf("%s%s", w.prefix, string(p))
return len(p), nil
}
+
+// DisconnectContainerFromNetwork removes a container from its CNI network
+func (r *Runtime) DisconnectContainerFromNetwork(nameOrID, netName string, force bool) error {
+ ctr, err := r.LookupContainer(nameOrID)
+ if err != nil {
+ return err
+ }
+
+ networks, err := ctr.networksByNameIndex()
+ if err != nil {
+ return err
+ }
+
+ exists, err := network.Exists(r.config, netName)
+ if err != nil {
+ return err
+ }
+ if !exists {
+ return errors.Wrap(define.ErrNoSuchNetwork, netName)
+ }
+
+ index, nameExists := networks[netName]
+ if !nameExists && len(networks) > 0 {
+ return errors.Errorf("container %s is not connected to network %s", nameOrID, netName)
+ }
+
+ ctr.lock.Lock()
+ defer ctr.lock.Unlock()
+ if err := ctr.syncContainer(); err != nil {
+ return err
+ }
+
+ podConfig := r.getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), []string{netName}, ctr.config.PortMappings, nil, nil)
+ if err := r.netPlugin.TearDownPod(podConfig); err != nil {
+ return err
+ }
+ if err := r.state.NetworkDisconnect(ctr, netName); err != nil {
+ return err
+ }
+
+ // update network status
+ networkStatus := ctr.state.NetworkStatus
+ // if len is one and we confirmed earlier that the container is in
+ // fact connected to the network, then just return an empty slice
+ if len(networkStatus) == 1 {
+ ctr.state.NetworkStatus = make([]*cnitypes.Result, 0)
+ } else {
+ // clip out the index of the network
+ networkStatus[len(networkStatus)-1], networkStatus[index] = networkStatus[index], networkStatus[len(networkStatus)-1]
+ // shorten the slice by one
+ ctr.state.NetworkStatus = networkStatus[:len(networkStatus)-1]
+ }
+ return nil
+}
+
+// ConnectContainerToNetwork connects a container to a CNI network
+func (r *Runtime) ConnectContainerToNetwork(nameOrID, netName string, aliases []string) error {
+ ctr, err := r.LookupContainer(nameOrID)
+ if err != nil {
+ return err
+ }
+
+ networks, err := ctr.networksByNameIndex()
+ if err != nil {
+ return err
+ }
+
+ exists, err := network.Exists(r.config, netName)
+ if err != nil {
+ return err
+ }
+ if !exists {
+ return errors.Wrap(define.ErrNoSuchNetwork, netName)
+ }
+
+ _, nameExists := networks[netName]
+ if !nameExists && len(networks) > 0 {
+ return errors.Errorf("container %s is not connected to network %s", nameOrID, netName)
+ }
+
+ ctr.lock.Lock()
+ defer ctr.lock.Unlock()
+ if err := ctr.syncContainer(); err != nil {
+ return err
+ }
+
+ if err := r.state.NetworkConnect(ctr, netName, aliases); err != nil {
+ return err
+ }
+
+ podConfig := r.getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), []string{netName}, ctr.config.PortMappings, nil, nil)
+ podConfig.Aliases = make(map[string][]string, 1)
+ podConfig.Aliases[netName] = aliases
+ results, err := r.netPlugin.SetUpPod(podConfig)
+ if err != nil {
+ return err
+ }
+ if len(results) != 1 {
+ return errors.New("when adding aliases, results must be of length 1")
+ }
+
+ networkResults := make([]*cnitypes.Result, 0)
+ for _, r := range results {
+ resultCurrent, err := cnitypes.GetResult(r.Result)
+ if err != nil {
+ return errors.Wrapf(err, "error parsing CNI plugin result %q: %v", r.Result, err)
+ }
+ networkResults = append(networkResults, resultCurrent)
+ }
+
+ // update network status
+ networkStatus := ctr.state.NetworkStatus
+ // if len is one and we confirmed earlier that the container is in
+ // fact connected to the network, then just return an empty slice
+ if len(networkStatus) == 0 {
+ ctr.state.NetworkStatus = append(ctr.state.NetworkStatus, networkResults...)
+ } else {
+ // build a list of network names so we can sort and
+ // get the new name's index
+ var networkNames []string
+ for netName := range networks {
+ networkNames = append(networkNames, netName)
+ }
+ networkNames = append(networkNames, netName)
+ // sort
+ sort.Strings(networkNames)
+ // get index of new network name
+ index := sort.SearchStrings(networkNames, netName)
+ // Append a zero value to to the slice
+ networkStatus = append(networkStatus, &cnitypes.Result{})
+ // populate network status
+ copy(networkStatus[index+1:], networkStatus[index:])
+ networkStatus[index] = networkResults[0]
+ }
+ return nil
+}
diff --git a/libpod/pod_api.go b/libpod/pod_api.go
index 87ac5c07a..845948dd3 100644
--- a/libpod/pod_api.go
+++ b/libpod/pod_api.go
@@ -535,7 +535,7 @@ func (p *Pod) Inspect() (*define.InspectPodData, error) {
infraConfig = new(define.InspectPodInfraConfig)
infraConfig.HostNetwork = p.config.InfraContainer.HostNetwork
infraConfig.StaticIP = p.config.InfraContainer.StaticIP
- infraConfig.StaticMAC = p.config.InfraContainer.StaticMAC
+ infraConfig.StaticMAC = p.config.InfraContainer.StaticMAC.String()
infraConfig.NoManageResolvConf = p.config.InfraContainer.UseImageResolvConf
infraConfig.NoManageHosts = p.config.InfraContainer.UseImageHosts