diff options
-rw-r--r-- | .github/PULL_REQUEST_TEMPLATE.md | 7 | ||||
-rw-r--r-- | CONTRIBUTING.md | 4 | ||||
-rw-r--r-- | Makefile | 1 | ||||
-rw-r--r-- | OWNERS | 2 | ||||
-rw-r--r-- | docs/source/markdown/containers-mounts.conf.5.md | 2 | ||||
-rw-r--r-- | libpod/runtime_volume.go | 12 | ||||
-rw-r--r-- | nix/default.nix | 2 | ||||
-rw-r--r-- | pkg/api/server/idle/tracker.go | 5 | ||||
-rw-r--r-- | test/apiv2/30-volumes.at | 2 | ||||
-rw-r--r-- | test/apiv2/rest_api/__init__.py | 132 | ||||
-rw-r--r-- | test/apiv2/rest_api/test_rest_v2_0_0.py | 82 | ||||
-rw-r--r-- | test/apiv2/rest_api/v1_test_rest_v1_0_0.py (renamed from test/apiv2/rest_api/test_rest_v1_0_0.py) | 52 | ||||
-rw-r--r-- | test/e2e/volume_ls_test.go | 23 | ||||
-rw-r--r-- | troubleshooting.md | 6 |
14 files changed, 259 insertions, 73 deletions
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..568cf7240 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,7 @@ +<!-- +Thanks for sending a pull request! + +Please make sure you've read our contributing guidelines and how to submit a pull request (https://github.com/containers/podman/blob/master/CONTRIBUTING.md#submitting-pull-requests). + +In case you're only changing docs, make sure to prefix the pull-request title with "[CI:DOCS]". That will prevent functional tests from running and save time and energy. +--> diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 308c7b197..1d2c26750 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -157,6 +157,10 @@ when the PR is merged. PRs will be approved by an [approver][owners] listed in [`OWNERS`](OWNERS). +In case you're only changing docs, make sure to prefix the PR title with +"[CI:DOCS]". That will prevent functional tests from running and save time and +energy. + ### Describe your Changes in Commit Messages Describe your problem. Whether your patch is a one-line bug fix or 5000 lines @@ -357,6 +357,7 @@ remotesystem: .PHONY: localapiv2 localapiv2: env PODMAN=./bin/podman ./test/apiv2/test-apiv2 + env PODMAN=./bin/podman ${PYTHON} -m unittest discover -v ./test/apiv2/rest_api/ .PHONY: remoteapiv2 remoteapiv2: @@ -7,6 +7,7 @@ approvers: - rhatdan - TomSweeneyRedHat - vrothberg + - umohnani8 reviewers: - baude - edsantiago @@ -18,3 +19,4 @@ reviewers: - vrothberg - ashley-cui - QiWang19 + - umohnani8 diff --git a/docs/source/markdown/containers-mounts.conf.5.md b/docs/source/markdown/containers-mounts.conf.5.md index 130c1c523..74492c831 100644 --- a/docs/source/markdown/containers-mounts.conf.5.md +++ b/docs/source/markdown/containers-mounts.conf.5.md @@ -10,7 +10,7 @@ The mounts.conf file specifies volume mount directories that are automatically m The format of the mounts.conf is the volume format `/SRC:/DEST`, one mount per line. For example, a mounts.conf with the line `/usr/share/secrets:/run/secrets` would cause the contents of the `/usr/share/secrets` directory on the host to be mounted on the `/run/secrets` directory inside the container. Setting mountpoints allows containers to use the files of the host, for instance, to use the host's subscription to some enterprise Linux distribution. ## FILES -Some distributions may provide a `/usr/share/containers/mounts.conf` file to provide default mounts, but users can create a `/etc/containers/mounts.conf`, to specify their own special volumes to mount in the container. +Some distributions may provide a `/usr/share/containers/mounts.conf` file to provide default mounts, but users can create a `/etc/containers/mounts.conf`, to specify their own special volumes to mount in the container. When Podman runs in rootless mode, the file `$HOME/.config/containers/mounts.conf` will override the default if it exists. ## HISTORY Aug 2018, Originally compiled by Valentin Rothberg <vrothberg@suse.com> diff --git a/libpod/runtime_volume.go b/libpod/runtime_volume.go index e4e6d87e6..055a243c0 100644 --- a/libpod/runtime_volume.go +++ b/libpod/runtime_volume.go @@ -86,8 +86,8 @@ func (r *Runtime) HasVolume(name string) (bool, error) { // Volumes retrieves all volumes // Filters can be provided which will determine which volumes are included in the -// output. Multiple filters are handled by ANDing their output, so only volumes -// matching all filters are returned +// output. If multiple filters are used, a volume will be returned if +// any of the filters are matched func (r *Runtime) Volumes(filters ...VolumeFilter) ([]*Volume, error) { r.lock.RLock() defer r.lock.RUnlock() @@ -101,11 +101,15 @@ func (r *Runtime) Volumes(filters ...VolumeFilter) ([]*Volume, error) { return nil, err } + if len(filters) == 0 { + return vols, nil + } + volsFiltered := make([]*Volume, 0, len(vols)) for _, vol := range vols { - include := true + include := false for _, filter := range filters { - include = include && filter(vol) + include = include || filter(vol) } if include { diff --git a/nix/default.nix b/nix/default.nix index cc8786ce0..a1a8c5287 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -44,7 +44,7 @@ let export CFLAGS='-static' export LDFLAGS='-s -w -static-libgcc -static' export EXTRA_LDFLAGS='-s -w -linkmode external -extldflags "-static -lm"' - export BUILDTAGS='static netgo exclude_graphdriver_btrfs exclude_graphdriver_devicemapper seccomp apparmor selinux' + export BUILDTAGS='static netgo osusergo exclude_graphdriver_btrfs exclude_graphdriver_devicemapper seccomp apparmor selinux' ''; buildPhase = '' patchShebangs . diff --git a/pkg/api/server/idle/tracker.go b/pkg/api/server/idle/tracker.go index 50e41b7bf..687ebd7d4 100644 --- a/pkg/api/server/idle/tracker.go +++ b/pkg/api/server/idle/tracker.go @@ -41,11 +41,12 @@ func (t *Tracker) ConnState(conn net.Conn, state http.ConnState) { logrus.Debugf("IdleTracker %p:%v %dm+%dh/%dt connection(s)", conn, state, len(t.managed), t.hijacked, t.TotalConnections()) switch state { - case http.StateNew, http.StateActive: + case http.StateNew: + t.total++ + case http.StateActive: // stop the API timer when the server transitions any connection to an "active" state t.managed[conn] = struct{}{} t.timer.Stop() - t.total++ case http.StateHijacked: // hijacked connections should call Close() when finished. // Note: If a handler hijack's a connection and then doesn't Close() it, diff --git a/test/apiv2/30-volumes.at b/test/apiv2/30-volumes.at index 2c38954b6..aa167a97a 100644 --- a/test/apiv2/30-volumes.at +++ b/test/apiv2/30-volumes.at @@ -35,6 +35,8 @@ t GET libpod/volumes/json 200 \ .[0].CreatedAt~[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}.* # -G --data-urlencode 'filters={"name":["foo1"]}' t GET libpod/volumes/json?filters=%7B%22name%22%3A%5B%22foo1%22%5D%7D 200 length=1 .[0].Name=foo1 +# -G --data-urlencode 'filters={"name":["foo1","foo2"]}' +t GET libpod/volumes/json?filters=%7B%22name%22%3A%20%5B%22foo1%22%2C%20%22foo2%22%5D%7D 200 length=2 .[0].Name=foo1 .[1].Name=foo2 # -G --data-urlencode 'filters={"name":["notexist"]}' t GET libpod/volumes/json?filters=%7B%22name%22%3A%5B%22notexists%22%5D%7D 200 length=0 diff --git a/test/apiv2/rest_api/__init__.py b/test/apiv2/rest_api/__init__.py index e69de29bb..5f0777d58 100644 --- a/test/apiv2/rest_api/__init__.py +++ b/test/apiv2/rest_api/__init__.py @@ -0,0 +1,132 @@ +import configparser +import json +import os +import shutil +import subprocess +import sys +import tempfile + + +class Podman(object): + """ + Instances hold the configuration and setup for running podman commands + """ + + def __init__(self): + """Initialize a Podman instance with global options""" + binary = os.getenv("PODMAN", "bin/podman") + self.cmd = [binary, "--storage-driver=vfs"] + + cgroupfs = os.getenv("CGROUP_MANAGER", "cgroupfs") + self.cmd.append(f"--cgroup-manager={cgroupfs}") + + if os.getenv("DEBUG"): + self.cmd.append("--log-level=debug") + + self.anchor_directory = tempfile.mkdtemp(prefix="podman_restapi_") + self.cmd.append("--root=" + os.path.join(self.anchor_directory, "crio")) + self.cmd.append("--runroot=" + os.path.join(self.anchor_directory, "crio-run")) + + os.environ["REGISTRIES_CONFIG_PATH"] = os.path.join( + self.anchor_directory, "registry.conf" + ) + p = configparser.ConfigParser() + p.read_dict( + { + "registries.search": {"registries": "['docker.io']"}, + "registries.insecure": {"registries": "[]"}, + "registries.block": {"registries": "[]"}, + } + ) + with open(os.environ["REGISTRIES_CONFIG_PATH"], "w") as w: + p.write(w) + + os.environ["CNI_CONFIG_PATH"] = os.path.join( + self.anchor_directory, "cni", "net.d" + ) + os.makedirs(os.environ["CNI_CONFIG_PATH"], exist_ok=True) + self.cmd.append("--cni-config-dir=" + os.environ["CNI_CONFIG_PATH"]) + cni_cfg = os.path.join( + os.environ["CNI_CONFIG_PATH"], "87-podman-bridge.conflist" + ) + # json decoded and encoded to ensure legal json + buf = json.loads( + """ + { + "cniVersion": "0.3.0", + "name": "podman", + "plugins": [{ + "type": "bridge", + "bridge": "cni0", + "isGateway": true, + "ipMasq": true, + "ipam": { + "type": "host-local", + "subnet": "10.88.0.0/16", + "routes": [{ + "dst": "0.0.0.0/0" + }] + } + }, + { + "type": "portmap", + "capabilities": { + "portMappings": true + } + } + ] + } + """ + ) + with open(cni_cfg, "w") as w: + json.dump(buf, w) + + def open(self, command, *args, **kwargs): + """Podman initialized instance to run a given command + + :param self: Podman instance + :param command: podman sub-command to run + :param args: arguments and options for command + :param kwargs: See subprocess.Popen() for shell keyword + :return: subprocess.Popen() instance configured to run podman instance + """ + cmd = self.cmd.copy() + cmd.append(command) + cmd.extend(args) + + shell = kwargs.get("shell", False) + + return subprocess.Popen( + cmd, + shell=shell, + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + def run(self, command, *args, **kwargs): + """Podman initialized instance to run a given command + + :param self: Podman instance + :param command: podman sub-command to run + :param args: arguments and options for command + :param kwargs: See subprocess.Popen() for shell and check keywords + :return: subprocess.Popen() instance configured to run podman instance + """ + cmd = self.cmd.copy() + cmd.append(command) + cmd.extend(args) + + check = kwargs.get("check", False) + shell = kwargs.get("shell", False) + + return subprocess.run( + cmd, + shell=shell, + check=check, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + + def tear_down(self): + shutil.rmtree(self.anchor_directory, ignore_errors=True) diff --git a/test/apiv2/rest_api/test_rest_v2_0_0.py b/test/apiv2/rest_api/test_rest_v2_0_0.py index 3376f8402..5dfd1fc02 100644 --- a/test/apiv2/rest_api/test_rest_v2_0_0.py +++ b/test/apiv2/rest_api/test_rest_v2_0_0.py @@ -1,5 +1,4 @@ import json -import os import subprocess import sys import time @@ -9,27 +8,25 @@ from multiprocessing import Process import requests from dateutil.parser import parse +from test.apiv2.rest_api import Podman + PODMAN_URL = "http://localhost:8080" def _url(path): - return PODMAN_URL + "/v1.0.0/libpod" + path - - -def podman(): - binary = os.getenv("PODMAN_BINARY") - if binary is None: - binary = "bin/podman" - return binary + return PODMAN_URL + "/v2.0.0/libpod" + path def ctnr(path): - r = requests.get(_url("/containers/json?all=true")) try: + r = requests.get(_url("/containers/json?all=true")) ctnrs = json.loads(r.text) except Exception as e: - sys.stderr.write("Bad container response: {}/{}".format(r.text, e)) - raise e + msg = f"Bad container response: {e}" + if r is not None: + msg = msg + " " + r.text + sys.stderr.write(msg + "\n") + raise return path.format(ctnrs[0]["Id"]) @@ -44,50 +41,50 @@ def validateObjectFields(buffer): class TestApi(unittest.TestCase): - podman = None + podman = None # initialized podman configuration for tests + service = None # podman service instance def setUp(self): super().setUp() - if TestApi.podman.poll() is not None: - sys.stderr.write(f"podman service returned {TestApi.podman.returncode}\n") - sys.exit(2) - requests.get( - _url("/images/create?fromSrc=docker.io%2Falpine%3Alatest")) - # calling out to podman is easier than the API for running a container - subprocess.run([podman(), "run", "alpine", "/bin/ls"], - check=True, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL) + + try: + TestApi.podman.run("run", "alpine", "/bin/ls", check=True) + except subprocess.CalledProcessError as e: + if e.stdout: + sys.stdout.write("\nRun Stdout:\n" + e.stdout.decode("utf-8")) + if e.stderr: + sys.stderr.write("\nRun Stderr:\n" + e.stderr.decode("utf-8")) + raise @classmethod def setUpClass(cls): super().setUpClass() - TestApi.podman = subprocess.Popen( - [ - podman(), "system", "service", "tcp:localhost:8080", - "--log-level=debug", "--time=0" - ], - shell=False, - stdin=subprocess.DEVNULL, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, + TestApi.podman = Podman() + TestApi.service = TestApi.podman.open( + "system", "service", "tcp:localhost:8080", "--log-level=debug", "--time=0" ) + # give the service some time to be ready... time.sleep(2) + returncode = TestApi.service.poll() + if returncode is not None: + raise subprocess.CalledProcessError(returncode, "podman system service") + + r = requests.post(_url("/images/pull?reference=docker.io%2Falpine%3Alatest")) + if r.status_code != 200: + raise subprocess.CalledProcessError( + r.status_code, f"podman images pull docker.io/alpine:latest {r.text}" + ) + @classmethod def tearDownClass(cls): - TestApi.podman.terminate() - stdout, stderr = TestApi.podman.communicate(timeout=0.5) + TestApi.service.terminate() + stdout, stderr = TestApi.service.communicate(timeout=0.5) if stdout: - print("\nService Stdout:\n" + stdout.decode('utf-8')) + sys.stdout.write("\nService Stdout:\n" + stdout.decode("utf-8")) if stderr: - print("\nService Stderr:\n" + stderr.decode('utf-8')) - - if TestApi.podman.returncode > 0: - sys.stderr.write(f"podman exited with error code {TestApi.podman.returncode}\n") - sys.exit(2) - + sys.stderr.write("\nService Stderr:\n" + stderr.decode("utf-8")) return super().tearDownClass() def test_info(self): @@ -160,6 +157,7 @@ class TestApi(unittest.TestCase): self.assertIsNone(r.text) def test_attach_containers(self): + self.skipTest("FIXME: Test timeouts") r = requests.post(_url(ctnr("/containers/{}/attach")), timeout=5) self.assertIn(r.status_code, (101, 500), r.text) @@ -242,5 +240,5 @@ class TestApi(unittest.TestCase): self.assertEqual(r.status_code, 200, r.text) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/test/apiv2/rest_api/test_rest_v1_0_0.py b/test/apiv2/rest_api/v1_test_rest_v1_0_0.py index 2e574e015..acd6273ef 100644 --- a/test/apiv2/rest_api/test_rest_v1_0_0.py +++ b/test/apiv2/rest_api/v1_test_rest_v1_0_0.py @@ -43,16 +43,16 @@ class TestApi(unittest.TestCase): def setUp(self): super().setUp() if TestApi.podman.poll() is not None: - sys.stderr.write("podman service returned {}", - TestApi.podman.returncode) + sys.stderr.write("podman service returned {}", TestApi.podman.returncode) sys.exit(2) - requests.get( - _url("/images/create?fromSrc=docker.io%2Falpine%3Alatest")) + requests.get(_url("/images/create?fromSrc=docker.io%2Falpine%3Alatest")) # calling out to podman is easier than the API for running a container - subprocess.run([podman(), "run", "alpine", "/bin/ls"], - check=True, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL) + subprocess.run( + [podman(), "run", "alpine", "/bin/ls"], + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) @classmethod def setUpClass(cls): @@ -60,8 +60,12 @@ class TestApi(unittest.TestCase): TestApi.podman = subprocess.Popen( [ - podman(), "system", "service", "tcp:localhost:8080", - "--log-level=debug", "--time=0" + podman(), + "system", + "service", + "tcp:localhost:8080", + "--log-level=debug", + "--time=0", ], shell=False, stdin=subprocess.DEVNULL, @@ -75,13 +79,14 @@ class TestApi(unittest.TestCase): TestApi.podman.terminate() stdout, stderr = TestApi.podman.communicate(timeout=0.5) if stdout: - print("\nService Stdout:\n" + stdout.decode('utf-8')) + print("\nService Stdout:\n" + stdout.decode("utf-8")) if stderr: - print("\nService Stderr:\n" + stderr.decode('utf-8')) + print("\nService Stderr:\n" + stderr.decode("utf-8")) if TestApi.podman.returncode > 0: - sys.stderr.write("podman exited with error code {}\n".format( - TestApi.podman.returncode)) + sys.stderr.write( + "podman exited with error code {}\n".format(TestApi.podman.returncode) + ) sys.exit(2) return super().tearDownClass() @@ -222,13 +227,14 @@ class TestApi(unittest.TestCase): def validateObjectFields(self, buffer): - objs = json.loads(buffer) - if not isinstance(objs, dict): - for o in objs: - _ = o["Id"] - else: - _ = objs["Id"] - return objs - -if __name__ == '__main__': + objs = json.loads(buffer) + if not isinstance(objs, dict): + for o in objs: + _ = o["Id"] + else: + _ = objs["Id"] + return objs + + +if __name__ == "__main__": unittest.main() diff --git a/test/e2e/volume_ls_test.go b/test/e2e/volume_ls_test.go index 1cb6440aa..cda118bf1 100644 --- a/test/e2e/volume_ls_test.go +++ b/test/e2e/volume_ls_test.go @@ -110,4 +110,27 @@ var _ = Describe("Podman volume ls", func() { Expect(lsDangling.ExitCode()).To(Equal(0)) Expect(lsDangling.OutputToString()).To(ContainSubstring(volName1)) }) + It("podman ls volume with multiple --filter flag", func() { + session := podmanTest.Podman([]string{"volume", "create", "--label", "foo=bar", "myvol"}) + volName := session.OutputToString() + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"volume", "create", "--label", "foo2=bar2", "anothervol"}) + anotherVol := session.OutputToString() + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"volume", "create"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"volume", "ls", "--filter", "label=foo", "--filter", "label=foo2"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(len(session.OutputToStringArray())).To(Equal(3)) + Expect(session.OutputToStringArray()[1]).To(ContainSubstring(volName)) + Expect(session.OutputToStringArray()[2]).To(ContainSubstring(anotherVol)) + + }) }) diff --git a/troubleshooting.md b/troubleshooting.md index c42afb642..2e0abae21 100644 --- a/troubleshooting.md +++ b/troubleshooting.md @@ -680,3 +680,9 @@ file `/etc/systemd/system/user@.service.d/delegate.conf` with the contents: After logging out and loggin back in, you should have permission to set CPU limits. + +### 26) `exec container process '/bin/sh': Exec format error` (or another binary than `bin/sh`) + +This can happen when running a container from an image for another architecture than the one you are running on. + +For example, if a remote repository only has, and thus send you, a `linux/arm64` _OS/ARCH_ but you run on `linux/amd64` (as happened in https://github.com/openMF/community-app/issues/3323 due to https://github.com/timbru31/docker-ruby-node/issues/564). |