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/common/completion.go269
-rw-r--r--cmd/podman/containers/ps.go3
-rw-r--r--cmd/podman/networks/connect.go47
-rw-r--r--cmd/podman/networks/disconnect.go45
-rw-r--r--cmd/podman/pods/ps.go3
-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
-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/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.go9
-rw-r--r--pkg/domain/infra/tunnel/network.go10
-rw-r--r--test/e2e/network_test.go153
29 files changed, 1000 insertions, 184 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/common/completion.go b/cmd/podman/common/completion.go
index 00123f9e6..db4d3d0d3 100644
--- a/cmd/podman/common/completion.go
+++ b/cmd/podman/common/completion.go
@@ -23,90 +23,75 @@ var (
LogLevels = []string{"debug", "info", "warn", "error", "fatal", "panic"}
)
-func getContainers(toComplete string, statuses ...string) ([]string, cobra.ShellCompDirective) {
+type completeType int
+
+const (
+ // complete names and IDs after two chars
+ completeDefault completeType = iota
+ // only complete IDs
+ completeIDs
+ // only complete Names
+ completeNames
+)
+
+type keyValueCompletion map[string]func(s string) ([]string, cobra.ShellCompDirective)
+
+func getContainers(toComplete string, cType completeType, statuses ...string) ([]string, cobra.ShellCompDirective) {
suggestions := []string{}
listOpts := entities.ContainerListOptions{
Filters: make(map[string][]string),
}
listOpts.All = true
listOpts.Pod = true
+ if len(statuses) > 0 {
+ listOpts.Filters["status"] = statuses
+ }
- // TODO: The api doesn't handle several different statuses correct see:
- // https://github.com/containers/podman/issues/8344
- // Instead of looping over the statuses we should be able to set
- // listOpts.Filters["status"] = statuses
-
- var containers []entities.ListContainer
- var err error
- if len(statuses) == 0 {
- containers, err = registry.ContainerEngine().ContainerList(registry.GetContext(), listOpts)
- if err != nil {
- cobra.CompErrorln(err.Error())
- return nil, cobra.ShellCompDirectiveError
- }
- } else {
- for _, s := range statuses {
- listOpts.Filters["status"] = []string{s}
- res, err := registry.ContainerEngine().ContainerList(registry.GetContext(), listOpts)
- if err != nil {
- cobra.CompErrorln(err.Error())
- return nil, cobra.ShellCompDirectiveError
- }
- containers = append(containers, res...)
- }
+ containers, err := registry.ContainerEngine().ContainerList(registry.GetContext(), listOpts)
+ if err != nil {
+ cobra.CompErrorln(err.Error())
+ return nil, cobra.ShellCompDirectiveError
}
for _, c := range containers {
- // include ids in suggestions if more then 2 chars are typed
- if len(toComplete) > 1 && strings.HasPrefix(c.ID, toComplete) {
+ // include ids in suggestions if cType == completeIDs or
+ // more then 2 chars are typed and cType == completeDefault
+ if ((len(toComplete) > 1 && cType == completeDefault) ||
+ cType == completeIDs) && strings.HasPrefix(c.ID, toComplete) {
suggestions = append(suggestions, c.ID[0:12]+"\t"+c.PodName)
}
// include name in suggestions
- if strings.HasPrefix(c.Names[0], toComplete) {
+ if cType != completeIDs && strings.HasPrefix(c.Names[0], toComplete) {
suggestions = append(suggestions, c.Names[0]+"\t"+c.PodName)
}
}
return suggestions, cobra.ShellCompDirectiveNoFileComp
}
-func getPods(toComplete string, statuses ...string) ([]string, cobra.ShellCompDirective) {
+func getPods(toComplete string, cType completeType, statuses ...string) ([]string, cobra.ShellCompDirective) {
suggestions := []string{}
listOpts := entities.PodPSOptions{
Filters: make(map[string][]string),
}
+ if len(statuses) > 0 {
+ listOpts.Filters["status"] = statuses
+ }
- // TODO: The api doesn't handle several different statuses correct see:
- // https://github.com/containers/podman/issues/8344
- // Instead of looping over the statuses we should be able to set
- // listOpts.Filters["status"] = statuses
-
- var pods []*entities.ListPodsReport
- var err error
- if len(statuses) == 0 {
- pods, err = registry.ContainerEngine().PodPs(registry.GetContext(), listOpts)
- if err != nil {
- cobra.CompErrorln(err.Error())
- return nil, cobra.ShellCompDirectiveError
- }
- } else {
- for _, s := range statuses {
- listOpts.Filters["status"] = []string{s}
- res, err := registry.ContainerEngine().PodPs(registry.GetContext(), listOpts)
- if err != nil {
- cobra.CompErrorln(err.Error())
- return nil, cobra.ShellCompDirectiveError
- }
- pods = append(pods, res...)
- }
+ pods, err := registry.ContainerEngine().PodPs(registry.GetContext(), listOpts)
+ if err != nil {
+ cobra.CompErrorln(err.Error())
+ return nil, cobra.ShellCompDirectiveError
}
for _, pod := range pods {
- // include ids in suggestions if more then 2 chars are typed
- if len(toComplete) > 1 && strings.HasPrefix(pod.Id, toComplete) {
+ // include ids in suggestions if cType == completeIDs or
+ // more then 2 chars are typed and cType == completeDefault
+ if ((len(toComplete) > 1 && cType == completeDefault) ||
+ cType == completeIDs) && strings.HasPrefix(pod.Id, toComplete) {
suggestions = append(suggestions, pod.Id[0:12])
}
// include name in suggestions
- if strings.HasPrefix(pod.Name, toComplete) {
+ if cType != completeIDs && strings.HasPrefix(pod.Name, toComplete) {
suggestions = append(suggestions, pod.Name)
}
}
@@ -188,9 +173,7 @@ func getRegistries() ([]string, cobra.ShellCompDirective) {
func getNetworks(toComplete string) ([]string, cobra.ShellCompDirective) {
suggestions := []string{}
- networkListOptions := entities.NetworkListOptions{
- Filter: "name=" + toComplete,
- }
+ networkListOptions := entities.NetworkListOptions{}
networks, err := registry.ContainerEngine().NetworkList(registry.Context(), networkListOptions)
if err != nil {
@@ -198,8 +181,10 @@ func getNetworks(toComplete string) ([]string, cobra.ShellCompDirective) {
return nil, cobra.ShellCompDirectiveError
}
- for _, network := range networks {
- suggestions = append(suggestions, network.Name)
+ for _, n := range networks {
+ if strings.HasPrefix(n.Name, toComplete) {
+ suggestions = append(suggestions, n.Name)
+ }
}
return suggestions, cobra.ShellCompDirectiveNoFileComp
}
@@ -244,6 +229,36 @@ func validCurrentCmdLine(cmd *cobra.Command, args []string, toComplete string) b
return true
}
+func prefixSlice(pre string, slice []string) []string {
+ for i := range slice {
+ slice[i] = pre + slice[i]
+ }
+ return slice
+}
+
+func completeKeyValues(toComplete string, k keyValueCompletion) ([]string, cobra.ShellCompDirective) {
+ suggestions := make([]string, 0, len(k))
+ directive := cobra.ShellCompDirectiveNoFileComp
+ for key, getComps := range k {
+ if strings.HasPrefix(toComplete, key) {
+ if getComps != nil {
+ suggestions, dir := getComps(toComplete[len(key):])
+ return prefixSlice(key, suggestions), dir
+ }
+ return nil, cobra.ShellCompDirectiveNoFileComp
+ }
+ if strings.HasPrefix(key, toComplete) {
+ suggestions = append(suggestions, key)
+ latKey := key[len(key)-1:]
+ if latKey == "=" || latKey == ":" {
+ // make sure we don't add a space after ':' or '='
+ directive = cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp
+ }
+ }
+ }
+ return suggestions, directive
+}
+
/* Autocomplete Functions for cobra ValidArgsFunction */
// AutocompleteContainers - Autocomplete all container names.
@@ -251,7 +266,7 @@ func AutocompleteContainers(cmd *cobra.Command, args []string, toComplete string
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
- return getContainers(toComplete)
+ return getContainers(toComplete, completeDefault)
}
// AutocompleteContainersCreated - Autocomplete only created container names.
@@ -259,7 +274,7 @@ func AutocompleteContainersCreated(cmd *cobra.Command, args []string, toComplete
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
- return getContainers(toComplete, "created")
+ return getContainers(toComplete, completeDefault, "created")
}
// AutocompleteContainersExited - Autocomplete only exited container names.
@@ -267,7 +282,7 @@ func AutocompleteContainersExited(cmd *cobra.Command, args []string, toComplete
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
- return getContainers(toComplete, "exited")
+ return getContainers(toComplete, completeDefault, "exited")
}
// AutocompleteContainersPaused - Autocomplete only paused container names.
@@ -275,7 +290,7 @@ func AutocompleteContainersPaused(cmd *cobra.Command, args []string, toComplete
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
- return getContainers(toComplete, "paused")
+ return getContainers(toComplete, completeDefault, "paused")
}
// AutocompleteContainersRunning - Autocomplete only running container names.
@@ -283,7 +298,7 @@ func AutocompleteContainersRunning(cmd *cobra.Command, args []string, toComplete
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
- return getContainers(toComplete, "running")
+ return getContainers(toComplete, completeDefault, "running")
}
// AutocompleteContainersStartable - Autocomplete only created and exited container names.
@@ -291,7 +306,7 @@ func AutocompleteContainersStartable(cmd *cobra.Command, args []string, toComple
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
- return getContainers(toComplete, "created", "exited")
+ return getContainers(toComplete, completeDefault, "created", "exited")
}
// AutocompletePods - Autocomplete all pod names.
@@ -299,7 +314,7 @@ func AutocompletePods(cmd *cobra.Command, args []string, toComplete string) ([]s
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
- return getPods(toComplete)
+ return getPods(toComplete, completeDefault)
}
// AutocompletePodsRunning - Autocomplete only running pod names.
@@ -308,7 +323,7 @@ func AutocompletePodsRunning(cmd *cobra.Command, args []string, toComplete strin
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
- return getPods(toComplete, "running", "degraded")
+ return getPods(toComplete, completeDefault, "running", "degraded")
}
// AutocompleteContainersAndPods - Autocomplete container names and pod names.
@@ -316,8 +331,8 @@ func AutocompleteContainersAndPods(cmd *cobra.Command, args []string, toComplete
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
- containers, _ := getContainers(toComplete)
- pods, _ := getPods(toComplete)
+ containers, _ := getContainers(toComplete, completeDefault)
+ pods, _ := getPods(toComplete, completeDefault)
return append(containers, pods...), cobra.ShellCompDirectiveNoFileComp
}
@@ -326,7 +341,7 @@ func AutocompleteContainersAndImages(cmd *cobra.Command, args []string, toComple
if !validCurrentCmdLine(cmd, args, toComplete) {
return nil, cobra.ShellCompDirectiveNoFileComp
}
- containers, _ := getContainers(toComplete)
+ containers, _ := getContainers(toComplete, completeDefault)
images, _ := getImages(toComplete)
return append(containers, images...), cobra.ShellCompDirectiveNoFileComp
}
@@ -381,7 +396,7 @@ func AutocompleteCpCommand(cmd *cobra.Command, args []string, toComplete string)
return nil, cobra.ShellCompDirectiveNoFileComp
}
if len(args) < 2 {
- containers, _ := getContainers(toComplete)
+ containers, _ := getContainers(toComplete, completeDefault)
for _, container := range containers {
// TODO: Add path completion for inside the container if possible
if strings.HasPrefix(container, toComplete) {
@@ -448,40 +463,20 @@ func AutocompleteCreateAttach(cmd *cobra.Command, args []string, toComplete stri
// AutocompleteNamespace - Autocomplete namespace options.
// -> host,container:[name],ns:[path],private
func AutocompleteNamespace(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- namespacesOptions := []string{"host", "container:", "ns:", "private"}
-
- switch {
- case strings.HasPrefix(toComplete, "container:"):
- // Complete containers after colon
- containers, _ := getContainers(toComplete[10:]) //trim "container:"
-
- // add "container:" in front of the suggestions
- var suggestions []string
- for _, container := range containers {
- suggestions = append(suggestions, "container:"+container)
- }
-
- return suggestions, cobra.ShellCompDirectiveNoFileComp
-
- case strings.HasPrefix(toComplete, "ns:"):
- // Complete path after colon
- return nil, cobra.ShellCompDirectiveDefault
-
- case strings.HasPrefix(toComplete, "c") || strings.HasPrefix(toComplete, "n"):
- // don't insert space for container: and ns:
- return []string{"container:", "ns:"}, cobra.ShellCompDirectiveNoSpace
+ kv := keyValueCompletion{
+ "container:": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(s, completeDefault) },
+ "ns:": func(s string) ([]string, cobra.ShellCompDirective) { return nil, cobra.ShellCompDirectiveDefault },
+ "host": nil,
+ "private": nil,
}
- return namespacesOptions, cobra.ShellCompDirectiveNoFileComp
+ return completeKeyValues(toComplete, kv)
}
// AutocompleteUserNamespace - Autocomplete namespace options.
// -> same as AutocompleteNamespace with "auto", "keep-id" added
func AutocompleteUserNamespace(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
results, directive := AutocompleteNamespace(cmd, args, toComplete)
- if directive == cobra.ShellCompDirectiveNoFileComp {
- // add the auto and keep-id options
- results = append(results, "auto", "keep-id")
- }
+ results = append(results, "auto", "keep-id")
return results, directive
}
@@ -535,29 +530,18 @@ func AutocompleteRestartOption(cmd *cobra.Command, args []string, toComplete str
// AutocompleteSecurityOption - Autocomplete security options options.
func AutocompleteSecurityOption(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
- SecurityOptions := []string{"apparmor=", "no-new-privileges", "seccomp=", "label="}
- switch {
- case strings.HasPrefix(toComplete, "apparmor=u"):
- // add space after unconfined
- return []string{"apparmor=unconfined"}, cobra.ShellCompDirectiveNoFileComp
-
- case strings.HasPrefix(toComplete, "label=d"):
- // add space after disable
- return []string{"label=disable"}, cobra.ShellCompDirectiveNoFileComp
-
- case strings.HasPrefix(toComplete, "label="):
- return []string{"label=user:", "label=role:", "label=type:", "label=level:", "label=filetype:", "label=disable"},
- cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace
-
- case strings.HasPrefix(toComplete, "seccomp="):
- // complete files
- return nil, cobra.ShellCompDirectiveDefault
-
- case strings.HasPrefix(toComplete, "n"):
- // add space if no-new-privileges
- return []string{"no-new-privileges"}, cobra.ShellCompDirectiveNoFileComp
+ kv := keyValueCompletion{
+ "apparmor=": nil,
+ "no-new-privileges": nil,
+ "seccomp=": func(s string) ([]string, cobra.ShellCompDirective) { return nil, cobra.ShellCompDirectiveDefault },
+ "label=": func(s string) ([]string, cobra.ShellCompDirective) {
+ if strings.HasPrefix(s, "d") {
+ return []string{"disable"}, cobra.ShellCompDirectiveNoFileComp
+ }
+ return []string{"user:", "role:", "type:", "level:", "filetype:", "disable"}, cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp
+ },
}
- return SecurityOptions, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace
+ return completeKeyValues(toComplete, kv)
}
// AutocompleteStopSignal - Autocomplete stop signal options.
@@ -763,3 +747,48 @@ func AutocompleteSDNotify(cmd *cobra.Command, args []string, toComplete string)
types := []string{"container", "conmon", "ignore"}
return types, cobra.ShellCompDirectiveNoFileComp
}
+
+var containerStatuses = []string{"created", "running", "paused", "stopped", "exited", "unknown"}
+
+// AutocompletePsFilters - Autocomplete ps filter options.
+func AutocompletePsFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+ kv := keyValueCompletion{
+ "id=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(s, completeIDs) },
+ "name=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(s, completeNames) },
+ "status=": func(_ string) ([]string, cobra.ShellCompDirective) {
+ return containerStatuses, cobra.ShellCompDirectiveNoFileComp
+ },
+ "ancestor": func(s string) ([]string, cobra.ShellCompDirective) { return getImages(s) },
+ "before=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(s, completeDefault) },
+ "since=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(s, completeDefault) },
+ "volume=": func(s string) ([]string, cobra.ShellCompDirective) { return getVolumes(s) },
+ "health=": func(_ string) ([]string, cobra.ShellCompDirective) {
+ return []string{define.HealthCheckHealthy,
+ define.HealthCheckUnhealthy}, cobra.ShellCompDirectiveNoFileComp
+ },
+ "label=": nil,
+ "exited=": nil,
+ "until=": nil,
+ }
+ return completeKeyValues(toComplete, kv)
+}
+
+// AutocompletePodPsFilters - Autocomplete pod ps filter options.
+func AutocompletePodPsFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+ kv := keyValueCompletion{
+ "id=": func(s string) ([]string, cobra.ShellCompDirective) { return getPods(s, completeIDs) },
+ "name=": func(s string) ([]string, cobra.ShellCompDirective) { return getPods(s, completeNames) },
+ "status=": func(_ string) ([]string, cobra.ShellCompDirective) {
+ return []string{"stopped", "running",
+ "paused", "exited", "dead", "created", "degraded"}, cobra.ShellCompDirectiveNoFileComp
+ },
+ "ctr-ids=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(s, completeIDs) },
+ "ctr-names=": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(s, completeNames) },
+ "ctr-number=": nil,
+ "ctr-status=": func(_ string) ([]string, cobra.ShellCompDirective) {
+ return containerStatuses, cobra.ShellCompDirectiveNoFileComp
+ },
+ "label=": nil,
+ }
+ return completeKeyValues(toComplete, kv)
+}
diff --git a/cmd/podman/containers/ps.go b/cmd/podman/containers/ps.go
index 642feb5e0..a1a41ae08 100644
--- a/cmd/podman/containers/ps.go
+++ b/cmd/podman/containers/ps.go
@@ -64,8 +64,7 @@ func listFlagSet(cmd *cobra.Command) {
filterFlagName := "filter"
flags.StringSliceVarP(&filters, filterFlagName, "f", []string{}, "Filter output based on conditions given")
- //TODO add custom filter function
- _ = cmd.RegisterFlagCompletionFunc(filterFlagName, completion.AutocompleteNone)
+ _ = cmd.RegisterFlagCompletionFunc(filterFlagName, common.AutocompletePsFilters)
formatFlagName := "format"
flags.StringVar(&listOpts.Format, formatFlagName, "", "Pretty-print containers to JSON or using a Go template")
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/cmd/podman/pods/ps.go b/cmd/podman/pods/ps.go
index 51c2e92f0..99d324411 100644
--- a/cmd/podman/pods/ps.go
+++ b/cmd/podman/pods/ps.go
@@ -57,8 +57,7 @@ func init() {
filterFlagName := "filter"
flags.StringSliceVarP(&inputFilters, filterFlagName, "f", []string{}, "Filter output based on conditions given")
- //TODO complete filters
- _ = psCmd.RegisterFlagCompletionFunc(filterFlagName, completion.AutocompleteNone)
+ _ = psCmd.RegisterFlagCompletionFunc(filterFlagName, common.AutocompletePodPsFilters)
formatFlagName := "format"
flags.StringVar(&psInput.Format, formatFlagName, "", "Pretty-print pods to JSON or using a Go template")
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/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/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 b7f90a034..c52584565 100644
--- a/pkg/domain/infra/abi/network.go
+++ b/pkg/domain/infra/abi/network.go
@@ -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/network_test.go b/test/e2e/network_test.go
index 9f2fb4459..139a90ac7 100644
--- a/test/e2e/network_test.go
+++ b/test/e2e/network_test.go
@@ -346,4 +346,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())
+ })
})