summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/common/create_opts.go7
-rw-r--r--cmd/podman/common/netflags.go2
-rw-r--r--libpod/container.go3
-rw-r--r--libpod/container_log_linux.go276
-rw-r--r--libpod/runtime.go6
-rw-r--r--pkg/api/handlers/compat/containers_create.go2
-rw-r--r--pkg/specgen/generate/namespaces.go2
-rw-r--r--pkg/specgen/namespaces.go8
-rw-r--r--test/e2e/logs_test.go2
-rw-r--r--test/e2e/run_networking_test.go14
-rw-r--r--test/system/035-logs.bats52
-rw-r--r--test/system/130-kill.bats3
-rw-r--r--vendor/github.com/containers/common/pkg/defaultnet/default_network.go222
-rw-r--r--vendor/modules.txt1
14 files changed, 473 insertions, 127 deletions
diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go
index 77ac781a5..76d7345fc 100644
--- a/cmd/podman/common/create_opts.go
+++ b/cmd/podman/common/create_opts.go
@@ -8,6 +8,7 @@ import (
"strconv"
"strings"
+ "github.com/containers/common/pkg/config"
"github.com/containers/podman/v3/cmd/podman/registry"
"github.com/containers/podman/v3/pkg/api/handlers"
"github.com/containers/podman/v3/pkg/cgroups"
@@ -140,7 +141,7 @@ func stringMaptoArray(m map[string]string) []string {
// ContainerCreateToContainerCLIOpts converts a compat input struct to cliopts so it can be converted to
// a specgen spec.
-func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroupsManager string) (*ContainerCLIOpts, []string, error) {
+func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, rtc *config.Config) (*ContainerCLIOpts, []string, error) {
var (
capAdd []string
cappDrop []string
@@ -248,7 +249,7 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroup
}
// netMode
- nsmode, _, err := specgen.ParseNetworkNamespace(string(cc.HostConfig.NetworkMode))
+ nsmode, _, err := specgen.ParseNetworkNamespace(string(cc.HostConfig.NetworkMode), true)
if err != nil {
return nil, nil, err
}
@@ -507,7 +508,7 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroup
cliOpts.Restart = policy
}
- if cc.HostConfig.MemorySwappiness != nil && (!rootless.IsRootless() || rootless.IsRootless() && cgroupsv2 && cgroupsManager == "systemd") {
+ if cc.HostConfig.MemorySwappiness != nil && (!rootless.IsRootless() || rootless.IsRootless() && cgroupsv2 && rtc.Engine.CgroupManager == "systemd") {
cliOpts.MemorySwappiness = *cc.HostConfig.MemorySwappiness
} else {
cliOpts.MemorySwappiness = -1
diff --git a/cmd/podman/common/netflags.go b/cmd/podman/common/netflags.go
index 4d0a554a6..9941bc716 100644
--- a/cmd/podman/common/netflags.go
+++ b/cmd/podman/common/netflags.go
@@ -201,7 +201,7 @@ func NetFlagsToNetOptions(cmd *cobra.Command) (*entities.NetOptions, error) {
parts := strings.SplitN(network, ":", 2)
- ns, cniNets, err := specgen.ParseNetworkNamespace(network)
+ ns, cniNets, err := specgen.ParseNetworkNamespace(network, containerConfig.Containers.RootlessNetworking == "cni")
if err != nil {
return nil, err
}
diff --git a/libpod/container.go b/libpod/container.go
index 591cf9bc5..c6f0cd618 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -14,7 +14,6 @@ import (
"github.com/containers/image/v5/manifest"
"github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/libpod/lock"
- "github.com/containers/podman/v3/pkg/rootless"
"github.com/containers/storage"
"github.com/cri-o/ocicni/pkg/ocicni"
spec "github.com/opencontainers/runtime-spec/specs-go"
@@ -1168,7 +1167,7 @@ func (c *Container) Networks() ([]string, bool, error) {
func (c *Container) networks() ([]string, bool, error) {
networks, err := c.runtime.state.GetNetworks(c)
if err != nil && errors.Cause(err) == define.ErrNoSuchNetwork {
- if len(c.config.Networks) == 0 && !rootless.IsRootless() {
+ if len(c.config.Networks) == 0 && c.config.NetMode.IsBridge() {
return []string{c.runtime.netPlugin.GetDefaultNetworkName()}, true, nil
}
return c.config.Networks, false, nil
diff --git a/libpod/container_log_linux.go b/libpod/container_log_linux.go
index ec4fa9724..892ee34e3 100644
--- a/libpod/container_log_linux.go
+++ b/libpod/container_log_linux.go
@@ -6,14 +6,12 @@ package libpod
import (
"context"
"fmt"
- "io"
- "math"
"strings"
"time"
- "github.com/containers/podman/v3/libpod/define"
+ "github.com/containers/podman/v3/libpod/events"
"github.com/containers/podman/v3/libpod/logs"
- journal "github.com/coreos/go-systemd/v22/sdjournal"
+ "github.com/coreos/go-systemd/v22/sdjournal"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -24,122 +22,187 @@ const (
// journaldLogErr is the journald priority signifying stderr
journaldLogErr = "3"
-
- // bufLen is the length of the buffer to read from a k8s-file
- // formatted log line
- // let's set it as 2k just to be safe if k8s-file format ever changes
- bufLen = 16384
)
func (c *Container) readFromJournal(ctx context.Context, options *logs.LogOptions, logChannel chan *logs.LogLine) error {
- var config journal.JournalReaderConfig
- if options.Tail < 0 {
- config.NumFromTail = 0
- } else if options.Tail == 0 {
- config.NumFromTail = math.MaxUint64
- } else {
- config.NumFromTail = uint64(options.Tail)
+ journal, err := sdjournal.NewJournal()
+ if err != nil {
+ return err
}
- if options.Multi {
- config.Formatter = journalFormatterWithID
- } else {
- config.Formatter = journalFormatter
- }
- defaultTime := time.Time{}
- if options.Since != defaultTime {
- // coreos/go-systemd/sdjournal doesn't correctly handle requests for data in the future
- // return nothing instead of falsely printing
- if time.Now().Before(options.Since) {
- return nil
- }
- // coreos/go-systemd/sdjournal expects a negative time.Duration for times in the past
- config.Since = -time.Since(options.Since)
+ // While logs are written to the `logChannel`, we inspect each event
+ // and stop once the container has died. Having logs and events in one
+ // stream prevents a race condition that we faced in #10323.
+
+ // Add the filters for events.
+ match := sdjournal.Match{Field: "SYSLOG_IDENTIFIER", Value: "podman"}
+ if err := journal.AddMatch(match.String()); err != nil {
+ return errors.Wrapf(err, "adding filter to journald logger: %v", match)
+ }
+ match = sdjournal.Match{Field: "PODMAN_ID", Value: c.ID()}
+ if err := journal.AddMatch(match.String()); err != nil {
+ return errors.Wrapf(err, "adding filter to journald logger: %v", match)
}
- config.Matches = append(config.Matches, journal.Match{
- Field: "CONTAINER_ID_FULL",
- Value: c.ID(),
- })
- options.WaitGroup.Add(1)
- r, err := journal.NewJournalReader(config)
- if err != nil {
+ // Add the filter for logs. Note the disjunction so that we match
+ // either the events or the logs.
+ if err := journal.AddDisjunction(); err != nil {
+ return errors.Wrap(err, "adding filter disjunction to journald logger")
+ }
+ match = sdjournal.Match{Field: "CONTAINER_ID_FULL", Value: c.ID()}
+ if err := journal.AddMatch(match.String()); err != nil {
+ return errors.Wrapf(err, "adding filter to journald logger: %v", match)
+ }
+
+ if err := journal.SeekHead(); err != nil {
return err
}
- if r == nil {
- return errors.Errorf("journal reader creation failed")
+ // API requires Next() immediately after SeekHead().
+ if _, err := journal.Next(); err != nil {
+ return errors.Wrap(err, "initial journal cursor")
}
- if options.Tail == math.MaxInt64 {
- r.Rewind()
+
+ // API requires a next|prev before getting a cursor.
+ if _, err := journal.Previous(); err != nil {
+ return errors.Wrap(err, "initial journal cursor")
}
- state, err := c.State()
- if err != nil {
- return err
+
+ // Note that the initial cursor may not yet be ready, so we'll do an
+ // exponential backoff.
+ var cursor string
+ var cursorError error
+ for i := 1; i <= 3; i++ {
+ cursor, cursorError = journal.GetCursor()
+ if err != nil {
+ continue
+ }
+ time.Sleep(time.Duration(i*100) * time.Millisecond)
+ break
+ }
+ if cursorError != nil {
+ return errors.Wrap(cursorError, "inital journal cursor")
+ }
+
+ // We need the container's events in the same journal to guarantee
+ // consistency, see #10323.
+ if options.Follow && c.runtime.config.Engine.EventsLogger != "journald" {
+ return errors.Errorf("using --follow with the journald --log-driver but without the journald --events-backend (%s) is not supported", c.runtime.config.Engine.EventsLogger)
}
- if options.Follow && state == define.ContainerStateRunning {
- go func() {
- done := make(chan bool)
- until := make(chan time.Time)
- go func() {
- select {
- case <-ctx.Done():
- until <- time.Time{}
- case <-done:
- // nothing to do anymore
+ options.WaitGroup.Add(1)
+ go func() {
+ defer func() {
+ options.WaitGroup.Done()
+ if err := journal.Close(); err != nil {
+ logrus.Errorf("Unable to close journal: %v", err)
+ }
+ }()
+
+ afterTimeStamp := false // needed for options.Since
+ tailQueue := []*logs.LogLine{} // needed for options.Tail
+ doTail := options.Tail > 0
+ for {
+ select {
+ case <-ctx.Done():
+ // Remote client may have closed/lost the connection.
+ return
+ default:
+ // Fallthrough
+ }
+
+ if _, err := journal.Next(); err != nil {
+ logrus.Errorf("Failed to move journal cursor to next entry: %v", err)
+ return
+ }
+ latestCursor, err := journal.GetCursor()
+ if err != nil {
+ logrus.Errorf("Failed to get journal cursor: %v", err)
+ return
+ }
+
+ // Hit the end of the journal.
+ if cursor == latestCursor {
+ if doTail {
+ // Flush *once* we hit the end of the journal.
+ startIndex := int64(len(tailQueue)-1) - options.Tail
+ if startIndex < 0 {
+ startIndex = 0
+ }
+ for i := startIndex; i < int64(len(tailQueue)); i++ {
+ logChannel <- tailQueue[i]
+ }
+ tailQueue = nil
+ doTail = false
+ }
+ // Unless we follow, quit.
+ if !options.Follow {
+ return
}
- }()
- go func() {
- // FIXME (#10323): we are facing a terrible
- // race condition here. At the time the
- // container dies and `c.Wait()` has returned,
- // we may not have received all journald logs.
- // So far there is no other way than waiting
- // for a second. Ultimately, `r.Follow` is
- // racy and we may have to implement our custom
- // logic here.
- c.Wait(ctx)
- time.Sleep(time.Second)
- until <- time.Time{}
- }()
- follower := journaldFollowBuffer{logChannel, options.Multi}
- err := r.Follow(until, follower)
+ // Sleep until something's happening on the journal.
+ journal.Wait(sdjournal.IndefiniteWait)
+ continue
+ }
+ cursor = latestCursor
+
+ entry, err := journal.GetEntry()
if err != nil {
- logrus.Debugf(err.Error())
+ logrus.Errorf("Failed to get journal entry: %v", err)
+ return
}
- r.Close()
- options.WaitGroup.Done()
- done <- true
- return
- }()
- return nil
- }
- go func() {
- bytes := make([]byte, bufLen)
- // /me complains about no do-while in go
- ec, err := r.Read(bytes)
- for ec != 0 && err == nil {
- // because we are reusing bytes, we need to make
- // sure the old data doesn't get into the new line
- bytestr := string(bytes[:ec])
- logLine, err2 := logs.NewJournaldLogLine(bytestr, options.Multi)
- if err2 != nil {
- logrus.Error(err2)
+ if !afterTimeStamp {
+ entryTime := time.Unix(0, int64(entry.RealtimeTimestamp)*int64(time.Microsecond))
+ if entryTime.Before(options.Since) {
+ continue
+ }
+ afterTimeStamp = true
+ }
+
+ // If we're reading an event and the container exited/died,
+ // then we're done and can return.
+ event, ok := entry.Fields["PODMAN_EVENT"]
+ if ok {
+ status, err := events.StringToStatus(event)
+ if err != nil {
+ logrus.Errorf("Failed to translate event: %v", err)
+ return
+ }
+ if status == events.Exited {
+ return
+ }
+ continue
+ }
+
+ var message string
+ var formatError error
+
+ if options.Multi {
+ message, formatError = journalFormatterWithID(entry)
+ } else {
+ message, formatError = journalFormatter(entry)
+ }
+
+ if formatError != nil {
+ logrus.Errorf("Failed to parse journald log entry: %v", err)
+ return
+ }
+
+ logLine, err := logs.NewJournaldLogLine(message, options.Multi)
+ if err != nil {
+ logrus.Errorf("Failed parse log line: %v", err)
+ return
+ }
+ if doTail {
+ tailQueue = append(tailQueue, logLine)
continue
}
logChannel <- logLine
- ec, err = r.Read(bytes)
}
- if err != nil && err != io.EOF {
- logrus.Error(err)
- }
- r.Close()
- options.WaitGroup.Done()
}()
+
return nil
}
-func journalFormatterWithID(entry *journal.JournalEntry) (string, error) {
+func journalFormatterWithID(entry *sdjournal.JournalEntry) (string, error) {
output, err := formatterPrefix(entry)
if err != nil {
return "", err
@@ -162,7 +225,7 @@ func journalFormatterWithID(entry *journal.JournalEntry) (string, error) {
return output, nil
}
-func journalFormatter(entry *journal.JournalEntry) (string, error) {
+func journalFormatter(entry *sdjournal.JournalEntry) (string, error) {
output, err := formatterPrefix(entry)
if err != nil {
return "", err
@@ -176,7 +239,7 @@ func journalFormatter(entry *journal.JournalEntry) (string, error) {
return output, nil
}
-func formatterPrefix(entry *journal.JournalEntry) (string, error) {
+func formatterPrefix(entry *sdjournal.JournalEntry) (string, error) {
usec := entry.RealtimeTimestamp
tsString := time.Unix(0, int64(usec)*int64(time.Microsecond)).Format(logs.LogTimeFormat)
output := fmt.Sprintf("%s ", tsString)
@@ -202,7 +265,7 @@ func formatterPrefix(entry *journal.JournalEntry) (string, error) {
return output, nil
}
-func formatterMessage(entry *journal.JournalEntry) (string, error) {
+func formatterMessage(entry *sdjournal.JournalEntry) (string, error) {
// Finally, append the message
msg, ok := entry.Fields["MESSAGE"]
if !ok {
@@ -211,18 +274,3 @@ func formatterMessage(entry *journal.JournalEntry) (string, error) {
msg = strings.TrimSuffix(msg, "\n")
return msg, nil
}
-
-type journaldFollowBuffer struct {
- logChannel chan *logs.LogLine
- withID bool
-}
-
-func (f journaldFollowBuffer) Write(p []byte) (int, error) {
- bytestr := string(p)
- logLine, err := logs.NewJournaldLogLine(bytestr, f.withID)
- if err != nil {
- return -1, err
- }
- f.logChannel <- logLine
- return len(p), nil
-}
diff --git a/libpod/runtime.go b/libpod/runtime.go
index e551e6fe8..d14048311 100644
--- a/libpod/runtime.go
+++ b/libpod/runtime.go
@@ -17,6 +17,7 @@ import (
"github.com/containers/common/libimage"
"github.com/containers/common/pkg/config"
+ "github.com/containers/common/pkg/defaultnet"
"github.com/containers/common/pkg/secrets"
"github.com/containers/image/v5/pkg/sysregistriesv2"
is "github.com/containers/image/v5/storage"
@@ -458,6 +459,11 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (retErr error) {
}
}
+ // If we need to make a default network - do so now.
+ if err := defaultnet.Create(runtime.config.Network.DefaultNetwork, runtime.config.Network.DefaultSubnet, runtime.config.Network.NetworkConfigDir, runtime.config.Engine.StaticDir, runtime.config.Engine.MachineEnabled); err != nil {
+ logrus.Errorf("Failed to created default CNI network: %v", err)
+ }
+
// Set up the CNI net plugin
netPlugin, err := ocicni.InitCNI(runtime.config.Network.DefaultNetwork, runtime.config.Network.NetworkConfigDir, runtime.config.Network.CNIPluginDirs...)
if err != nil {
diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go
index 162a98135..8e9e1fb39 100644
--- a/pkg/api/handlers/compat/containers_create.go
+++ b/pkg/api/handlers/compat/containers_create.go
@@ -62,7 +62,7 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) {
}
// Take body structure and convert to cliopts
- cliOpts, args, err := common.ContainerCreateToContainerCLIOpts(body, rtc.Engine.CgroupManager)
+ cliOpts, args, err := common.ContainerCreateToContainerCLIOpts(body, rtc)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "make cli opts()"))
return
diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go
index 278f35c22..f41186ae4 100644
--- a/pkg/specgen/generate/namespaces.go
+++ b/pkg/specgen/generate/namespaces.go
@@ -66,7 +66,7 @@ func GetDefaultNamespaceMode(nsType string, cfg *config.Config, pod *libpod.Pod)
case "cgroup":
return specgen.ParseCgroupNamespace(cfg.Containers.CgroupNS)
case "net":
- ns, _, err := specgen.ParseNetworkNamespace(cfg.Containers.NetNS)
+ ns, _, err := specgen.ParseNetworkNamespace(cfg.Containers.NetNS, cfg.Containers.RootlessNetworking == "cni")
return ns, err
}
diff --git a/pkg/specgen/namespaces.go b/pkg/specgen/namespaces.go
index f665fc0be..80852930a 100644
--- a/pkg/specgen/namespaces.go
+++ b/pkg/specgen/namespaces.go
@@ -253,7 +253,7 @@ func ParseUserNamespace(ns string) (Namespace, error) {
// ParseNetworkNamespace parses a network namespace specification in string
// form.
// Returns a namespace and (optionally) a list of CNI networks to join.
-func ParseNetworkNamespace(ns string) (Namespace, []string, error) {
+func ParseNetworkNamespace(ns string, rootlessDefaultCNI bool) (Namespace, []string, error) {
toReturn := Namespace{}
var cniNetworks []string
// Net defaults to Slirp on rootless
@@ -264,7 +264,11 @@ func ParseNetworkNamespace(ns string) (Namespace, []string, error) {
toReturn.NSMode = FromPod
case ns == "" || ns == string(Default) || ns == string(Private):
if rootless.IsRootless() {
- toReturn.NSMode = Slirp
+ if rootlessDefaultCNI {
+ toReturn.NSMode = Bridge
+ } else {
+ toReturn.NSMode = Slirp
+ }
} else {
toReturn.NSMode = Bridge
}
diff --git a/test/e2e/logs_test.go b/test/e2e/logs_test.go
index 3051031a5..4d9cbb48b 100644
--- a/test/e2e/logs_test.go
+++ b/test/e2e/logs_test.go
@@ -163,7 +163,7 @@ var _ = Describe("Podman logs", func() {
})
It("podman logs on a created container should result in 0 exit code: "+log, func() {
- session := podmanTest.Podman([]string{"create", "-t", "--name", "log", ALPINE})
+ session := podmanTest.Podman([]string{"create", "--log-driver", log, "-t", "--name", "log", ALPINE})
session.WaitWithDefaultTimeout()
Expect(session).To(Exit(0))
diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go
index 37e837b1d..696cec76c 100644
--- a/test/e2e/run_networking_test.go
+++ b/test/e2e/run_networking_test.go
@@ -786,4 +786,18 @@ var _ = Describe("Podman run networking", func() {
Expect(session.ExitCode()).To(BeZero())
Expect(session.OutputToString()).To(ContainSubstring("search dns.podman"))
})
+
+ It("Rootless podman run with --net=bridge works and connects to default network", func() {
+ // This is harmless when run as root, so we'll just let it run.
+ ctrName := "testctr"
+ ctr := podmanTest.Podman([]string{"run", "-d", "--net=bridge", "--name", ctrName, ALPINE, "top"})
+ ctr.WaitWithDefaultTimeout()
+ Expect(ctr.ExitCode()).To(BeZero())
+
+ inspectOut := podmanTest.InspectContainer(ctrName)
+ Expect(len(inspectOut)).To(Equal(1))
+ Expect(len(inspectOut[0].NetworkSettings.Networks)).To(Equal(1))
+ _, ok := inspectOut[0].NetworkSettings.Networks["podman"]
+ Expect(ok).To(BeTrue())
+ })
})
diff --git a/test/system/035-logs.bats b/test/system/035-logs.bats
index 3dd88e5eb..ccf83df14 100644
--- a/test/system/035-logs.bats
+++ b/test/system/035-logs.bats
@@ -73,4 +73,56 @@ ${cid[0]} d" "Sequential output from logs"
_log_test_multi journald
}
+@test "podman logs - journald log driver requires journald events backend" {
+ skip_if_remote "remote does not support --events-backend"
+ # We can't use journald on RHEL as rootless: rhbz#1895105
+ skip_if_journald_unavailable
+
+ run_podman --events-backend=file run --log-driver=journald -d --name test --replace $IMAGE ls /
+ run_podman --events-backend=file logs test
+ run_podman 125 --events-backend=file logs --follow test
+ is "$output" "Error: using --follow with the journald --log-driver but without the journald --events-backend (file) is not supported" "journald logger requires journald eventer"
+}
+
+function _log_test_since() {
+ local driver=$1
+
+ s_before="before_$(random_string)_${driver}"
+ s_after="after_$(random_string)_${driver}"
+
+ before=$(date --iso-8601=seconds)
+ run_podman run --log-driver=$driver -d --name test $IMAGE sh -c \
+ "echo $s_before; trap 'echo $s_after; exit' SIGTERM; while :; do sleep 1; done"
+
+ # sleep a second to make sure the date is after the first echo
+ sleep 1
+ after=$(date --iso-8601=seconds)
+ run_podman stop test
+
+ run_podman logs test
+ is "$output" \
+ "$s_before
+$s_after"
+
+ run_podman logs --since $before test
+ is "$output" \
+ "$s_before
+$s_after"
+
+ run_podman logs --since $after test
+ is "$output" "$s_after"
+ run_podman rm -f test
+}
+
+@test "podman logs - since k8s-file" {
+ _log_test_since k8s-file
+}
+
+@test "podman logs - since journald" {
+ # We can't use journald on RHEL as rootless: rhbz#1895105
+ skip_if_journald_unavailable
+
+ _log_test_since journald
+}
+
# vim: filetype=sh
diff --git a/test/system/130-kill.bats b/test/system/130-kill.bats
index 1b02b4976..3770eac27 100644
--- a/test/system/130-kill.bats
+++ b/test/system/130-kill.bats
@@ -8,8 +8,7 @@ load helpers
@test "podman kill - test signal handling in containers" {
# Start a container that will handle all signals by emitting 'got: N'
local -a signals=(1 2 3 4 5 6 8 10 12 13 14 15 16 20 21 22 23 24 25 26 64)
- # Force the k8s-file driver until #10323 is fixed.
- run_podman run --log-driver=k8s-file -d $IMAGE sh -c \
+ run_podman run -d $IMAGE sh -c \
"for i in ${signals[*]}; do trap \"echo got: \$i\" \$i; done;
echo READY;
while ! test -e /stop; do sleep 0.05; done;
diff --git a/vendor/github.com/containers/common/pkg/defaultnet/default_network.go b/vendor/github.com/containers/common/pkg/defaultnet/default_network.go
new file mode 100644
index 000000000..9b32241d6
--- /dev/null
+++ b/vendor/github.com/containers/common/pkg/defaultnet/default_network.go
@@ -0,0 +1,222 @@
+package defaultnet
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net"
+ "os"
+ "path/filepath"
+ "regexp"
+ "text/template"
+
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+// TODO: A smarter implementation would make sure cni-podman0 was unused before
+// making the default, and adjust if necessary
+const networkTemplate = `{
+ "cniVersion": "0.4.0",
+ "name": "{{{{.Name}}}}",
+ "plugins": [
+ {
+ "type": "bridge",
+ "bridge": "cni-podman0",
+ "isGateway": true,
+ "ipMasq": true,
+ "hairpinMode": true,
+ "ipam": {
+ "type": "host-local",
+ "routes": [{ "dst": "0.0.0.0/0" }],
+ "ranges": [
+ [
+ {
+ "subnet": "{{{{.Subnet}}}}",
+ "gateway": "{{{{.Gateway}}}}"
+ }
+ ]
+ ]
+ }
+ },
+{{{{- if (eq .Machine true) }}}}
+ {
+ "type": "podman-machine",
+ "capabilities": {
+ "portMappings": true
+ }
+ },
+{{{{- end}}}}
+ {
+ "type": "portmap",
+ "capabilities": {
+ "portMappings": true
+ }
+ },
+ {
+ "type": "firewall"
+ },
+ {
+ "type": "tuning"
+ }
+ ]
+}
+`
+
+var (
+ // Borrowed from Podman, modified to remove dashes and periods.
+ nameRegex = regexp.MustCompile("^[a-zA-Z0-9][a-zA-Z0-9_]*$")
+)
+
+// Used to pass info into the template engine
+type networkInfo struct {
+ Name string
+ Subnet string
+ Gateway string
+ Machine bool
+}
+
+// The most trivial definition of a CNI network possible for our use here.
+// We need the name, and nothing else.
+type network struct {
+ Name string `json:"name"`
+}
+
+// Create makes the CNI default network, if necessary.
+// Accepts the name and subnet of the network to create (a standard template
+// will be used, with these values plugged in), the configuration directory
+// where CNI configs are stored (to verify if a named configuration already
+// exists), an exists directory (where a sentinel file will be stored, to ensure
+// the network is only made once), and an isMachine bool (to determine whether
+// the machine block will be added to the config).
+// Create first checks if a default network has already been created via the
+// presence of a sentinel file. If it does exist, it returns immediately without
+// error.
+// It next checks if a CNI network with the given name already exists. In that
+// case, it creates the sentinel file and returns without error.
+// If neither of these are true, the default network is created.
+func Create(name, subnet, configDir, existsDir string, isMachine bool) error {
+ // TODO: Should probably regex name to make sure it's valid.
+ if name == "" || subnet == "" || configDir == "" || existsDir == "" {
+ return errors.Errorf("must provide values for all arguments to MakeDefaultNetwork")
+ }
+ if !nameRegex.MatchString(name) {
+ return errors.Errorf("invalid default network name %s - letters, numbers, and underscores only", name)
+ }
+
+ sentinelFile := filepath.Join(existsDir, "defaultCNINetExists")
+
+ // Check if sentinel file exists, return immediately if it does.
+ if _, err := os.Stat(sentinelFile); err == nil {
+ return nil
+ }
+
+ // Create the sentinel file if it doesn't exist, so subsequent checks
+ // don't need to go further.
+ file, err := os.Create(sentinelFile)
+ if err != nil {
+ return err
+ }
+ file.Close()
+
+ // We may need to make the config dir.
+ if err := os.MkdirAll(configDir, 0755); err != nil && !os.IsExist(err) {
+ return errors.Wrapf(err, "error creating CNI configuration directory")
+ }
+
+ // Check all networks in the CNI conflist.
+ files, err := ioutil.ReadDir(configDir)
+ if err != nil {
+ return errors.Wrapf(err, "error reading CNI configuration directory")
+ }
+ if len(files) > 0 {
+ configPaths := make([]string, 0, len(files))
+ for _, path := range files {
+ if !path.IsDir() && filepath.Ext(path.Name()) == ".conflist" {
+ configPaths = append(configPaths, filepath.Join(configDir, path.Name()))
+ }
+ }
+ for _, config := range configPaths {
+ configName, err := getConfigName(config)
+ if err != nil {
+ logrus.Errorf("Error reading CNI configuration file: %v", err)
+ continue
+ }
+ if configName == name {
+ return nil
+ }
+ }
+ }
+
+ // We need to make the config.
+ // Get subnet and gateway.
+ _, ipNet, err := net.ParseCIDR(subnet)
+ if err != nil {
+ return errors.Wrapf(err, "default network subnet %s is invalid", subnet)
+ }
+
+ ones, bits := ipNet.Mask.Size()
+ if ones == bits {
+ return errors.Wrapf(err, "default network subnet %s is to small", subnet)
+ }
+ gateway := make(net.IP, len(ipNet.IP))
+ // copy the subnet ip to the gateway so we can modify it
+ copy(gateway, ipNet.IP)
+ // the default gateway should be the first ip in the subnet
+ gateway[len(gateway)-1]++
+
+ netInfo := new(networkInfo)
+ netInfo.Name = name
+ netInfo.Gateway = gateway.String()
+ netInfo.Subnet = ipNet.String()
+ netInfo.Machine = isMachine
+
+ templ, err := template.New("network_template").Delims("{{{{", "}}}}").Parse(networkTemplate)
+ if err != nil {
+ return errors.Wrapf(err, "error compiling template for default network")
+ }
+ var output bytes.Buffer
+ if err := templ.Execute(&output, netInfo); err != nil {
+ return errors.Wrapf(err, "error executing template for default network")
+ }
+
+ // Next, we need to place the config on disk.
+ // Loop through possible indexes, with a limit of 100 attempts.
+ created := false
+ for i := 87; i < 187; i++ {
+ configFile, err := os.OpenFile(filepath.Join(configDir, fmt.Sprintf("%d-%s.conflist", i, name)), os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
+ if err != nil {
+ logrus.Infof("Attempt to create default CNI network config file failed: %v", err)
+ continue
+ }
+ defer configFile.Close()
+
+ created = true
+
+ // Success - file is open. Write our buffer to it.
+ if _, err := configFile.Write(output.Bytes()); err != nil {
+ return errors.Wrapf(err, "error writing default CNI config to file")
+ }
+ break
+ }
+ if !created {
+ return errors.Errorf("no available default network configuration file was found")
+ }
+
+ return nil
+}
+
+// Get the name of the configuration contained in a given conflist file. Accepts
+// the full path of a .conflist CNI configuration.
+func getConfigName(file string) (string, error) {
+ contents, err := ioutil.ReadFile(file)
+ if err != nil {
+ return "", err
+ }
+ config := new(network)
+ if err := json.Unmarshal(contents, config); err != nil {
+ return "", errors.Wrapf(err, "error decoding CNI configuration %s", filepath.Base(file))
+ }
+ return config.Name, nil
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index b4c2c6330..50f8e7338 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -102,6 +102,7 @@ github.com/containers/common/pkg/cgroupv2
github.com/containers/common/pkg/chown
github.com/containers/common/pkg/completion
github.com/containers/common/pkg/config
+github.com/containers/common/pkg/defaultnet
github.com/containers/common/pkg/filters
github.com/containers/common/pkg/manifests
github.com/containers/common/pkg/parse