summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-x.github/actions/check_cirrus_cron/cron_failures.sh116
-rw-r--r--.github/workflows/check_cirrus_cron.yml81
-rw-r--r--cmd/podman/networks/connect.go47
-rw-r--r--cmd/podman/networks/disconnect.go45
-rw-r--r--commands-demo.md2
-rw-r--r--contrib/cirrus/cron-fail_addrs.csv1
-rw-r--r--docs/source/markdown/podman-network-connect.1.md34
-rw-r--r--docs/source/markdown/podman-network-disconnect.1.md29
-rw-r--r--docs/source/markdown/podman-network.1.md2
-rw-r--r--docs/source/network.rst4
-rwxr-xr-xhack/release.sh2
-rw-r--r--libpod/container.go24
-rw-r--r--libpod/define/errors.go3
-rw-r--r--libpod/events.go12
-rw-r--r--libpod/events/config.go8
-rw-r--r--libpod/events/events.go8
-rw-r--r--libpod/events/journal_linux.go6
-rw-r--r--libpod/events/logfile.go2
-rw-r--r--libpod/networking_linux.go179
-rw-r--r--pkg/api/handlers/compat/networks.go37
-rw-r--r--pkg/api/handlers/libpod/networks.go26
-rw-r--r--pkg/api/server/register_networks.go54
-rw-r--r--pkg/bindings/network/network.go38
-rw-r--r--pkg/domain/entities/engine_container.go2
-rw-r--r--pkg/domain/entities/network.go14
-rw-r--r--pkg/domain/infra/abi/network.go11
-rw-r--r--pkg/domain/infra/tunnel/network.go10
-rw-r--r--test/e2e/mount_test.go3
-rw-r--r--test/e2e/network_test.go153
-rw-r--r--test/e2e/push_test.go11
-rw-r--r--test/e2e/run_cgroup_parent_test.go2
-rw-r--r--test/e2e/run_signal_test.go4
-rw-r--r--test/e2e/trust_test.go3
33 files changed, 892 insertions, 81 deletions
diff --git a/.github/actions/check_cirrus_cron/cron_failures.sh b/.github/actions/check_cirrus_cron/cron_failures.sh
new file mode 100755
index 000000000..2693df417
--- /dev/null
+++ b/.github/actions/check_cirrus_cron/cron_failures.sh
@@ -0,0 +1,116 @@
+#!/bin/bash
+
+set -eo pipefail
+
+# Intended to be executed from a github action workflow step.
+# Outputs the Cirrus cron names and IDs of any failed builds
+
+err() {
+ # Ref: https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions
+ echo "::error file=${BASH_SOURCE[0]},line=${BASH_LINENO[0]}::${1:-No error message given}"
+ exit 1
+}
+
+_errfmt="Expecting %s value to not be empty"
+if [[ -z "$GITHUB_REPOSITORY" ]]; then
+ err $(printf "$_errfmt" "\$GITHUB_REPOSITORY")
+elif [[ -z "$NAME_ID_FILEPATH" ]]; then
+ err $(printf "$_errfmt" "\$NAME_ID_FILEPATH")
+fi
+
+mkdir -p artifacts
+cat > ./artifacts/query_raw.json << "EOF"
+{"query":"
+ query CronNameStatus($owner: String!, $repo: String!) {
+ githubRepository(owner: $owner, name: $repo) {
+ cronSettings {
+ name
+ lastInvocationBuild {
+ id
+ status
+ }
+ }
+ }
+ }
+",
+"variables":"{
+ \"owner\": \"@@OWNER@@\",
+ \"repo\": \"@@REPO@@\"
+}"}
+EOF
+# Makes for easier copy/pasting query to/from
+# https://cirrus-ci.com/explorer
+owner=$(cut -d '/' -f 1 <<<"$GITHUB_REPOSITORY")
+repo=$(cut -d '/' -f 2 <<<"$GITHUB_REPOSITORY")
+sed -i -r -e "s/@@OWNER@@/$owner/g" -e "s/@@REPO@@/$repo/g" ./artifacts/query_raw.json
+
+echo "::group::Posting GraphQL Query"
+# Easier to debug in error-reply when query is compacted
+tr -d '\n' < ./artifacts/query_raw.json | tr -s ' ' | tee ./artifacts/query.json | \
+ jq --indent 4 --color-output .
+
+if grep -q '@@' ./artifacts/query.json; then
+ err "Found unreplaced substitution token in raw query JSON"
+fi
+curl \
+ --request POST \
+ --silent \
+ --location \
+ --header 'content-type: application/json' \
+ --url 'https://api.cirrus-ci.com/graphql' \
+ --data @./artifacts/query.json \
+ --output ./artifacts/reply.json
+echo "::endgroup::"
+
+echo "::group::Received GraphQL Reply"
+jq --indent 4 --color-output . <./artifacts/reply.json || \
+ cat ./artifacts/reply.json
+echo "::endgroup::"
+
+# Desireable to catch non-JSON encoded errors in reply.
+if grep -qi 'error' ./artifacts/reply.json; then
+ err "Found the word 'error' in reply"
+fi
+
+# e.x. reply.json
+# {
+# "data": {
+# "githubRepository": {
+# "cronSettings": [
+# {
+# "name": "Keepalive_v2.0",
+# "lastInvocationBuild": {
+# "id": "5776050544181248",
+# "status": "EXECUTING"
+# }
+# },
+# {
+# "name": "Keepalive_v1.9",
+# "lastInvocationBuild": {
+# "id": "5962921081569280",
+# "status": "COMPLETED"
+# }
+# },
+# {
+# "name": "Keepalive_v2.0.5-rhel",
+# "lastInvocationBuild": {
+# "id": "5003065549914112",
+# "status": "FAILED"
+# }
+# }
+# ]
+# }
+# }
+# }
+_filt='.data.githubRepository.cronSettings | map(select(.lastInvocationBuild.status=="FAILED") | { name:.name, id:.lastInvocationBuild.id} | join(" ")) | join("\n")'
+jq --raw-output "$_filt" ./artifacts/reply.json > "$NAME_ID_FILEPATH"
+
+echo "<Cron Name> <Failed Build ID>"
+cat "$NAME_ID_FILEPATH"
+
+# Don't rely on a newline present for zero/one output line, always count words
+records=$(wc --words "$NAME_ID_FILEPATH" | cut -d ' ' -f 1)
+# Always two words per record
+failures=$((records/2))
+echo "::set-output name=failures::$failures"
+echo "Total failed Cirrus-CI cron builds: $failures"
diff --git a/.github/workflows/check_cirrus_cron.yml b/.github/workflows/check_cirrus_cron.yml
new file mode 100644
index 000000000..86f8c26dc
--- /dev/null
+++ b/.github/workflows/check_cirrus_cron.yml
@@ -0,0 +1,81 @@
+---
+
+# Format Ref: https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions
+
+# Required to un-FUBAR default ${{github.workflow}} value
+name: check_cirrus_cron
+
+on:
+ schedule:
+ # Assume cirrus cron jobs runs at least once per day
+ - cron: '59 23 * * *'
+ # Debug: Allow triggering job manually in github-actions WebUI
+ workflow_dispatch: {}
+
+env:
+ # Debug-mode can reveal secrets, only enable by a secret value.
+ # Ref: https://help.github.com/en/actions/configuring-and-managing-workflows/managing-a-workflow-run#enabling-step-debug-logging
+ ACTIONS_STEP_DEBUG: '${{ secrets.ACTIONS_STEP_DEBUG }}'
+ # File with CSV listing of zero or more e-mail addresses for delivery
+ # of daily failure notice e-mails.
+ FAILMAILCSV: './contrib/cirrus/cron-fail_addrs.csv'
+ # Filename for table of cron-name to build-id data
+ # (must be in $GITHUB_WORKSPACE/artifacts/)
+ NAME_ID_FILEPATH: './artifacts/name_id.txt'
+
+jobs:
+ cron_failures:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ ref: master
+ persist-credentials: false
+
+ - name: Get failed cron names and Build IDs
+ id: cron
+ run: './.github/actions/${{ github.workflow }}/${{ github.job }}.sh'
+
+ - if: steps.cron.outputs.failures > 0
+ shell: bash
+ # Must be inline, since context expressions are used.
+ # Ref: https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions
+ run: |
+ set -eo pipefail
+ (
+ echo "Detected one or more Cirrus-CI cron-triggered jobs have failed recently:"
+ echo ""
+
+ while read -r NAME BID; do
+ echo "Cron build '$NAME' Failed: https://cirrus-ci.com/build/$BID"
+ done < "$NAME_ID_FILEPATH"
+
+ echo ""
+ echo "# Source: ${{ github.workflow }} workflow on ${{ github.repository }}."
+ # Separate content from sendgrid.com automatic footer.
+ echo ""
+ ) > ./artifacts/email_body.txt
+
+ - if: steps.cron.outputs.failures > 0
+ id: mailto
+ run: printf "::set-output name=csv::%s\n" $(cat "$FAILMAILCSV")
+
+ - if: steps.mailto.outputs.csv != ''
+ name: Send failure notification e-mail
+ # Ref: https://github.com/dawidd6/action-send-mail
+ uses: dawidd6/action-send-mail@v2.2.2
+ with:
+ server_address: ${{secrets.ACTION_MAIL_SERVER}}
+ server_port: 465
+ username: ${{secrets.ACTION_MAIL_USERNAME}}
+ password: ${{secrets.ACTION_MAIL_PASSWORD}}
+ subject: Cirrus-CI cron build failures on ${{github.repository}}
+ to: ${{steps.mailto.outputs.csv}}
+ from: ${{secrets.ACTION_MAIL_SENDER}}
+ body: file://./artifacts/email_body.txt
+
+ - if: always()
+ uses: actions/upload-artifact@v2
+ with:
+ name: ${{ github.job }}_artifacts
+ path: artifacts/*
diff --git a/cmd/podman/networks/connect.go b/cmd/podman/networks/connect.go
new file mode 100644
index 000000000..a7636688c
--- /dev/null
+++ b/cmd/podman/networks/connect.go
@@ -0,0 +1,47 @@
+package network
+
+import (
+ "github.com/containers/common/pkg/completion"
+ "github.com/containers/podman/v2/cmd/podman/common"
+ "github.com/containers/podman/v2/cmd/podman/registry"
+ "github.com/containers/podman/v2/pkg/domain/entities"
+ "github.com/spf13/cobra"
+)
+
+var (
+ networkConnectDescription = `Add container to a network`
+ networkConnectCommand = &cobra.Command{
+ Use: "connect [options] NETWORK CONTAINER",
+ Short: "network connect",
+ Long: networkConnectDescription,
+ RunE: networkConnect,
+ Example: `podman network connect web secondary`,
+ Args: cobra.ExactArgs(2),
+ ValidArgsFunction: common.AutocompleteNetworks,
+ }
+)
+
+var (
+ networkConnectOptions entities.NetworkConnectOptions
+)
+
+func networkConnectFlags(cmd *cobra.Command) {
+ flags := cmd.Flags()
+ aliasFlagName := "alias"
+ flags.StringSliceVar(&networkConnectOptions.Aliases, aliasFlagName, []string{}, "network scoped alias for container")
+ _ = cmd.RegisterFlagCompletionFunc(aliasFlagName, completion.AutocompleteNone)
+}
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: networkConnectCommand,
+ Parent: networkCmd,
+ })
+ networkConnectFlags(networkConnectCommand)
+}
+
+func networkConnect(cmd *cobra.Command, args []string) error {
+ networkConnectOptions.Container = args[1]
+ return registry.ContainerEngine().NetworkConnect(registry.Context(), args[0], networkConnectOptions)
+}
diff --git a/cmd/podman/networks/disconnect.go b/cmd/podman/networks/disconnect.go
new file mode 100644
index 000000000..598c23a1c
--- /dev/null
+++ b/cmd/podman/networks/disconnect.go
@@ -0,0 +1,45 @@
+package network
+
+import (
+ "github.com/containers/podman/v2/cmd/podman/common"
+ "github.com/containers/podman/v2/cmd/podman/registry"
+ "github.com/containers/podman/v2/pkg/domain/entities"
+ "github.com/spf13/cobra"
+ "github.com/spf13/pflag"
+)
+
+var (
+ networkDisconnectDescription = `Remove container from a network`
+ networkDisconnectCommand = &cobra.Command{
+ Use: "disconnect [options] NETWORK CONTAINER",
+ Short: "network rm",
+ Long: networkDisconnectDescription,
+ RunE: networkDisconnect,
+ Example: `podman network disconnect web secondary`,
+ Args: cobra.ExactArgs(2),
+ ValidArgsFunction: common.AutocompleteNetworks,
+ }
+)
+
+var (
+ networkDisconnectOptions entities.NetworkDisconnectOptions
+)
+
+func networkDisconnectFlags(flags *pflag.FlagSet) {
+ flags.BoolVarP(&networkDisconnectOptions.Force, "force", "f", false, "force removal of container from network")
+}
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: networkDisconnectCommand,
+ Parent: networkCmd,
+ })
+ flags := networkDisconnectCommand.Flags()
+ networkDisconnectFlags(flags)
+}
+
+func networkDisconnect(cmd *cobra.Command, args []string) error {
+ networkDisconnectOptions.Container = args[1]
+ return registry.ContainerEngine().NetworkDisconnect(registry.Context(), args[0], networkDisconnectOptions)
+}
diff --git a/commands-demo.md b/commands-demo.md
index 74879842f..cea7a63a3 100644
--- a/commands-demo.md
+++ b/commands-demo.md
@@ -47,6 +47,8 @@
| [podman-mount(1)](https://podman.readthedocs.io/en/latest/markdown/podman-mount.1.html) | Mount a working container's root filesystem |
| [podman-network(1)](https://podman.readthedocs.io/en/latest/network.html) | Manage Podman CNI networks |
| [podman-network-create(1)](https://podman.readthedocs.io/en/latest/markdown/podman-network-create.1.html) | Create a CNI network |
+| [podman-network-connect(1)](https://podman.readthedocs.io/en/latest/markdown/podman-network-connect.1.html) | Connect a container to a CNI network |
+| [podman-network-disconnect(1)](https://podman.readthedocs.io/en/latest/markdown/podman-network-dosconnect.1.html) | Disconnect a container from a CNI network |
| [podman-network-inspect(1)](https://podman.readthedocs.io/en/latest/markdown/podman-network-inspect.1.html) | Displays the raw CNI network configuration for one or more networks |
| [podman-network-ls(1)](https://podman.readthedocs.io/en/latest/markdown/podman-network-ls.1.html) | Display a summary of CNI networks |
| [podman-network-rm(1)](https://podman.readthedocs.io/en/latest/markdown/podman-network-rm.1.html) | Remove one or more CNI networks |
diff --git a/contrib/cirrus/cron-fail_addrs.csv b/contrib/cirrus/cron-fail_addrs.csv
new file mode 100644
index 000000000..c25fc1226
--- /dev/null
+++ b/contrib/cirrus/cron-fail_addrs.csv
@@ -0,0 +1 @@
+rh.container.bot@gmail.com
diff --git a/docs/source/markdown/podman-network-connect.1.md b/docs/source/markdown/podman-network-connect.1.md
new file mode 100644
index 000000000..58b6e5c44
--- /dev/null
+++ b/docs/source/markdown/podman-network-connect.1.md
@@ -0,0 +1,34 @@
+% podman-network-connect(1)
+
+## NAME
+podman\-network\-connect - Connect a container to a network
+
+## SYNOPSIS
+**podman network connect** [*options*] network container
+
+## DESCRIPTION
+Connects a container to a network. A container can be connected to a network by name or by ID.
+Once connected, the container can communicate with other containers in the same network.
+
+## OPTIONS
+#### **--alias**
+Add network-scoped alias for the container. If the network is using the `dnsname` CNI plugin, these aliases
+can be used for name resolution on the given network. Multiple *--alias* options may be specificed as input.
+
+## EXAMPLE
+
+Connect a container named *web* to a network named *test*
+```
+podman network connect test web
+```
+
+Connect a container name *web* to a network named *test* with two aliases: web1 and web2
+```
+podman network connect --alias web1 --alias web2 test web
+```
+
+## SEE ALSO
+podman(1), podman-network(1), podman-network-disconnect(1), podman-network-inspect(1)
+
+## HISTORY
+November 2020, Originally compiled by Brent Baude <bbaude@redhat.com>
diff --git a/docs/source/markdown/podman-network-disconnect.1.md b/docs/source/markdown/podman-network-disconnect.1.md
new file mode 100644
index 000000000..95c7018a8
--- /dev/null
+++ b/docs/source/markdown/podman-network-disconnect.1.md
@@ -0,0 +1,29 @@
+% podman-network-disconnect(1)
+
+## NAME
+podman\-network\-disconnect - Disconnect a container from a network
+
+## SYNOPSIS
+**podman network disconnect** [*options*] network container
+
+## DESCRIPTION
+Disconnects a container from a network.
+
+## OPTIONS
+#### **--force**, **-f**
+
+Force the container to disconnect from a network
+
+## EXAMPLE
+Disconnect a container named *web* from a network called *test*.
+
+```
+podman network disconnect test web
+```
+
+
+## SEE ALSO
+podman(1), podman-network(1), podman-network-connect(1)
+
+## HISTORY
+November 2020, Originally compiled by Brent Baude <bbaude@redhat.com>
diff --git a/docs/source/markdown/podman-network.1.md b/docs/source/markdown/podman-network.1.md
index f05b2b78f..d21b200d9 100644
--- a/docs/source/markdown/podman-network.1.md
+++ b/docs/source/markdown/podman-network.1.md
@@ -13,7 +13,9 @@ The network command manages CNI networks for Podman. It is not supported for roo
| Command | Man Page | Description |
| ------- | --------------------------------------------------- | ---------------------------------------------------------------------------- |
+| connect | [podman-network-connect(1)](podman-network-connect.1.md)| Connect a container to a network|
| create | [podman-network-create(1)](podman-network-create.1.md)| Create a Podman CNI network|
+| disconnect | [podman-network-disconnect(1)](podman-network-disconnect.1.md)| Disconnect a container from a network|
| inspect | [podman-network-inspect(1)](podman-network-inspect.1.md)| Displays the raw CNI network configuration for one or more networks|
| ls | [podman-network-ls(1)](podman-network-ls.1.md)| Display a summary of CNI networks |
| rm | [podman-network-rm(1)](podman-network-rm.1.md)| Remove one or more CNI networks |
diff --git a/docs/source/network.rst b/docs/source/network.rst
index e7848c90e..0414c0880 100644
--- a/docs/source/network.rst
+++ b/docs/source/network.rst
@@ -1,8 +1,12 @@
Network
=======
+:doc:`connect <markdown/podman-network-connect.1>` network connect
+
:doc:`create <markdown/podman-network-create.1>` network create
+:doc:`disconnect <markdown/podman-network-disconnect.1>` network disconnect
+
:doc:`inspect <markdown/podman-network-inspect.1>` network inspect
:doc:`ls <markdown/podman-network-ls.1>` network list
diff --git a/hack/release.sh b/hack/release.sh
index 56cd04079..e9eeaa73b 100755
--- a/hack/release.sh
+++ b/hack/release.sh
@@ -27,7 +27,7 @@ LAST_TAG=$(git describe --tags --abbrev=0)
write_go_version()
{
LOCAL_VERSION="$1"
- sed -i "s/^\(var Version = semver.MustParse\( \"\).*/\1${LOCAL_VERSION}\"\)/" version/version.go
+ sed -i "s/^\(var Version = semver.MustParse( *\"\).*/\1${LOCAL_VERSION}\")/" version/version.go
}
write_spec_version()
diff --git a/libpod/container.go b/libpod/container.go
index 9009a4ec8..4b9e6a5ba 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -206,6 +206,10 @@ type ContainerState struct {
// and not delegated to the OCI runtime.
ExtensionStageHooks map[string][]spec.Hook `json:"extensionStageHooks,omitempty"`
+ // NetInterfaceDescriptions describe the relationship between a CNI
+ // network and an interface names
+ NetInterfaceDescriptions ContainerNetworkDescriptions `json:"networkDescriptions,omitempty"`
+
// containerPlatformState holds platform-specific container state.
containerPlatformState
}
@@ -244,6 +248,10 @@ type ContainerImageVolume struct {
ReadWrite bool `json:"rw"`
}
+// ContainerNetworkDescriptions describes the relationship between the CNI
+// network and the ethN where N is an integer
+type ContainerNetworkDescriptions map[string]int
+
// Config accessors
// Unlocked
@@ -1102,3 +1110,19 @@ func (c *Container) networksByNameIndex() (map[string]int, error) {
}
return networkNamesByIndex, nil
}
+
+// add puts the new given CNI network name into the tracking map
+// and assigns it a new integer based on the map length
+func (d ContainerNetworkDescriptions) add(networkName string) {
+ d[networkName] = len(d)
+}
+
+// getInterfaceByName returns a formatted interface name for a given
+// network along with a bool as to whether the network existed
+func (d ContainerNetworkDescriptions) getInterfaceByName(networkName string) (string, bool) {
+ val, exists := d[networkName]
+ if !exists {
+ return "", exists
+ }
+ return fmt.Sprintf("eth%d", val), exists
+}
diff --git a/libpod/define/errors.go b/libpod/define/errors.go
index 471827b7c..b96d36429 100644
--- a/libpod/define/errors.go
+++ b/libpod/define/errors.go
@@ -178,4 +178,7 @@ var (
// ErrStoreNotInitialized indicates that the container storage was never
// initialized.
ErrStoreNotInitialized = errors.New("the container storage was never initialized")
+
+ // ErrNoNetwork indicates that a container has no net namespace, like network=none
+ ErrNoNetwork = errors.New("container has no network namespace")
)
diff --git a/libpod/events.go b/libpod/events.go
index 95317eb01..e199a3846 100644
--- a/libpod/events.go
+++ b/libpod/events.go
@@ -50,6 +50,18 @@ func (c *Container) newContainerExitedEvent(exitCode int32) {
}
}
+// netNetworkEvent creates a new event based on a network connect/disconnect
+func (c *Container) newNetworkEvent(status events.Status, netName string) {
+ e := events.NewEvent(status)
+ e.ID = c.ID()
+ e.Name = c.Name()
+ e.Type = events.Network
+ e.Network = netName
+ if err := c.runtime.eventer.Write(e); err != nil {
+ logrus.Errorf("unable to write pod event: %q", err)
+ }
+}
+
// newPodEvent creates a new event for a libpod pod
func (p *Pod) newPodEvent(status events.Status) {
e := events.NewEvent(status)
diff --git a/libpod/events/config.go b/libpod/events/config.go
index 2ec3111fe..af09a65ae 100644
--- a/libpod/events/config.go
+++ b/libpod/events/config.go
@@ -30,6 +30,8 @@ type Event struct {
Image string `json:",omitempty"`
// Name where applicable
Name string `json:",omitempty"`
+ // Network is the network name in a network event
+ Network string `json:"network,omitempty"`
// Status describes the event that occurred
Status Status
// Time the event occurred
@@ -101,6 +103,8 @@ const (
Container Type = "container"
// Image - event is related to images
Image Type = "image"
+ // Network - event is related to networks
+ Network Type = "network"
// Pod - event is related to pods
Pod Type = "pod"
// System - event is related to Podman whole and not to any specific
@@ -141,6 +145,10 @@ const (
LoadFromArchive Status = "loadfromarchive"
// Mount ...
Mount Status = "mount"
+ // NetworkConnect
+ NetworkConnect Status = "connect"
+ // NetworkDisconnect
+ NetworkDisconnect Status = "disconnect"
// Pause ...
Pause Status = "pause"
// Prune ...
diff --git a/libpod/events/events.go b/libpod/events/events.go
index 42939d64c..4e7267af3 100644
--- a/libpod/events/events.go
+++ b/libpod/events/events.go
@@ -77,6 +77,8 @@ func (e *Event) ToHumanReadable() string {
}
}
humanFormat += ")"
+ case Network:
+ humanFormat = fmt.Sprintf("%s %s %s %s (container=%s, name=%s)", e.Time, e.Type, e.Status, e.ID, e.ID, e.Network)
case Image:
humanFormat = fmt.Sprintf("%s %s %s %s %s", e.Time, e.Type, e.Status, e.ID, e.Name)
case System:
@@ -115,6 +117,8 @@ func StringToType(name string) (Type, error) {
return Container, nil
case Image.String():
return Image, nil
+ case Network.String():
+ return Network, nil
case Pod.String():
return Pod, nil
case System.String():
@@ -162,6 +166,10 @@ func StringToStatus(name string) (Status, error) {
return LoadFromArchive, nil
case Mount.String():
return Mount, nil
+ case NetworkConnect.String():
+ return NetworkConnect, nil
+ case NetworkDisconnect.String():
+ return NetworkDisconnect, nil
case Pause.String():
return Pause, nil
case Prune.String():
diff --git a/libpod/events/journal_linux.go b/libpod/events/journal_linux.go
index 5e3be8009..9a514e302 100644
--- a/libpod/events/journal_linux.go
+++ b/libpod/events/journal_linux.go
@@ -56,6 +56,9 @@ func (e EventJournalD) Write(ee Event) error {
}
m["PODMAN_LABELS"] = string(b)
}
+ case Network:
+ m["PODMAN_ID"] = ee.ID
+ m["PODMAN_NETWORK_NAME"] = ee.Network
case Volume:
m["PODMAN_NAME"] = ee.Name
}
@@ -197,6 +200,9 @@ func newEventFromJournalEntry(entry *sdjournal.JournalEntry) (*Event, error) { /
newEvent.Details = Details{Attributes: labels}
}
}
+ case Network:
+ newEvent.ID = entry.Fields["PODMAN_ID"]
+ newEvent.Network = entry.Fields["PODMAN_NETWORK_NAME"]
case Image:
newEvent.ID = entry.Fields["PODMAN_ID"]
}
diff --git a/libpod/events/logfile.go b/libpod/events/logfile.go
index b70102450..57e38b815 100644
--- a/libpod/events/logfile.go
+++ b/libpod/events/logfile.go
@@ -76,7 +76,7 @@ func (e EventLogFile) Read(ctx context.Context, options ReadOptions) error {
return err
}
switch event.Type {
- case Image, Volume, Pod, System, Container:
+ case Image, Volume, Pod, System, Container, Network:
// no-op
default:
return errors.Errorf("event type %s is not valid in %s", event.Type.String(), e.options.LogFilePath)
diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go
index 3882e095a..8dce7c9fe 100644
--- a/libpod/networking_linux.go
+++ b/libpod/networking_linux.go
@@ -21,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/events"
"github.com/containers/podman/v2/libpod/network"
"github.com/containers/podman/v2/pkg/errorhandling"
"github.com/containers/podman/v2/pkg/netns"
@@ -34,16 +35,16 @@ import (
)
// Get an OCICNI network config
-func (r *Runtime) getPodNetwork(id, name, nsPath string, networks []string, ports []ocicni.PortMapping, staticIP net.IP, staticMAC net.HardwareAddr) ocicni.PodNetwork {
+func (r *Runtime) getPodNetwork(id, name, nsPath string, networks []string, ports []ocicni.PortMapping, staticIP net.IP, staticMAC net.HardwareAddr, netDescriptions ContainerNetworkDescriptions) ocicni.PodNetwork {
var networkKey string
if len(networks) > 0 {
- // This is inconsistent for >1 network, but it's probably the
+ // This is inconsistent for >1 ctrNetwork, but it's probably the
// best we can do.
networkKey = networks[0]
} else {
networkKey = r.netPlugin.GetDefaultNetworkName()
}
- network := ocicni.PodNetwork{
+ ctrNetwork := ocicni.PodNetwork{
Name: name,
Namespace: name, // TODO is there something else we should put here? We don't know about Kube namespaces
ID: id,
@@ -55,9 +56,12 @@ func (r *Runtime) getPodNetwork(id, name, nsPath string, networks []string, port
// If we have extra networks, add them
if len(networks) > 0 {
- network.Networks = make([]ocicni.NetAttachment, len(networks))
+ ctrNetwork.Networks = make([]ocicni.NetAttachment, len(networks))
for i, netName := range networks {
- network.Networks[i].Name = netName
+ ctrNetwork.Networks[i].Name = netName
+ if eth, exists := netDescriptions.getInterfaceByName(netName); exists {
+ ctrNetwork.Networks[i].Ifname = eth
+ }
}
}
@@ -66,8 +70,8 @@ func (r *Runtime) getPodNetwork(id, name, nsPath string, networks []string, port
// it's just the default.
if len(networks) == 0 {
// If len(networks) == 0 this is guaranteed to be the
- // default network.
- network.Networks = []ocicni.NetAttachment{{Name: networkKey}}
+ // default ctrNetwork.
+ ctrNetwork.Networks = []ocicni.NetAttachment{{Name: networkKey}}
}
var rt ocicni.RuntimeConfig = ocicni.RuntimeConfig{PortMappings: ports}
if staticIP != nil {
@@ -76,12 +80,12 @@ func (r *Runtime) getPodNetwork(id, name, nsPath string, networks []string, port
if staticMAC != nil {
rt.MAC = staticMAC.String()
}
- network.RuntimeConfig = map[string]ocicni.RuntimeConfig{
+ ctrNetwork.RuntimeConfig = map[string]ocicni.RuntimeConfig{
networkKey: rt,
}
}
- return network
+ return ctrNetwork
}
// Create and configure a new network namespace for a container
@@ -110,7 +114,12 @@ func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) ([]*cnitypes.Re
if err != nil {
return nil, err
}
- podNetwork := r.getPodNetwork(ctr.ID(), podName, ctrNS.Path(), networks, ctr.config.PortMappings, requestedIP, requestedMAC)
+
+ // Update container map of interface descriptions
+ if err := ctr.setupNetworkDescriptions(networks); err != nil {
+ return nil, err
+ }
+ podNetwork := r.getPodNetwork(ctr.ID(), podName, ctrNS.Path(), networks, ctr.config.PortMappings, requestedIP, requestedMAC, ctr.state.NetInterfaceDescriptions)
aliases, err := ctr.runtime.state.GetAllNetworkAliases(ctr)
if err != nil {
return nil, err
@@ -760,7 +769,7 @@ func (r *Runtime) teardownNetNS(ctr *Container) error {
requestedMAC = ctr.config.StaticMAC
}
- podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), networks, ctr.config.PortMappings, requestedIP, requestedMAC)
+ podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), networks, ctr.config.PortMappings, requestedIP, requestedMAC, ContainerNetworkDescriptions{})
if err := r.netPlugin.TearDownPod(podNetwork); err != nil {
return errors.Wrapf(err, "error tearing down CNI namespace configuration for container %s", ctr.ID())
@@ -934,6 +943,29 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e
return settings, nil
}
+// setupNetworkDescriptions adds networks and eth values to the container's
+// network descriptions
+func (c *Container) setupNetworkDescriptions(networks []string) error {
+ // if the map is nil and we have networks
+ if c.state.NetInterfaceDescriptions == nil && len(networks) > 0 {
+ c.state.NetInterfaceDescriptions = make(ContainerNetworkDescriptions)
+ }
+ origLen := len(c.state.NetInterfaceDescriptions)
+ for _, n := range networks {
+ // if the network is not in the map, add it
+ if _, exists := c.state.NetInterfaceDescriptions[n]; !exists {
+ c.state.NetInterfaceDescriptions.add(n)
+ }
+ }
+ // if the map changed, we need to save the container state
+ if origLen != len(c.state.NetInterfaceDescriptions) {
+ if err := c.save(); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
// resultToBasicNetworkConfig produces an InspectBasicNetworkConfig from a CNI
// result
func resultToBasicNetworkConfig(result *cnitypes.Result) (define.InspectBasicNetworkConfig, error) {
@@ -984,19 +1016,14 @@ func (w *logrusDebugWriter) Write(p []byte) (int, error) {
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)
+// NetworkDisconnect removes a container from the network
+func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) error {
+ networks, err := c.networksByNameIndex()
if err != nil {
return err
}
- networks, err := ctr.networksByNameIndex()
- if err != nil {
- return err
- }
-
- exists, err := network.Exists(r.config, netName)
+ exists, err := network.Exists(c.runtime.config, netName)
if err != nil {
return err
}
@@ -1009,48 +1036,48 @@ func (r *Runtime) DisconnectContainerFromNetwork(nameOrID, netName string, force
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 {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+ if err := c.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 {
+ if c.state.State != define.ContainerStateRunning {
+ return errors.Wrapf(define.ErrCtrStateInvalid, "cannot disconnect container %s from networks as it is not running", nameOrID)
+ }
+ if c.state.NetNS == nil {
+ return errors.Wrapf(define.ErrNoNetwork, "unable to disconnect %s from %s", nameOrID, netName)
+ }
+ podConfig := c.runtime.getPodNetwork(c.ID(), c.Name(), c.state.NetNS.Path(), []string{netName}, c.config.PortMappings, nil, nil, c.state.NetInterfaceDescriptions)
+ if err := c.runtime.netPlugin.TearDownPod(podConfig); err != nil {
return err
}
- if err := r.state.NetworkDisconnect(ctr, netName); err != nil {
+ if err := c.runtime.state.NetworkDisconnect(c, 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]
+ networkStatus := c.state.NetworkStatus
+ // clip out the index of the network
+ tmpNetworkStatus := make([]*cnitypes.Result, len(networkStatus)-1)
+ for k, v := range networkStatus {
+ if index != k {
+ tmpNetworkStatus = append(tmpNetworkStatus, v)
+ }
}
- return nil
+ c.state.NetworkStatus = tmpNetworkStatus
+ c.newNetworkEvent(events.NetworkDisconnect, netName)
+ return c.save()
}
-// ConnectContainerToNetwork connects a container to a CNI network
-func (r *Runtime) ConnectContainerToNetwork(nameOrID, netName string, aliases []string) error {
- ctr, err := r.LookupContainer(nameOrID)
+// ConnnectNetwork connects a container to a given network
+func (c *Container) NetworkConnect(nameOrID, netName string, aliases []string) error {
+ networks, err := c.networksByNameIndex()
if err != nil {
return err
}
- networks, err := ctr.networksByNameIndex()
- if err != nil {
- return err
- }
-
- exists, err := network.Exists(r.config, netName)
+ exists, err := network.Exists(c.runtime.config, netName)
if err != nil {
return err
}
@@ -1058,25 +1085,34 @@ func (r *Runtime) ConnectContainerToNetwork(nameOrID, netName string, aliases []
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)
+ c.lock.Lock()
+ defer c.lock.Unlock()
+ if err := c.syncContainer(); err != nil {
+ return err
}
- ctr.lock.Lock()
- defer ctr.lock.Unlock()
- if err := ctr.syncContainer(); err != nil {
+ if c.state.State != define.ContainerStateRunning {
+ return errors.Wrapf(define.ErrCtrStateInvalid, "cannot connect container %s to networks as it is not running", nameOrID)
+ }
+ if c.state.NetNS == nil {
+ return errors.Wrapf(define.ErrNoNetwork, "unable to connect %s to %s", nameOrID, netName)
+ }
+ if err := c.runtime.state.NetworkConnect(c, netName, aliases); err != nil {
return err
}
- if err := r.state.NetworkConnect(ctr, netName, aliases); err != nil {
+ ctrNetworks, err := c.networks()
+ if err != nil {
return err
}
-
- podConfig := r.getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), []string{netName}, ctr.config.PortMappings, nil, nil)
+ // Update network descriptions
+ if err := c.setupNetworkDescriptions(ctrNetworks); err != nil {
+ return err
+ }
+ podConfig := c.runtime.getPodNetwork(c.ID(), c.Name(), c.state.NetNS.Path(), []string{netName}, c.config.PortMappings, nil, nil, c.state.NetInterfaceDescriptions)
podConfig.Aliases = make(map[string][]string, 1)
podConfig.Aliases[netName] = aliases
- results, err := r.netPlugin.SetUpPod(podConfig)
+ results, err := c.runtime.netPlugin.SetUpPod(podConfig)
if err != nil {
return err
}
@@ -1094,11 +1130,11 @@ func (r *Runtime) ConnectContainerToNetwork(nameOrID, netName string, aliases []
}
// update network status
- networkStatus := ctr.state.NetworkStatus
+ networkStatus := c.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...)
+ c.state.NetworkStatus = append(c.state.NetworkStatus, networkResults...)
} else {
// build a list of network names so we can sort and
// get the new name's index
@@ -1117,5 +1153,30 @@ func (r *Runtime) ConnectContainerToNetwork(nameOrID, netName string, aliases []
copy(networkStatus[index+1:], networkStatus[index:])
networkStatus[index] = networkResults[0]
}
- return nil
+ c.newNetworkEvent(events.NetworkConnect, netName)
+ return c.save()
+}
+
+// DisconnectContainerFromNetwork removes a container from its CNI network
+func (r *Runtime) DisconnectContainerFromNetwork(nameOrID, netName string, force bool) error {
+ if rootless.IsRootless() {
+ return errors.New("network connect is not enabled for rootless containers")
+ }
+ ctr, err := r.LookupContainer(nameOrID)
+ if err != nil {
+ return err
+ }
+ return ctr.NetworkDisconnect(nameOrID, netName, force)
+}
+
+// ConnectContainerToNetwork connects a container to a CNI network
+func (r *Runtime) ConnectContainerToNetwork(nameOrID, netName string, aliases []string) error {
+ if rootless.IsRootless() {
+ return errors.New("network disconnect is not enabled for rootless containers")
+ }
+ ctr, err := r.LookupContainer(nameOrID)
+ if err != nil {
+ return err
+ }
+ return ctr.NetworkConnect(nameOrID, netName, aliases)
}
diff --git a/pkg/api/handlers/compat/networks.go b/pkg/api/handlers/compat/networks.go
index 64ddebf9c..c74cdb840 100644
--- a/pkg/api/handlers/compat/networks.go
+++ b/pkg/api/handlers/compat/networks.go
@@ -289,25 +289,44 @@ func CreateNetwork(w http.ResponseWriter, r *http.Request) {
func RemoveNetwork(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
- config, err := runtime.GetConfig()
- if err != nil {
- utils.InternalServerError(w, err)
+ ic := abi.ContainerEngine{Libpod: runtime}
+
+ query := struct {
+ Force bool `schema:"force"`
+ }{
+ // This is where you can override the golang default value for one of fields
+ }
+
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
return
}
+
+ options := entities.NetworkRmOptions{
+ Force: query.Force,
+ }
+
name := utils.GetName(r)
- exists, err := network.Exists(config, name)
+ reports, err := ic.NetworkRm(r.Context(), []string{name}, options)
if err != nil {
- utils.InternalServerError(w, err)
+ utils.Error(w, "remove Network failed", http.StatusInternalServerError, err)
return
}
- if !exists {
- utils.Error(w, "network not found", http.StatusNotFound, define.ErrNoSuchNetwork)
+ if len(reports) == 0 {
+ utils.Error(w, "remove Network failed", http.StatusInternalServerError, errors.Errorf("internal error"))
return
}
- if err := network.RemoveNetwork(config, name); err != nil {
- utils.InternalServerError(w, err)
+ report := reports[0]
+ if report.Err != nil {
+ if errors.Cause(report.Err) == define.ErrNoSuchNetwork {
+ utils.Error(w, "network not found", http.StatusNotFound, define.ErrNoSuchNetwork)
+ return
+ }
+ utils.InternalServerError(w, report.Err)
return
}
+
utils.WriteResponse(w, http.StatusNoContent, "")
}
diff --git a/pkg/api/handlers/libpod/networks.go b/pkg/api/handlers/libpod/networks.go
index 78e525f1f..f1578f829 100644
--- a/pkg/api/handlers/libpod/networks.go
+++ b/pkg/api/handlers/libpod/networks.go
@@ -131,3 +131,29 @@ func InspectNetwork(w http.ResponseWriter, r *http.Request) {
}
utils.WriteResponse(w, http.StatusOK, reports)
}
+
+// Connect adds a container to a network
+func Connect(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ var netConnect entities.NetworkConnectOptions
+ if err := json.NewDecoder(r.Body).Decode(&netConnect); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
+ return
+ }
+ name := utils.GetName(r)
+ err := runtime.ConnectContainerToNetwork(netConnect.Container, name, netConnect.Aliases)
+ if err != nil {
+ if errors.Cause(err) == define.ErrNoSuchCtr {
+ utils.ContainerNotFound(w, netConnect.Container, err)
+ return
+ }
+ if errors.Cause(err) == define.ErrNoSuchNetwork {
+ utils.Error(w, "network not found", http.StatusNotFound, err)
+ return
+ }
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, "OK")
+}
diff --git a/pkg/api/server/register_networks.go b/pkg/api/server/register_networks.go
index 6222006e5..ea169cbdf 100644
--- a/pkg/api/server/register_networks.go
+++ b/pkg/api/server/register_networks.go
@@ -253,5 +253,59 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error {
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/networks/create"), s.APIHandler(libpod.CreateNetwork)).Methods(http.MethodPost)
+ // swagger:operation POST /libpod/networks/{name}/connect libpod libpodConnectNetwork
+ // ---
+ // tags:
+ // - networks
+ // summary: Connect container to network
+ // description: Connect a container to a network.
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: path
+ // name: name
+ // type: string
+ // required: true
+ // description: the name of the network
+ // - in: body
+ // name: create
+ // description: attributes for connecting a container to a network
+ // schema:
+ // $ref: "#/definitions/NetworkConnectRequest"
+ // responses:
+ // 200:
+ // description: OK
+ // 404:
+ // $ref: "#/responses/NoSuchNetwork"
+ // 500:
+ // $ref: "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/networks/{name}/connect"), s.APIHandler(libpod.Connect)).Methods(http.MethodPost)
+ // swagger:operation POST /libpod/networks/{name}/disconnect libpod libpodDisconnectNetwork
+ // ---
+ // tags:
+ // - networks
+ // summary: Disconnect container from network
+ // description: Disconnect a container from a network.
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: path
+ // name: name
+ // type: string
+ // required: true
+ // description: the name of the network
+ // - in: body
+ // name: create
+ // description: attributes for disconnecting a container from a network
+ // schema:
+ // $ref: "#/definitions/NetworkDisconnectRequest"
+ // responses:
+ // 200:
+ // description: OK
+ // 404:
+ // $ref: "#/responses/NoSuchNetwork"
+ // 500:
+ // $ref: "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/networks/{name}/disconnect"), s.APIHandler(compat.Disconnect)).Methods(http.MethodPost)
return nil
}
diff --git a/pkg/bindings/network/network.go b/pkg/bindings/network/network.go
index 151d15d3e..1d4be8a4c 100644
--- a/pkg/bindings/network/network.go
+++ b/pkg/bindings/network/network.go
@@ -88,3 +88,41 @@ func List(ctx context.Context, options entities.NetworkListOptions) ([]*entities
}
return netList, response.Process(&netList)
}
+
+// Disconnect removes a container from a given network
+func Disconnect(ctx context.Context, networkName string, options entities.NetworkDisconnectOptions) error {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return err
+ }
+ params := url.Values{}
+ body, err := jsoniter.MarshalToString(options)
+ if err != nil {
+ return err
+ }
+ stringReader := strings.NewReader(body)
+ response, err := conn.DoRequest(stringReader, http.MethodPost, "/networks/%s/disconnect", params, nil, networkName)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+// Connect adds a container to a network
+func Connect(ctx context.Context, networkName string, options entities.NetworkConnectOptions) error {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return err
+ }
+ params := url.Values{}
+ body, err := jsoniter.MarshalToString(options)
+ if err != nil {
+ return err
+ }
+ stringReader := strings.NewReader(body)
+ response, err := conn.DoRequest(stringReader, http.MethodPost, "/networks/%s/connect", params, nil, networkName)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go
index 8ab72dbd8..b051d3eec 100644
--- a/pkg/domain/entities/engine_container.go
+++ b/pkg/domain/entities/engine_container.go
@@ -50,7 +50,9 @@ type ContainerEngine interface {
SystemPrune(ctx context.Context, options SystemPruneOptions) (*SystemPruneReport, error)
HealthCheckRun(ctx context.Context, nameOrID string, options HealthCheckOptions) (*define.HealthCheckResults, error)
Info(ctx context.Context) (*define.Info, error)
+ NetworkConnect(ctx context.Context, networkname string, options NetworkConnectOptions) error
NetworkCreate(ctx context.Context, name string, options NetworkCreateOptions) (*NetworkCreateReport, error)
+ NetworkDisconnect(ctx context.Context, networkname string, options NetworkDisconnectOptions) error
NetworkInspect(ctx context.Context, namesOrIds []string, options InspectOptions) ([]NetworkInspectReport, []error, error)
NetworkList(ctx context.Context, options NetworkListOptions) ([]*NetworkListReport, error)
NetworkRm(ctx context.Context, namesOrIds []string, options NetworkRmOptions) ([]*NetworkRmReport, error)
diff --git a/pkg/domain/entities/network.go b/pkg/domain/entities/network.go
index 3cc970531..86c2e1bcd 100644
--- a/pkg/domain/entities/network.go
+++ b/pkg/domain/entities/network.go
@@ -49,3 +49,17 @@ type NetworkCreateOptions struct {
type NetworkCreateReport struct {
Filename string
}
+
+// NetworkDisconnectOptions describes options for disconnecting
+// containers from networks
+type NetworkDisconnectOptions struct {
+ Container string
+ Force bool
+}
+
+// NetworkConnectOptions describes options for connecting
+// a container to a network
+type NetworkConnectOptions struct {
+ Aliases []string
+ Container string
+}
diff --git a/pkg/domain/infra/abi/network.go b/pkg/domain/infra/abi/network.go
index 06941f8d0..c52584565 100644
--- a/pkg/domain/infra/abi/network.go
+++ b/pkg/domain/infra/abi/network.go
@@ -96,7 +96,7 @@ func (ic *ContainerEngine) NetworkRm(ctx context.Context, namesOrIds []string, o
if err := ic.Libpod.RemovePod(ctx, pod, true, true); err != nil {
return reports, err
}
- } else if err := ic.Libpod.RemoveContainer(ctx, c, true, true); err != nil {
+ } else if err := ic.Libpod.RemoveContainer(ctx, c, true, true); err != nil && errors.Cause(err) != define.ErrNoSuchCtr {
return reports, err
}
}
@@ -138,3 +138,12 @@ func ifPassesFilterTest(netconf *libcni.NetworkConfigList, filter []string) bool
}
return result
}
+
+// NetworkDisconnect removes a container from a given network
+func (ic *ContainerEngine) NetworkDisconnect(ctx context.Context, networkname string, options entities.NetworkDisconnectOptions) error {
+ return ic.Libpod.DisconnectContainerFromNetwork(options.Container, networkname, options.Force)
+}
+
+func (ic *ContainerEngine) NetworkConnect(ctx context.Context, networkname string, options entities.NetworkConnectOptions) error {
+ return ic.Libpod.ConnectContainerToNetwork(options.Container, networkname, options.Aliases)
+}
diff --git a/pkg/domain/infra/tunnel/network.go b/pkg/domain/infra/tunnel/network.go
index 15527e02c..10ae03045 100644
--- a/pkg/domain/infra/tunnel/network.go
+++ b/pkg/domain/infra/tunnel/network.go
@@ -55,3 +55,13 @@ func (ic *ContainerEngine) NetworkRm(ctx context.Context, namesOrIds []string, o
func (ic *ContainerEngine) NetworkCreate(ctx context.Context, name string, options entities.NetworkCreateOptions) (*entities.NetworkCreateReport, error) {
return network.Create(ic.ClientCxt, options, &name)
}
+
+// NetworkDisconnect removes a container from a given network
+func (ic *ContainerEngine) NetworkDisconnect(ctx context.Context, networkname string, options entities.NetworkDisconnectOptions) error {
+ return network.Disconnect(ic.ClientCxt, networkname, options)
+}
+
+// NetworkConnect removes a container from a given network
+func (ic *ContainerEngine) NetworkConnect(ctx context.Context, networkname string, options entities.NetworkConnectOptions) error {
+ return network.Connect(ic.ClientCxt, networkname, options)
+}
diff --git a/test/e2e/mount_test.go b/test/e2e/mount_test.go
index e710ceda1..c9274553b 100644
--- a/test/e2e/mount_test.go
+++ b/test/e2e/mount_test.go
@@ -1,5 +1,3 @@
-// +build !remote
-
package integration
import (
@@ -18,6 +16,7 @@ var _ = Describe("Podman mount", func() {
)
BeforeEach(func() {
+ SkipIfRemote("Podman mount not supported for remote connections")
SkipIfRootless("Podman mount requires podman unshare first to work")
tempdir, err = CreateTempDirInTempDir()
if err != nil {
diff --git a/test/e2e/network_test.go b/test/e2e/network_test.go
index b010010f0..c6593df34 100644
--- a/test/e2e/network_test.go
+++ b/test/e2e/network_test.go
@@ -341,4 +341,157 @@ var _ = Describe("Podman network", func() {
c3.WaitWithDefaultTimeout()
Expect(c3.ExitCode()).To(BeZero())
})
+
+ It("bad network name in disconnect should result in error", func() {
+ SkipIfRootless("network connect and disconnect are only rootfull")
+ dis := podmanTest.Podman([]string{"network", "disconnect", "foobar", "test"})
+ dis.WaitWithDefaultTimeout()
+ Expect(dis.ExitCode()).ToNot(BeZero())
+
+ })
+
+ It("bad container name in network disconnect should result in error", func() {
+ SkipIfRootless("network connect and disconnect are only rootfull")
+ netName := "aliasTest" + stringid.GenerateNonCryptoID()
+ session := podmanTest.Podman([]string{"network", "create", netName})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(BeZero())
+ defer podmanTest.removeCNINetwork(netName)
+
+ dis := podmanTest.Podman([]string{"network", "disconnect", netName, "foobar"})
+ dis.WaitWithDefaultTimeout()
+ Expect(dis.ExitCode()).ToNot(BeZero())
+
+ })
+
+ It("podman network disconnect with invalid container state should result in error", func() {
+ SkipIfRootless("network connect and disconnect are only rootfull")
+ netName := "aliasTest" + stringid.GenerateNonCryptoID()
+ session := podmanTest.Podman([]string{"network", "create", netName})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(BeZero())
+ defer podmanTest.removeCNINetwork(netName)
+
+ ctr := podmanTest.Podman([]string{"create", "--name", "test", "--network", netName, ALPINE, "top"})
+ ctr.WaitWithDefaultTimeout()
+ Expect(ctr.ExitCode()).To(BeZero())
+
+ dis := podmanTest.Podman([]string{"network", "disconnect", netName, "test"})
+ dis.WaitWithDefaultTimeout()
+ Expect(dis.ExitCode()).ToNot(BeZero())
+ })
+
+ It("podman network disconnect", func() {
+ SkipIfRootless("network connect and disconnect are only rootfull")
+ netName := "aliasTest" + stringid.GenerateNonCryptoID()
+ session := podmanTest.Podman([]string{"network", "create", netName})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(BeZero())
+ defer podmanTest.removeCNINetwork(netName)
+
+ ctr := podmanTest.Podman([]string{"run", "-dt", "--name", "test", "--network", netName, ALPINE, "top"})
+ ctr.WaitWithDefaultTimeout()
+ Expect(ctr.ExitCode()).To(BeZero())
+
+ exec := podmanTest.Podman([]string{"exec", "-it", "test", "ip", "addr", "show", "eth0"})
+ exec.WaitWithDefaultTimeout()
+ Expect(exec.ExitCode()).To(BeZero())
+
+ dis := podmanTest.Podman([]string{"network", "disconnect", netName, "test"})
+ dis.WaitWithDefaultTimeout()
+ Expect(dis.ExitCode()).To(BeZero())
+
+ exec = podmanTest.Podman([]string{"exec", "-it", "test", "ip", "addr", "show", "eth0"})
+ exec.WaitWithDefaultTimeout()
+ Expect(exec.ExitCode()).ToNot(BeZero())
+ })
+
+ It("bad network name in connect should result in error", func() {
+ SkipIfRootless("network connect and disconnect are only rootfull")
+ dis := podmanTest.Podman([]string{"network", "connect", "foobar", "test"})
+ dis.WaitWithDefaultTimeout()
+ Expect(dis.ExitCode()).ToNot(BeZero())
+
+ })
+
+ It("bad container name in network connect should result in error", func() {
+ SkipIfRootless("network connect and disconnect are only rootfull")
+ netName := "aliasTest" + stringid.GenerateNonCryptoID()
+ session := podmanTest.Podman([]string{"network", "create", netName})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(BeZero())
+ defer podmanTest.removeCNINetwork(netName)
+
+ dis := podmanTest.Podman([]string{"network", "connect", netName, "foobar"})
+ dis.WaitWithDefaultTimeout()
+ Expect(dis.ExitCode()).ToNot(BeZero())
+
+ })
+
+ It("podman connect on a container that already is connected to the network should error", func() {
+ SkipIfRootless("network connect and disconnect are only rootfull")
+ netName := "aliasTest" + stringid.GenerateNonCryptoID()
+ session := podmanTest.Podman([]string{"network", "create", netName})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(BeZero())
+ defer podmanTest.removeCNINetwork(netName)
+
+ ctr := podmanTest.Podman([]string{"create", "--name", "test", "--network", netName, ALPINE, "top"})
+ ctr.WaitWithDefaultTimeout()
+ Expect(ctr.ExitCode()).To(BeZero())
+
+ con := podmanTest.Podman([]string{"network", "connect", netName, "test"})
+ con.WaitWithDefaultTimeout()
+ Expect(con.ExitCode()).ToNot(BeZero())
+ })
+
+ It("podman network connect with invalid container state should result in error", func() {
+ SkipIfRootless("network connect and disconnect are only rootfull")
+ netName := "aliasTest" + stringid.GenerateNonCryptoID()
+ session := podmanTest.Podman([]string{"network", "create", netName})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(BeZero())
+ defer podmanTest.removeCNINetwork(netName)
+
+ ctr := podmanTest.Podman([]string{"create", "--name", "test", "--network", netName, ALPINE, "top"})
+ ctr.WaitWithDefaultTimeout()
+ Expect(ctr.ExitCode()).To(BeZero())
+
+ dis := podmanTest.Podman([]string{"network", "connect", netName, "test"})
+ dis.WaitWithDefaultTimeout()
+ Expect(dis.ExitCode()).ToNot(BeZero())
+ })
+
+ It("podman network connect", func() {
+ SkipIfRemote("This requires a pending PR to be merged before it will work")
+ SkipIfRootless("network connect and disconnect are only rootfull")
+ netName := "aliasTest" + stringid.GenerateNonCryptoID()
+ session := podmanTest.Podman([]string{"network", "create", netName})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(BeZero())
+ defer podmanTest.removeCNINetwork(netName)
+
+ ctr := podmanTest.Podman([]string{"run", "-dt", "--name", "test", "--network", netName, ALPINE, "top"})
+ ctr.WaitWithDefaultTimeout()
+ Expect(ctr.ExitCode()).To(BeZero())
+
+ exec := podmanTest.Podman([]string{"exec", "-it", "test", "ip", "addr", "show", "eth0"})
+ exec.WaitWithDefaultTimeout()
+ Expect(exec.ExitCode()).To(BeZero())
+
+ // Create a second network
+ newNetName := "aliasTest" + stringid.GenerateNonCryptoID()
+ session = podmanTest.Podman([]string{"network", "create", newNetName})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(BeZero())
+ defer podmanTest.removeCNINetwork(newNetName)
+
+ connect := podmanTest.Podman([]string{"network", "connect", newNetName, "test"})
+ connect.WaitWithDefaultTimeout()
+ Expect(connect.ExitCode()).To(BeZero())
+
+ exec = podmanTest.Podman([]string{"exec", "-it", "test", "ip", "addr", "show", "eth1"})
+ exec.WaitWithDefaultTimeout()
+ Expect(exec.ExitCode()).To(BeZero())
+ })
})
diff --git a/test/e2e/push_test.go b/test/e2e/push_test.go
index 9074e19b8..922995060 100644
--- a/test/e2e/push_test.go
+++ b/test/e2e/push_test.go
@@ -1,5 +1,3 @@
-// +build !remote
-
package integration
import (
@@ -39,6 +37,7 @@ var _ = Describe("Podman push", func() {
})
It("podman push to containers/storage", func() {
+ SkipIfRemote("Remote push does not support containers-storage transport")
session := podmanTest.Podman([]string{"push", ALPINE, "containers-storage:busybox:test"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
@@ -49,6 +48,7 @@ var _ = Describe("Podman push", func() {
})
It("podman push to dir", func() {
+ SkipIfRemote("Remote push does not support dir transport")
bbdir := filepath.Join(podmanTest.TempDir, "busybox")
session := podmanTest.Podman([]string{"push", "--remove-signatures", ALPINE,
fmt.Sprintf("dir:%s", bbdir)})
@@ -57,6 +57,7 @@ var _ = Describe("Podman push", func() {
})
It("podman push to local registry", func() {
+ SkipIfRemote("FIXME: This should work")
if podmanTest.Host.Arch == "ppc64le" {
Skip("No registry image for ppc64le")
}
@@ -87,6 +88,7 @@ var _ = Describe("Podman push", func() {
})
It("podman push to local registry with authorization", func() {
+ SkipIfRemote("FIXME: This does not seem to be returning an error")
SkipIfRootless("FIXME: Creating content in certs.d we use directories in homedir")
if podmanTest.Host.Arch == "ppc64le" {
Skip("No registry image for ppc64le")
@@ -163,6 +165,7 @@ var _ = Describe("Podman push", func() {
})
It("podman push to docker-archive", func() {
+ SkipIfRemote("Remote push does not support docker-archive transport")
tarfn := filepath.Join(podmanTest.TempDir, "alp.tar")
session := podmanTest.Podman([]string{"push", ALPINE,
fmt.Sprintf("docker-archive:%s:latest", tarfn)})
@@ -171,6 +174,7 @@ var _ = Describe("Podman push", func() {
})
It("podman push to docker daemon", func() {
+ SkipIfRemote("Remote push does not support docker-daemon transport")
setup := SystemExec("bash", []string{"-c", "systemctl status docker 2>&1"})
if setup.LineInOutputContains("Active: inactive") {
@@ -196,6 +200,7 @@ var _ = Describe("Podman push", func() {
})
It("podman push to oci-archive", func() {
+ SkipIfRemote("Remote push does not support oci-archive transport")
tarfn := filepath.Join(podmanTest.TempDir, "alp.tar")
session := podmanTest.Podman([]string{"push", ALPINE,
fmt.Sprintf("oci-archive:%s:latest", tarfn)})
@@ -204,6 +209,7 @@ var _ = Describe("Podman push", func() {
})
It("podman push to docker-archive no reference", func() {
+ SkipIfRemote("Remote push does not support docker-archive transport")
tarfn := filepath.Join(podmanTest.TempDir, "alp.tar")
session := podmanTest.Podman([]string{"push", ALPINE,
fmt.Sprintf("docker-archive:%s", tarfn)})
@@ -212,6 +218,7 @@ var _ = Describe("Podman push", func() {
})
It("podman push to oci-archive no reference", func() {
+ SkipIfRemote("Remote push does not support oci-archive transport")
ociarc := filepath.Join(podmanTest.TempDir, "alp-oci")
session := podmanTest.Podman([]string{"push", ALPINE,
fmt.Sprintf("oci-archive:%s", ociarc)})
diff --git a/test/e2e/run_cgroup_parent_test.go b/test/e2e/run_cgroup_parent_test.go
index 5765d5ef6..35628d44b 100644
--- a/test/e2e/run_cgroup_parent_test.go
+++ b/test/e2e/run_cgroup_parent_test.go
@@ -1,5 +1,3 @@
-// +build !remote
-
package integration
import (
diff --git a/test/e2e/run_signal_test.go b/test/e2e/run_signal_test.go
index 2350fe1e5..58b8d04e5 100644
--- a/test/e2e/run_signal_test.go
+++ b/test/e2e/run_signal_test.go
@@ -1,5 +1,3 @@
-// +build !remote
-
package integration
import (
@@ -46,6 +44,7 @@ var _ = Describe("Podman run with --sig-proxy", func() {
})
Specify("signals are forwarded to container using sig-proxy", func() {
+ SkipIfRemote("FIXME: This looks like it is supposed to work in remote")
if podmanTest.Host.Arch == "ppc64le" {
Skip("Doesn't work on ppc64le")
}
@@ -111,6 +110,7 @@ var _ = Describe("Podman run with --sig-proxy", func() {
})
Specify("signals are not forwarded to container with sig-proxy false", func() {
+ SkipIfRemote("FIXME: This looks like it is supposed to work in remote")
signal := syscall.SIGFPE
if rootless.IsRootless() {
podmanTest.RestoreArtifact(fedoraMinimal)
diff --git a/test/e2e/trust_test.go b/test/e2e/trust_test.go
index 987023e4c..19e576490 100644
--- a/test/e2e/trust_test.go
+++ b/test/e2e/trust_test.go
@@ -1,5 +1,3 @@
-// +build !remote
-
package integration
import (
@@ -21,6 +19,7 @@ var _ = Describe("Podman trust", func() {
)
BeforeEach(func() {
+ SkipIfRemote("podman-remote does not support image trust")
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)