181 Commits

Author SHA1 Message Date
2681566580 Release 1.1.0 2024-04-19 12:37:30 +03:00
62ce087c6e Merge pull request #914 from p12tic/release-notes
Add release notes for v1.1.0
2024-04-17 20:26:52 +03:00
c97f003732 Add release notes for v1.1.0
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-04-17 20:23:48 +03:00
c88558b4ec Merge pull request #913 from p12tic/network-mac-address-vendor-prefix
Use x- prefix for x-podman.mac_address
2024-04-17 20:21:56 +03:00
131010bc9d Use x- prefix for x-podman.mac_address
Docker compose documents x- prefix as standard vendor prefix.

https://github.com/compose-spec/compose-spec/pull/18

Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-04-17 20:16:03 +03:00
1da2f85a90 Merge pull request #906 from camspiers/fixes/ulimit-build-args
Fix, use "service.build.ulimits" when it is present instead of "service.ulimits"
2024-04-17 19:48:55 +03:00
cdcedeb6b2 Use "service.build.ulimits" when present instead of "service.ulimits"
Signed-off-by: Cam Spiers <camspiers@gmail.com>
2024-04-17 19:45:34 +03:00
e893d06313 Merge pull request #867 from baszoetekouw/fix-networks
Fix multiple networks with separately specified ip and mac
2024-04-08 23:21:38 +03:00
9a5b43907f Merge pull request #905 from acotty/main
FIX issue https://github.com/containers/podman-compose/issues/704 - Windows fix sock not working.
2024-04-07 12:43:43 +03:00
6c09ce710e FIX issue https://github.com/containers/podman-compose/issues/704 - Windows fix sock not working. 2024-04-07 16:28:45 +10:00
953534a71a Support and document all podman-specific network_modes
Signed-off-by: Bas Zoetekouw <bas.zoetekouw@surf.nl>
2024-03-28 16:09:43 +02:00
6feff244db slirp4netns can be used without options
Signed-off-by: Bas Zoetekouw <bas.zoetekouw@surf.nl>
2024-03-28 16:09:43 +02:00
9fd4cf43c1 Add unit tests for network_mode
Signed-off-by: Bas Zoetekouw <bas.zoetekouw@surf.nl>
2024-03-28 16:09:43 +02:00
65849c95e2 add comment about per-network aliases
Signed-off-by: Bas Zoetekouw <bas.zoetekouw@surf.nl>
2024-03-28 16:09:43 +02:00
9baea704d7 use preferred format of podman command line options
Specifically:
  - use "--network=foo" instead of "--network foo"
  - specify "--network-alias" multiple times instead of concatenating values

Signed-off-by: Bas Zoetekouw <bas.zoetekouw@surf.nl>
2024-03-28 16:09:43 +02:00
bdff78dcba Ignore files generated by tests
Signed-off-by: Bas Zoetekouw <bas.zoetekouw@surf.nl>
2024-03-28 16:09:43 +02:00
45ca1f994f Support podman-specific per-network mac_address specifiation
Signed-off-by: Bas Zoetekouw <bas.zoetekouw@surf.nl>
2024-03-28 16:09:43 +02:00
91fbea3d89 Add unit tests for get_net_args()
Signed-off-by: Bas Zoetekouw <bas.zoetekouw@surf.nl>
2024-03-28 16:09:43 +02:00
2743d690d2 Fix support for multiple networks with explicitly specified ipv4/ipv6 addresses
Signed-off-by: Bas Zoetekouw <bas.zoetekouw@surf.nl>
2024-03-28 16:09:43 +02:00
dd34a90068 Add testcase for failing network config
Signed-off-by: Bas Zoetekouw <bas.zoetekouw@surf.nl>
2024-03-28 16:09:43 +02:00
6103df78fb Merge pull request #777 from wiehe/build-secrets
Pass file secrets to "podman build" via parameter "--secret"
2024-03-28 00:40:12 +02:00
07128f6b37 Merge pull request #779 from jbaptperez/lint-fixes-and-enhancements
Lint fixes and enhancements
2024-03-28 00:36:50 +02:00
81d81fb303 Formats CONTRIBUTING.md
Signed-off-by: Jean-Baptiste Perez <jbaptperez@gmail.com>
2024-03-17 19:31:47 +01:00
b263dc1a7d Fixes typos and enhances CONTRIBUTING.md
Signed-off-by: Jean-Baptiste Perez <jbaptperez@gmail.com>
2024-03-17 19:31:47 +01:00
078ee7b649 Updates Signed-off-by link in CONTRIBUTING.md
The initial link was related to GPG signature in commits (--gpg-sign or
-S git-commit option) as the required signature is actually a
"Signed-off-by" trailer at the end of the commit message (--signoff or
-s git-commit option).

Signed-off-by: Jean-Baptiste Perez <jbaptperez@gmail.com>
2024-03-17 19:31:47 +01:00
a6e3ae7b31 Fixes invalid name of test package in setup.py
Signed-off-by: Jean-Baptiste Perez <jbaptperez@gmail.com>
2024-03-17 19:31:47 +01:00
1e9cf1dff0 Pass file secrets to "podman build" via parameter "--secret"
to make them available for "RUN --mount=type=secret" statements inside the
Dockerfile.

Keep using --volume to pass file secrets to "podman run".

Signed-off-by: wiehe <28457227+wiehe@users.noreply.github.com>
2024-03-12 17:23:40 +01:00
d704622522 Merge pull request #890 from containers/dependabot/github_actions/actions/checkout-4
Bump actions/checkout from 3 to 4
2024-03-12 15:27:34 +02:00
bbfff78e25 Bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-11 21:44:23 +00:00
abb0cb1649 Merge pull request #888 from aripollak/fix-test-class-names
Rename test classes to match filenames
2024-03-10 00:21:20 +02:00
d53df307f8 Merge pull request #889 from aripollak/add-coverage-to-job-summary
Add coverage to Github Action summary
2024-03-10 00:20:50 +02:00
c351f99da5 Add coverage to Github Action summary
Signed-off-by: Ari Pollak <ajp@aripollak.com>
2024-03-09 17:10:14 -05:00
829cde0de9 Rename test classes to match filenames
Signed-off-by: Ari Pollak <ajp@aripollak.com>
2024-03-09 16:34:06 -05:00
f18c8092cc Merge pull request #887 from baszoetekouw/fix-oldpython
Fix support for older python versions
2024-03-09 23:29:32 +02:00
43059dc16f Merge pull request #783 from ChuJiani/devel
Fix #782: add support for `http-proxy` option of podman
2024-03-09 23:27:22 +02:00
da63048775 Don't spellcheck requirementfiles
As some python module names are not proper english words

Signed-off-by: Bas Zoetekouw <bas.zoetekouw@surf.nl>
2024-03-09 22:26:36 +01:00
7987a7185e Merge pull request #861 from aripollak/remove-implemented-from-missing
Remove implemented commands from Missing Commands
2024-03-09 23:26:36 +02:00
5e55df890c fix python < 3.9 compatibility
Signed-off-by: Bas Zoetekouw <bas.zoetekouw@surf.nl>
2024-03-09 22:26:36 +01:00
fde7995cb8 Fix python < 3.11 compatibility
Signed-off-by: Bas Zoetekouw <bas.zoetekouw@surf.nl>
2024-03-09 22:26:36 +01:00
c592596a5e Split out the different tests into separate CI steps
Signed-off-by: Bas Zoetekouw <bas.zoetekouw@surf.nl>
2024-03-09 22:26:36 +01:00
688ee9a104 Fix pylint issues
Signed-off-by: Bas Zoetekouw <bas.zoetekouw@surf.nl>
2024-03-09 22:26:35 +01:00
c3a152e68f Enable pylint, because it catches stuff that ruff misses
Signed-off-by: Bas Zoetekouw <bas.zoetekouw@surf.nl>
2024-03-09 22:26:35 +01:00
bd60bc9f21 Run tests on all supported python versions
Signed-off-by: Bas Zoetekouw <bas.zoetekouw@surf.nl>
2024-03-09 22:26:35 +01:00
9d8b0b8632 add editorconfig
Signed-off-by: Bas Zoetekouw <bas.zoetekouw@surf.nl>
2024-03-09 22:26:32 +01:00
ee013316c0 Merge pull request #860 from aripollak/fix-env-vars-with-equals
Allow run/exec -e with equals sign in value
2024-03-09 23:25:56 +02:00
f2f5483a74 Extract compose_run args parsing and add unit tests
Signed-off-by: Ari Pollak <ajp@aripollak.com>
2024-03-09 16:18:13 -05:00
c4fa8f7a16 Split exec args parsing into new function and add unit tests for it
Signed-off-by: Ari Pollak <ajp@aripollak.com>
2024-03-09 16:02:47 -05:00
4c270b9116 Allow run/exec -e with equals sign in value
Fixes: #798
Signed-off-by: Ari Pollak <ajp@aripollak.com>
2024-03-09 14:35:08 -05:00
c5f7f550f9 Remove implemented commands from Missing Commands
Signed-off-by: Ari Pollak <ajp@aripollak.com>
2024-03-09 14:32:29 -05:00
c98cbaaaf0 Fix #782: add support for http_proxy
Signed-off-by: ChuJiani <archet@chujiani.top>
2024-03-09 17:10:09 +00:00
f2f2f15a3b Merge pull request #765 from timocov/devel
Added handling `pid` option
2024-03-09 18:30:48 +02:00
91d316ff1f Added handling pid option
Fixes #554

Signed-off-by: Evgeniy Timokhov <timocov@gmail.com>
2024-03-09 16:10:37 +00:00
121b5f6ea1 Merge pull request #749 from maxi0604/more-network-options
Add support for enable_ipv6
2024-03-09 13:03:27 +02:00
969edb88d0 Add support for enable_ipv6
Signed-off-by: Maximilian R <maxi.rostock@outlook.de>
2024-03-09 13:01:41 +02:00
bba1f33d51 Factor out arg creation for "network create". Add unit tests.
Signed-off-by: Maximilian R <public@complexvector.space>
2024-03-09 13:00:09 +02:00
970e4ac4ab Merge pull request #764 from lemmi/sysctls-dict
Handle sysctls maps
2024-03-09 12:47:10 +02:00
a9c335bf82 Handle sysctls maps
- Add support to handle sysctls maps.
- Directly raise an error if sysctls is neither an array nor map instead
  of letting podman fail with an unhelpful message.

Support for sysctls arrays was added in #261.

Fixes #754: sysctls only works with arrays, not maps

Signed-off-by: lemmi <lemmi@nerd2nerd.org>
2024-03-09 12:44:55 +02:00
e67c52f2c4 test_container_to_args: handle async tests
Signed-off-by: lemmi <lemmi@nerd2nerd.org>
2024-03-09 12:44:55 +02:00
e0fc9a7546 Merge pull request #696 from dkull/devel
Fix stop/restart args stacking
2024-03-09 12:42:54 +02:00
2cdfb3e6d4 Fix stop/restart args stacking
Signed-off-by: Tanel Liiv <tanel.liiv@reconeyez.com>
Signed-off-by: Tanel Liiv <liivtanel@gmail.com>
2024-03-09 12:39:53 +02:00
60137eb7f9 Merge pull request #741 from maurerle/quiet-output
Output: use logging, remove verbose output by default
2024-03-09 12:35:00 +02:00
a7a993faef Merge pull request #886 from p12tic/github-pr-template
github: Add PR template
2024-03-09 12:27:17 +02:00
8ec5e0372a github: Add PR template
Adding a link to related part of compose spec is much easier for the
contributors, because they're already working on the feature. This saves
reviewer time.

Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-03-09 12:18:19 +02:00
da520e299b switch to % string lazy formatting
* fix black and pylint issues

Signed-off-by: Florian Maurer <f.maurer@outlook.de>
2024-03-09 10:40:24 +01:00
1e9e2ee776 Output: use logging, remove garbage output by default
Fixes #489.

Introduces a --verbose flag if you want to see all the noise that
was previously printed by default.

Signed-off-by: James O'Beirne <james.obeirne@pm.me>
Signed-off-by: Florian Maurer <f.maurer@outlook.de>
2024-03-09 10:36:20 +01:00
dcb6cdb55a Merge pull request #858 from breca/asyncio_compat
Replaces asyncio timeout with bespoke timeout function
2024-03-09 11:05:17 +02:00
0f693ee584 Replaces asyncio timeout with bespoke timeout function
Signed-off-by: Brett Calliss <brett@obligatory.email>
2024-03-09 19:02:32 +11:00
d468f85fd1 Merge pull request #755 from yarikoptic/enh-codespell
Add codespell config (to ignore loved "poped") and github workflow to prevent future typos
2024-03-09 01:47:25 +02:00
cf90ab2858 Run codespell throughout fixing typos automagically
Signed-off-by: Yaroslav Halchenko <debian@onerussian.com>
2024-03-08 18:41:03 -05:00
d2fa80196f ignore poped (not sure why not to make it popped)
Signed-off-by: Yaroslav Halchenko <debian@onerussian.com>
2024-03-08 18:41:03 -05:00
247774822c Add pre-commit definition for codespell
Signed-off-by: Yaroslav Halchenko <debian@onerussian.com>
2024-03-08 18:41:03 -05:00
6e65a73ab9 Add rudimentary codespell config
Signed-off-by: Yaroslav Halchenko <debian@onerussian.com>
2024-03-08 18:41:03 -05:00
f95ca7aca7 Add github action to codespell devel on push and PRs
Signed-off-by: Yaroslav Halchenko <debian@onerussian.com>
2024-03-08 18:41:03 -05:00
cdf5961bf2 Merge pull request #758 from containers/dependabot/github_actions/actions/checkout-4
Bump actions/checkout from 3 to 4
2024-03-09 01:09:28 +02:00
0c0e77c246 Bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-08 22:51:44 +00:00
e6fc585702 Merge pull request #884 from p12tic/lint-fixes
Fix unsorted imports
2024-03-08 23:59:06 +02:00
bb2338e447 Fix unsorted imports
This is a logical merge conflict that has been caused by several PRs
landing in parallel.

Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-03-08 23:54:48 +02:00
a8db898ac6 Merge pull request #883 from p12tic/lint-fixes
Enable ruff check and fix lint errors
2024-03-08 23:51:25 +02:00
1ba1c364b9 Merge pull request #819 from p12tic/service-runtime
Add support for runtime service configuration key
2024-03-08 23:51:17 +02:00
15ae2140d4 Add support for runtime service configuration key
https://docs.docker.com/compose/compose-file/05-services/#runtime

Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-03-08 23:48:55 +02:00
ed39523342 github: Enable ruff check
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-03-08 23:47:13 +02:00
4e43606df3 Shorten too long lines
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-03-08 23:46:24 +02:00
91052cb2d9 Fix ambiguous variable name
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-03-08 23:46:23 +02:00
a6e0092627 Remove unused variable
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-03-08 23:46:22 +02:00
59a1fa3942 Use 'not in' for membership test
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-03-08 23:46:21 +02:00
36139fb282 Remove unused f-string
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-03-08 23:46:20 +02:00
b0da6f82d3 Fix import formatting
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-03-08 23:46:19 +02:00
b1e4324d41 Merge pull request #882 from p12tic/readme-fix-badge-status
README: fix status badges
2024-03-08 16:26:23 +02:00
94df95acea README: Fix link to tests badge
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-03-08 16:09:15 +02:00
3a5a283cf6 README: Remove link to pylint test badge
This is an internal metric that the users don't need to care about at
all.

Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-03-08 16:09:11 +02:00
16a90e2bce Merge pull request #873 from p12tic/github-run-container
github: Run actions on python:3.11-bookworm
2024-03-08 15:58:18 +02:00
7c81044860 github: Run actions on python:3.11-bookworm
This ensures a stable environment for tests even when ubuntu-latest
changes. Also, the dependency on setup-python action has been
removed. That action supports only Ubuntu and limited architectures.

Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-03-08 15:56:30 +02:00
5b571942e0 Merge pull request #880 from p12tic/fix-test-names
github: Fix test names
2024-03-08 15:56:01 +02:00
de8f545f07 Merge pull request #881 from p12tic/test-fix-teardown
tests: Properly tear down containers in extends_w_file_subdir test
2024-03-08 15:55:54 +02:00
1a24cde608 tests: Properly tear down containers in extends_w_file_subdir test
podman rmi does not guarantee that the container itself is shut down.

Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-03-08 15:52:40 +02:00
a90da4dfaf github: Fix test names
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-03-08 15:51:24 +02:00
6841619b9c Merge pull request #879 from p12tic/tests-debug-output
tests: Add verbose debug option via env variable
2024-03-08 15:50:58 +02:00
27c8cebbdc tests: Add verbose debug option via env variable
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-03-08 15:34:00 +02:00
c84b4c33fc Merge pull request #878 from p12tic/tests-decoded-output
tests: Print decoded subprocess output in case of failure
2024-03-08 15:15:24 +02:00
0614687840 tests: Print decoded subprocess output in case of failure
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-03-08 15:11:25 +02:00
a494f20e15 Merge pull request #877 from p12tic/tests-faster
tests: Optimize speed by using dumb-init to handle SIGTERM signal
2024-03-08 14:49:25 +02:00
6af7a2d691 tests: Optimize speed by using dumb-init to handle SIGTERM signal
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-03-08 14:45:53 +02:00
872664b727 Merge pull request #876 from p12tic/tests-print-subprocess-errors
tests: Print output in case subprocess returns unexpected code
2024-03-08 14:41:23 +02:00
f4dc5f3b93 tests: Print output in case subprocess returns unexpected code
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-03-08 13:14:02 +02:00
ac5034065d Merge pull request #875 from p12tic/spdx-identifiers
Add SPDX license identifiers to each source file
2024-03-08 12:35:02 +02:00
b34f699adb Add SPDX license identifiers to each source file
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-03-08 12:19:51 +02:00
3d68d2432d Merge pull request #874 from p12tic/github-tests-on-all-branches
github: Run tests on all branches
2024-03-08 12:09:57 +02:00
2c6c1be197 github: Run tests on all branches
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-03-08 12:02:06 +02:00
f81cbe39b5 Merge pull request #872 from containers/dependabot/github_actions/actions/setup-python-5
Bump actions/setup-python from 4 to 5
2024-03-08 11:43:49 +02:00
91737ee0a6 Bump actions/setup-python from 4 to 5
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-08 09:41:46 +00:00
9d1407ba90 Merge pull request #871 from p12tic/unittest
Migrate tests to unittest
2024-03-08 11:39:20 +02:00
b65d4a3916 github: Fix ruff job setup
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-03-08 11:36:57 +02:00
23fe9e7e1d Migrate tests to unittest
unittest is much more straightforward without any magic. In a small
project like podman-compose being easy to understand is more important
than features.

Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-03-08 11:33:17 +02:00
7539257ee8 requirements: Install . for tests
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-03-08 11:00:33 +02:00
9e29891aa7 Merge pull request #870 from p12tic/ruff
Use ruff for formatting
2024-03-07 18:28:52 +02:00
a967cab02b Format codebase with ruff
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-03-07 18:28:06 +02:00
a5c354d60b Replace flake8, black, pylint with ruff
For now pylint checks are disabled.

Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-03-07 18:28:05 +02:00
e4e5b7d461 Make test requirements reproducible
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-03-07 18:28:04 +02:00
e0edd5dac1 Fix package installation
Signed-off-by: Brett Calliss <brett@obligatory.email>
2024-02-25 01:25:28 +03:00
831caa6276 remove recursion 2024-02-06 00:23:14 +03:00
9ac33392a0 Fix issue #831
Signed-off-by: Ben Plessinger <Benjamin.Plessinger@roswellpark.org>
2024-02-06 00:20:11 +03:00
c5be5bae90 Fixup tests
Signed-off-by: Falmarri <463948+Falmarri@users.noreply.github.com>
2024-02-04 10:11:57 +03:00
c6a1c4c432 Add tests to make sure all async paths are covered
Not at 100% yet. But upped code coverage significantly and covered major
async calls.

Signed-off-by: Falmarri <463948+Falmarri@users.noreply.github.com>
2024-02-04 10:11:57 +03:00
3c9628b462 Fix a couple issues and update docs
Signed-off-by: Falmarri <463948+Falmarri@users.noreply.github.com>
2024-02-04 10:11:57 +03:00
38b13a34ea Use asyncio for subprocess calls
Removes the threads from compose_up and manages it using async. Also
uses async processing to format the log messages instead of piping
through sed. This should work on windows without having sed installed

Adds --parallel to support pull and build in parallel, same as docker
compose

Signed-off-by: Falmarri <463948+Falmarri@users.noreply.github.com>
2024-02-04 10:11:57 +03:00
bce40c2db3 Change "an key-value" to "a key-value" 2023-08-08 18:05:58 +03:00
78f8cad7c4 Fix typos
Found via `codespell -L poped`
2023-08-08 18:05:25 +03:00
7942a540cd fix styling errors
Signed-off-by: Mohammed Tayeh <m.tayeh94@gmail.com>
2023-08-08 18:05:02 +03:00
cb9cf6002f add stats command
Signed-off-by: Mohammed Tayeh <info@tayeh.me>
2023-08-08 18:05:02 +03:00
06587c1dca rm redundant tests
Signed-off-by: Evedel <svbiriukov@gmail.com>
2023-08-02 14:19:15 +03:00
bc9168b039 add no-normalize flag
Signed-off-by: Evedel <svbiriukov@gmail.com>
2023-08-02 14:19:15 +03:00
57c527c2c9 add edits from review
Signed-off-by: Sergei Biriukov <svbiriukov@gmail.com>
2023-08-02 14:19:15 +03:00
d1f5ac9edc convert build context path to absolute during final normalisation
Signed-off-by: Sergei Biriukov <svbiriukov@gmail.com>
2023-08-02 14:19:15 +03:00
0164c1db56 Simplify the fix using or.
Signed-off-by: Natanael Arndt <arndtn@gmail.com>
2023-07-26 17:32:14 +03:00
e5cdce4e7d default to an empty dict for the from service if the service is None
Signed-off-by: Natanael Arndt <arndtn@gmail.com>
2023-07-26 17:32:14 +03:00
280f1770bf Add a test to extend using an empty service (placeholder)
Signed-off-by: Natanael Arndt <arndtn@gmail.com>
2023-07-26 17:32:14 +03:00
f75d12af21 broken in py 3.7 2023-07-18 13:23:55 +03:00
5454c3ad0f Add 'links' aliases to container aliases
Signed-off-by: Hedayat Vatankhah <hedayat.fwd@gmail.com>
2023-07-18 13:14:57 +03:00
901adf47d0 Use defined environment variables in the image build process
Build images with service environment variables defined so that they can be
used in the build process

Signed-off-by: Hedayat Vatankhah <hedayat.fwd@gmail.com>
2023-07-18 13:13:33 +03:00
bf07e91163 Implement include from compose-spec
Signed-off-by: Mahmoud Abduljawad <mahmoud@masaar.com>
2023-07-18 13:05:46 +03:00
3890eacf57 Merge branch 'Evedel-allow-config-to-merge-strings-and-dicts-in-build' into devel 2023-05-29 11:47:41 +03:00
cfd24cc2e8 Merge branch 'devel' into allow-config-to-merge-strings-and-dicts-in-build
Signed-off-by: Sergei Biriukov <svbiriukov@gmail.com>
2023-05-06 18:14:44 +10:00
79bfad103c move logic from rec_merge to normalize_service
Signed-off-by: Sergei Biriukov <svbiriukov@gmail.com>
2023-05-06 10:42:44 +03:00
d1509468c3 allow empty list to be a command/entrypoint
Signed-off-by: Sergei Biriukov <svbiriukov@gmail.com>
2023-05-06 10:42:44 +03:00
9011e9faa1 add tests, dry up code, use shlex.split instead of str.split
Signed-off-by: Sergei Biriukov <svbiriukov@gmail.com>
2023-05-06 10:42:44 +03:00
517aeba330 Allow config to merge strings and lists in command and entrypoint
Signed-off-by: Sergei Biriukov <svbiriukov@gmail.com>
2023-05-06 10:42:44 +03:00
85d5d5dcc9 move logic from rec_merge to normalize_service
Signed-off-by: Sergei Biriukov <svbiriukov@gmail.com>
2023-05-06 15:17:54 +10:00
1ffd24dcf9 Python version support: sync verified and advertised versions
There are differences with regards to the versions of Python that are
verified (somehow) through Pylint, and the ones that are advertised.

Given that there's no pinning of Pylint versions, it shouldn't be
possible to use it on Python versions such as 3.5 and 3.6 (latest
Pylint doesn't support those).  With that, let's cover all the
currently supported Python versions.

Signed-off-by: Cleber Rosa <crosa@redhat.com>
2023-05-06 00:28:13 +03:00
8c66b1cda7 add test case for when build is a complex dictionary
Signed-off-by: Sergei Biriukov <svbiriukov@gmail.com>
2023-04-30 15:37:52 +10:00
a0005db474 add code implementing build value merge
Signed-off-by: Sergei Biriukov <svbiriukov@gmail.com>
2023-04-29 13:52:19 +10:00
221cf14501 add tests for build value merge
Signed-off-by: Sergei Biriukov <svbiriukov@gmail.com>
2023-04-29 13:17:43 +10:00
a61945b516 fix format
Signed-off-by: Sergei Biriukov <svbiriukov@gmail.com>
2023-04-21 20:47:53 +03:00
6b6330c587 add build subcommand and --build arg to compose_run
Signed-off-by: Sergei Biriukov <svbiriukov@gmail.com>
2023-04-21 20:47:53 +03:00
5d279c4948 Build-fail test example
Signed-off-by: BugFest <bugfest.dev@pm.me>
2023-04-12 22:25:33 +03:00
5a3bdbf89b Exit code managed at PodmanCompose.run()
Signed-off-by: BugFest <bugfest.dev@pm.me>
2023-04-12 22:25:33 +03:00
1eb166445b Linting fixes
Signed-off-by: BugFest <bugfest.dev@pm.me>
2023-04-12 22:25:33 +03:00
82182b7bc6 Finish execution in compose_build only on command=build calls
Signed-off-by: BugFest <bugfest.dev@pm.me>
2023-04-12 22:25:33 +03:00
3f4618866b Update project-1.env 2023-04-10 14:14:14 +03:00
91bc6ebdb4 Keep chdir after loading env file
Signed-off-by: BugFest <bugfest.dev@pm.me>
2023-04-10 14:14:14 +03:00
59a59c1a3a Fixes #636: env-file shall be resolved relative to the CWD
Signed-off-by: BugFest <bugfest.dev@pm.me>
2023-04-10 14:14:14 +03:00
620f5d7473 pre-commit black config: run in check only mode
Signed-off-by: BugFest <bugfest.dev@pm.me>
2023-04-10 14:13:00 +03:00
6f902faed0 Fix linting issues
Signed-off-by: BugFest <bugfest.dev@pm.me>
2023-04-10 14:12:32 +03:00
ccdf01e9b0 Revert "Use SELinux mount flag for secrets"
This reverts commit 874192568f.
2023-04-10 12:26:53 +03:00
e6b1eabe4c Revert "Use more lenient SELinux mount flag for secrets"
This reverts commit 75de39c239.
2023-04-10 12:26:53 +03:00
75de39c239 Use more lenient SELinux mount flag for secrets
Signed-off-by: Henry Reed <60915078+henryreed@users.noreply.github.com>
2023-04-10 12:25:53 +03:00
874192568f Use SELinux mount flag for secrets
Signed-off-by: Henry Reed <github.69ofd@simplelogin.com>
2023-04-10 12:25:53 +03:00
0b853f29f4 Ignore access mode when merging volumes short syntax
The target path inside the container is treated as a key. Ref:
https://github.com/compose-spec/compose-spec/blob/master/spec.md#merging-service-definitions

Signed-off-by: Bhavin Gandhi <bhavin7392@gmail.com>
2023-04-10 12:25:05 +03:00
847f01a6c6 Add a docker-compose test file for uidmaps/gidmaps
Add a simple docker-compose.yml test to use the x-podman extension with
uidmaps and gitmaps
2023-04-10 12:22:25 +03:00
e511e6420f FIXES #228: Add support for uidmap and gidmap
Implement an x-podman extension on the level of the individual services
to handle `--uidmap` and `--gidmap`
2023-04-10 12:22:25 +03:00
a9723ec1cf Added a way to start containers with multiple ips and nets
Signed-off-by: KuhnChris <kuhnchris@kuhnchris.eu>
2023-04-10 12:16:54 +03:00
1cb608d8a7 allow project name to be fetched from dotenv
Look for project name in `self.environ` which includes both `os.environ`
and dotenv variables so that the project name can also be defined in an
environment file.

Signed-off-by: Kuan-Yi Li <kyli@abysm.org>
2023-04-10 12:13:23 +03:00
252f1d57a5 updating black formatting for podman-compose.py
Signed-off-by: Dixon Whitmire <dixonwh@gmail.com>
2023-04-10 12:12:18 +03:00
13856d2e9c updating black formatting
Signed-off-by: Dixon Whitmire <dixonwh@gmail.com>
2023-04-10 12:12:18 +03:00
8d8df0bc28 Adding basic support for --profile argument
Signed-off-by: Dixon Whitmire <dixonwh@gmail.com>
2023-04-10 12:12:18 +03:00
bc5f0123d9 add option to start podman in existing network namespace 2023-04-10 12:11:02 +03:00
9a08f85ffd FIXES #586: preserve exit code for podman-compose build
Signed-off-by: Roman Blanco <rblanco@redhat.com>
2023-04-10 12:10:16 +03:00
8625d7a4e8 add ipam-driver support
Signed-off-by: Benedikt Braunger <bb@emlix.com>
2023-04-10 12:02:47 +03:00
016c97fd1e Fixes #663 - Fixes linting/pylint errors
Signed-off-by: BugFest <bugfest.dev@pm.me>
2023-04-10 11:53:47 +03:00
2df11674c4 Fixes #661 - Fixes linting/flake8 errors
Signed-off-by: BugFest <bugfest.dev@pm.me>
2023-04-10 11:53:47 +03:00
5eff38e743 Fixes #659: fix permissions when installing OS packages for linting/black
Signed-off-by: BugFest <bugfest.dev@pm.me>
2023-04-10 11:28:07 +03:00
7f5ce26b1b start version 1.0.7 and default with pod enabled by default 2023-04-09 14:08:54 +03:00
80 changed files with 3778 additions and 826 deletions

4
.codespellrc Normal file
View File

@ -0,0 +1,4 @@
[codespell]
skip = .git,*.pdf,*.svg,requirements.txt,test-requirements.txt
# poped - loved variable name
ignore-words-list = poped

2
.coveragerc Normal file
View File

@ -0,0 +1,2 @@
[run]
parallel=True

19
.editorconfig Normal file
View File

@ -0,0 +1,19 @@
root = true
[*]
indent_style = space
indent_size = tab
tab_width = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 100
[*.{yml,yaml}]
indent_style = space
indent_size = 2
[*.py]
indent_style = space

7
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,7 @@
## Contributor Checklist:
If this PR adds a new feature that improves compatibility with docker-compose, please add a link
to the exact part of compose spec that the PR touches.
All changes require additional unit tests.

20
.github/workflows/codespell.yml vendored Normal file
View File

@ -0,0 +1,20 @@
---
name: Codespell
on:
push:
pull_request:
permissions:
contents: read
jobs:
codespell:
name: Check for spelling errors
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Codespell
uses: codespell-project/actions-codespell@v2

View File

@ -1,41 +0,0 @@
name: Pylint
on:
- push
- pull_request
jobs:
lint-black:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install psf/black requirements
run: |
apt-get update
apt-get install -y python3 python3-venv
- uses: psf/black@stable
with:
options: "--check --verbose"
version: "~= 23.3"
lint-pylint:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
pip install pylint
- name: Analysing the code with pylint
run: |
python -m compileall podman_compose.py
pylint podman_compose.py
# pylint $(git ls-files '*.py')

View File

@ -1,36 +0,0 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
name: PyTest
on:
push:
branches: [ devel ]
pull_request:
branches: [ devel ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.10
uses: actions/setup-python@v4
with:
python-version: "3.10"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest
run: |
python -m pytest ./pytests

25
.github/workflows/static-checks.yml vendored Normal file
View File

@ -0,0 +1,25 @@
name: Static checks
on:
- push
- pull_request
jobs:
static-checks:
runs-on: ubuntu-latest
container:
image: docker.io/library/python:3.11-bookworm
# cgroupns needed to address the following error:
# write /sys/fs/cgroup/cgroup.subtree_control: operation not supported
options: --privileged --cgroupns=host
steps:
- uses: actions/checkout@v4
- name: Analysing the code with ruff
run: |
set -e
pip install -r test-requirements.txt
ruff format --check
ruff check
- name: Analysing the code with pylint
run: |
pylint podman_compose.py

40
.github/workflows/test.yml vendored Normal file
View File

@ -0,0 +1,40 @@
name: Tests
on:
push:
pull_request:
jobs:
test:
strategy:
fail-fast: false
matrix:
python-version: [ '3.8', '3.9', '3.10', '3.11', '3.12' ]
runs-on: ubuntu-latest
container:
image: "docker.io/library/python:${{ matrix.python-version }}-bookworm"
# cgroupns needed to address the following error:
# write /sys/fs/cgroup/cgroup.subtree_control: operation not supported
options: --privileged --cgroupns=host
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: |
set -e
apt update && apt install -y podman
python -m pip install --upgrade pip
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
if [ -f test-requirements.txt ]; then pip install -r test-requirements.txt; fi
- name: Run tests in tests/
run: |
python -m unittest tests/*.py
env:
TESTS_DEBUG: 1
- name: Run tests in pytests/
run: |
coverage run --source podman_compose -m unittest pytests/*.py
- name: Report coverage
run: |
coverage combine
coverage report --format=markdown | tee -a $GITHUB_STEP_SUMMARY

2
.gitignore vendored
View File

@ -47,6 +47,8 @@ coverage.xml
*.cover
.hypothesis/
.pytest_cache/
test-compose.yaml
test-compose-?.yaml
# Translations
*.mo

View File

@ -9,6 +9,9 @@ repos:
# https://pre-commit.com/#top_level-default_language_version
language_version: python3.10
types: [python]
args: [
"--check", # Don't apply changes automatically
]
- repo: https://github.com/pycqa/flake8
rev: 6.0.0
hooks:
@ -27,3 +30,7 @@ repos:
"-sn", # Don't display the score
"--rcfile=.pylintrc", # Link to your config file
]
- repo: https://github.com/codespell-project/codespell
rev: v2.2.5
hooks:
- id: codespell

View File

@ -1,114 +1,141 @@
# Contributing to podman-compose
## Who can contribute?
## Who can contribute?
- Users that found a bug
- Users that wants to propose new functionalities or enhancements
- Users that want to help other users to troubleshoot their environments
- Developers that want to fix bugs
- Developers that want to implement new functionalities or enhancements
- Users that found a bug,
- Users that want to propose new functionalities or enhancements,
- Users that want to help other users to troubleshoot their environments,
- Developers that want to fix bugs,
- Developers that want to implement new functionalities or enhancements.
## Branches
Please request your PR to be merged into the `devel` branch.
Please request your pull request to be merged into the `devel` branch.
Changes to the `stable` branch are managed by the repository maintainers.
## Development environment setup
Note: Some steps are OPTIONAL but all are RECOMMENDED.
1. Fork the project repo and clone it
```shell
$ git clone https://github.com/USERNAME/podman-compose.git
$ cd podman-compose
```
1. (OPTIONAL) Create a python virtual environment. Example using [virtualenv wrapper](https://virtualenvwrapper.readthedocs.io/en/latest/):
```shell
mkvirtualenv podman-compose
```
2. Install the project runtime and development requirements
```shell
$ pip install '.[devel]'
```
3. (OPTIONAL) Install `pre-commit` git hook scripts (https://pre-commit.com/#3-install-the-git-hook-scripts)
```shell
$ pre-commit install
```
4. Create a new branch, develop and add tests when possible
5. Run linting & testing before commiting code. Ensure all the hooks are passing.
```shell
$ pre-commit run --all-files
```
6. Commit your code to your fork's branch.
- Make sure you include a `Signed-off-by` message in your commits. Read [this guide](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits) to learn how to sign your commits
- In the commit message reference the Issue ID that your code fixes and a brief description of the changes. Example: `Fixes #516: allow empty network`
7. Open a PR to `containers/podman-compose:devel` and wait for a maintainer to review your work.
1. Fork the project repository and clone it:
```shell
$ git clone https://github.com/USERNAME/podman-compose.git
$ cd podman-compose
```
2. (OPTIONAL) Create a Python virtual environment. Example using
[virtualenv wrapper](https://virtualenvwrapper.readthedocs.io/en/latest/):
```shell
$ mkvirtualenv podman-compose
```
3. Install the project runtime and development requirements:
```shell
$ pip install '.[devel]'
```
4. (OPTIONAL) Install `pre-commit` git hook scripts
(https://pre-commit.com/#3-install-the-git-hook-scripts):
```shell
$ pre-commit install
```
5. Create a new branch, develop and add tests when possible.
6. Run linting and testing before committing code. Ensure all the hooks are passing.
```shell
$ pre-commit run --all-files
```
7. Run code coverage:
```shell
$ coverage run --source podman_compose -m unittest pytests/*.py
$ python -m unittest tests/*.py
$ coverage combine
$ coverage report
$ coverage html
```
8. Commit your code to your fork's branch.
- Make sure you include a `Signed-off-by` message in your commits.
Read [this guide](https://github.com/containers/common/blob/main/CONTRIBUTING.md#sign-your-prs)
to learn how to sign your commits.
- In the commit message, reference the Issue ID that your code fixes and a brief description of
the changes.
Example: `Fixes #516: Allow empty network`
9. Open a pull request to `containers/podman-compose:devel` and wait for a maintainer to review your
work.
## Adding new commands
To add a command you need to add a function that is decorated
with `@cmd_run` passing the compose instance, command name and
description. the wrapped function should accept two arguments
the compose instance and the command-specific arguments (resulted
from python's `argparse` package) inside that command you can
run PodMan like this `compose.podman.run(['inspect', 'something'])`
and inside that function you can access `compose.pods`
and `compose.containers` ...etc.
Here is an example
To add a command, you need to add a function that is decorated with `@cmd_run`.
```
The decorated function must be declared `async` and should accept two arguments: The compose
instance and the command-specific arguments (resulted from the Python's `argparse` package).
In this function, you can run Podman (e.g. `await compose.podman.run(['inspect', 'something'])`),
access `compose.pods`, `compose.containers` etc.
Here is an example:
```python
@cmd_run(podman_compose, 'build', 'build images defined in the stack')
def compose_build(compose, args):
compose.podman.run(['build', 'something'])
async def compose_build(compose, args):
await compose.podman.run(['build', 'something'])
```
## Command arguments parsing
Add a function that accept `parser` which is an instance from `argparse`.
In side that function you can call `parser.add_argument()`.
The function decorated with `@cmd_parse` accepting the compose instance,
and command names (as a list or as a string).
You can do this multiple times.
To add arguments to be parsed by a command, you need to add a function that is decorated with
`@cmd_parse` which accepts the compose instance and the command's name (as a string list or as a
single string).
Here is an example
The decorated function should accept a single argument: An instance of `argparse`.
```
In this function, you can call `parser.add_argument()` to add a new argument to the command.
Note you can add such a function multiple times.
Here is an example:
```python
@cmd_parse(podman_compose, 'build')
def compose_build_parse(parser):
parser.add_argument("--pull",
help="attempt to pull a newer version of the image", action='store_true')
parser.add_argument("--pull-always",
help="attempt to pull a newer version of the image, Raise an error even if the image is present locally.", action='store_true')
help="Attempt to pull a newer version of the image, "
"raise an error even if the image is present locally.",
action='store_true')
```
NOTE: `@cmd_parse` should be after `@cmd_run`
NOTE: `@cmd_parse` should be after `@cmd_run`.
## Calling a command from inside another
## Calling a command from another one
If you need to call `podman-compose down` from inside `podman-compose up`
do something like:
If you need to call `podman-compose down` from `podman-compose up`, do something like:
```
```python
@cmd_run(podman_compose, 'up', 'up desc')
def compose_up(compose, args):
compose.commands['down'](compose, args)
async def compose_up(compose, args):
await compose.commands['down'](compose, args)
# or
compose.commands['down'](argparse.Namespace(foo=123))
await compose.commands['down'](argparse.Namespace(foo=123))
```
## Missing Commands (help needed)
```
bundle Generate a Docker bundle from the Compose file
config Validate and view the Compose file
create Create services
events Receive real time events from containers
images List images
logs View output from containers
port Print the public port for a port binding
ps List containers
rm Remove stopped containers
run Run a one-off command
scale Set number of containers for a service
top Display the running processes
```

View File

@ -1,6 +1,5 @@
# Podman Compose
## [![Pylint Test: ](https://github.com/containers/podman-compose/actions/workflows/pylint.yml/badge.svg)](https://github.com/containers/podman-compose/actions/workflows/pylint.yml) [![Unit tests PyTest](https://github.com/containers/podman-compose/actions/workflows/pytest.yml/badge.svg)](https://github.com/containers/podman-compose/actions/workflows/pytest.yml)
## [![Tests](https://github.com/containers/podman-compose/actions/workflows/test.yml/badge.svg)](https://github.com/containers/podman-compose/actions/workflows/test.yml)
An implementation of [Compose Spec](https://compose-spec.io/) with [Podman](https://podman.io/) backend.
This project focuses on:
@ -103,11 +102,11 @@ There is also AWX 17.1.0
Inside `tests/` directory we have many useless docker-compose stacks
that are meant to test as many cases as we can to make sure we are compatible
### Unit tests with pytest
run a pytest with following command
### Unit tests with unittest
run a unittest with following command
```shell
python -m pytest pytests
python -m unittest pytests/*.py
```
# Contributing guide

33
docs/Changelog-1.1.0.md Normal file
View File

@ -0,0 +1,33 @@
Version v1.1.0 (2024-04-17)
===========================
Bug fixes
---------
- Fixed support for values with equals sign in `-e` argument of `run` and `exec` commands.
- Fixed duplicate arguments being emitted in `stop` and `restart` commands.
- Removed extraneous debug output. `--verbose` flag has been added to preserve verbose output.
- Links aliases are now added to service aliases.
- Fixed image build process to use defined environmental variables.
- Empty list is now allowed to be `COMMAND` and `ENTRYPOINT`.
- Environment files are now resolved relative to current working directory.
- Exit code of container build is now preserved as return code of `build` command.
New features
------------
- Added support for `uidmap`, `gidmap`, `http_proxy` and `runtime` service configuration keys.
- Added support for `enable_ipv6` network configuration key.
- Added `--parallel` option to support parallel pulling and building of images.
- Implemented support for maps in `sysctls` container configuration key.
- Implemented `stats` command.
- Added `--no-normalize` flag to `config` command.
- Added support for `include` global configuration key.
- Added support for `build` command.
- Added support to start containers with multiple networks.
- Added support for `profile` argument.
- Added support for starting podman in existing network namespace.
- Added IPAM driver support.
- Added support for file secrets being passed to `podman build` via `--secret` argument.
- Added support for multiple networks with separately specified IP and MAC address.
- Added support for `service.build.ulimits` when building image.

67
docs/Extensions.md Normal file
View File

@ -0,0 +1,67 @@
# Podman specific extensions to the docker-compose format
Podman-compose supports the following extension to the docker-compose format.
## Per-network MAC-addresses
Generic docker-compose files support specification of the MAC address on the container level. If the
container has multiple network interfaces, the specified MAC address is applied to the first
specified network.
Podman-compose in addition supports the specification of MAC addresses on a per-network basis. This
is done by adding a `x-podman.mac_address` key to the network configuration in the container. The
value of the `x-podman.mac_address` key is the MAC address to be used for the network interface.
Specifying a MAC address for the container and for individual networks at the same time is not
supported.
Example:
```yaml
---
version: "3"
networks:
net0:
driver: "bridge"
ipam:
config:
- subnet: "192.168.0.0/24"
net1:
driver: "bridge"
ipam:
config:
- subnet: "192.168.1.0/24"
services:
webserver
image: "busybox"
command: ["/bin/busybox", "httpd", "-f", "-h", "/etc", "-p", "8001"]
networks:
net0:
ipv4_address: "192.168.0.10"
x-podman.mac_address: "02:aa:aa:aa:aa:aa"
net1:
ipv4_address: "192.168.1.10"
x-podman.mac_address: "02:bb:bb:bb:bb:bb"
```
## Podman-specific network modes
Generic docker-compose supports the following values for `network-mode` for a container:
- `bridge`
- `host`
- `none`
- `service`
- `container`
In addition, podman-compose supports the following podman-specific values for `network-mode`:
- `slirp4netns[:<options>,...]`
- `ns:<options>`
- `pasta[:<options>,...]`
- `private`
The options to the network modes are passed to the `--network` option of the `podman create` command
as-is.

View File

@ -1,7 +1,7 @@
---
- name: Manage AWX Container Images
block:
- name: Export Docker awx image if it isnt local and there isnt a registry defined
- name: Export Docker awx image if it isn't local and there isn't a registry defined
docker_image:
name: "{{ awx_image }}"
tag: "{{ awx_version }}"

View File

@ -7,6 +7,6 @@ RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD [ "python", "-m", "App.web" ]
CMD [ "python", "-m", "app.web" ]
EXPOSE 8080

View File

@ -1,5 +1,7 @@
# pylint: disable=import-error
# pylint: disable=unused-import
import asyncio # noqa: F401
import os
import asyncio
import aioredis
from aiohttp import web
@ -14,13 +16,13 @@ routes = web.RouteTableDef()
@routes.get("/")
async def hello(request):
async def hello(request): # pylint: disable=unused-argument
counter = await redis.incr("mycounter")
return web.Response(text=f"counter={counter}")
@routes.get("/hello.json")
async def hello_json(request):
async def hello_json(request): # pylint: disable=unused-argument
counter = await redis.incr("mycounter")
data = {"counter": counter}
return web.json_response(data)

File diff suppressed because it is too large Load Diff

15
pyproject.toml Normal file
View File

@ -0,0 +1,15 @@
[tool.ruff]
line-length = 100
target-version = "py38"
[tool.ruff.lint]
select = ["W", "E", "F", "I"]
ignore = [
]
[tool.ruff.lint.isort]
force-single-line = true
[tool.ruff.format]
preview = true # needed for quote-style
quote-style = "preserve"

0
pytests/__init__.py Normal file
View File

View File

@ -0,0 +1,143 @@
# SPDX-License-Identifier: GPL-2.0
from __future__ import annotations
import argparse
import os
import unittest
import yaml
from parameterized import parameterized
from podman_compose import PodmanCompose
from podman_compose import normalize_service
class TestCanMergeBuild(unittest.TestCase):
@parameterized.expand([
({"test": "test"}, {"test": "test"}),
({"build": "."}, {"build": {"context": "."}}),
({"build": "./dir-1"}, {"build": {"context": "./dir-1"}}),
({"build": {"context": "./dir-1"}}, {"build": {"context": "./dir-1"}}),
(
{"build": {"dockerfile": "dockerfile-1"}},
{"build": {"dockerfile": "dockerfile-1"}},
),
(
{"build": {"context": "./dir-1", "dockerfile": "dockerfile-1"}},
{"build": {"context": "./dir-1", "dockerfile": "dockerfile-1"}},
),
])
def test_simple(self, input, expected):
self.assertEqual(normalize_service(input), expected)
@parameterized.expand([
({"test": "test"}, {"test": "test"}),
({"build": "."}, {"build": {"context": "./sub_dir/."}}),
({"build": "./dir-1"}, {"build": {"context": "./sub_dir/dir-1"}}),
({"build": {"context": "./dir-1"}}, {"build": {"context": "./sub_dir/dir-1"}}),
(
{"build": {"dockerfile": "dockerfile-1"}},
{"build": {"context": "./sub_dir", "dockerfile": "dockerfile-1"}},
),
(
{"build": {"context": "./dir-1", "dockerfile": "dockerfile-1"}},
{"build": {"context": "./sub_dir/dir-1", "dockerfile": "dockerfile-1"}},
),
])
def test_normalize_service_with_sub_dir(self, input, expected):
self.assertEqual(normalize_service(input, sub_dir="./sub_dir"), expected)
@parameterized.expand([
({}, {}, {}),
({}, {"test": "test"}, {"test": "test"}),
({"test": "test"}, {}, {"test": "test"}),
({"test": "test-1"}, {"test": "test-2"}, {"test": "test-2"}),
({}, {"build": "."}, {"build": {"context": "."}}),
({"build": "."}, {}, {"build": {"context": "."}}),
({"build": "./dir-1"}, {"build": "./dir-2"}, {"build": {"context": "./dir-2"}}),
({}, {"build": {"context": "./dir-1"}}, {"build": {"context": "./dir-1"}}),
({"build": {"context": "./dir-1"}}, {}, {"build": {"context": "./dir-1"}}),
(
{"build": {"context": "./dir-1"}},
{"build": {"context": "./dir-2"}},
{"build": {"context": "./dir-2"}},
),
(
{},
{"build": {"dockerfile": "dockerfile-1"}},
{"build": {"dockerfile": "dockerfile-1"}},
),
(
{"build": {"dockerfile": "dockerfile-1"}},
{},
{"build": {"dockerfile": "dockerfile-1"}},
),
(
{"build": {"dockerfile": "./dockerfile-1"}},
{"build": {"dockerfile": "./dockerfile-2"}},
{"build": {"dockerfile": "./dockerfile-2"}},
),
(
{"build": {"dockerfile": "./dockerfile-1"}},
{"build": {"context": "./dir-2"}},
{"build": {"dockerfile": "./dockerfile-1", "context": "./dir-2"}},
),
(
{"build": {"dockerfile": "./dockerfile-1", "context": "./dir-1"}},
{"build": {"dockerfile": "./dockerfile-2", "context": "./dir-2"}},
{"build": {"dockerfile": "./dockerfile-2", "context": "./dir-2"}},
),
(
{"build": {"dockerfile": "./dockerfile-1"}},
{"build": {"dockerfile": "./dockerfile-2", "args": ["ENV1=1"]}},
{"build": {"dockerfile": "./dockerfile-2", "args": ["ENV1=1"]}},
),
(
{"build": {"dockerfile": "./dockerfile-2", "args": ["ENV1=1"]}},
{"build": {"dockerfile": "./dockerfile-1"}},
{"build": {"dockerfile": "./dockerfile-1", "args": ["ENV1=1"]}},
),
(
{"build": {"dockerfile": "./dockerfile-2", "args": ["ENV1=1"]}},
{"build": {"dockerfile": "./dockerfile-1", "args": ["ENV2=2"]}},
{"build": {"dockerfile": "./dockerfile-1", "args": ["ENV1=1", "ENV2=2"]}},
),
])
def test_parse_compose_file_when_multiple_composes(self, input, override, expected):
compose_test_1 = {"services": {"test-service": input}}
compose_test_2 = {"services": {"test-service": override}}
dump_yaml(compose_test_1, "test-compose-1.yaml")
dump_yaml(compose_test_2, "test-compose-2.yaml")
podman_compose = PodmanCompose()
set_args(podman_compose, ["test-compose-1.yaml", "test-compose-2.yaml"])
podman_compose._parse_compose_file() # pylint: disable=protected-access
actual_compose = {}
if podman_compose.services:
podman_compose.services["test-service"].pop("_deps")
actual_compose = podman_compose.services["test-service"]
self.assertEqual(actual_compose, expected)
def set_args(podman_compose: PodmanCompose, file_names: list[str]) -> None:
podman_compose.global_args = argparse.Namespace()
podman_compose.global_args.file = file_names
podman_compose.global_args.project_name = None
podman_compose.global_args.env_file = None
podman_compose.global_args.profile = []
podman_compose.global_args.in_pod = True
podman_compose.global_args.no_normalize = True
def dump_yaml(compose: dict, name: str) -> None:
with open(name, "w", encoding="utf-8") as outfile:
yaml.safe_dump(compose, outfile, default_flow_style=False)
def test_clean_test_yamls() -> None:
test_files = ["test-compose-1.yaml", "test-compose-2.yaml"]
for file in test_files:
if os.path.exists(file):
os.remove(file)

View File

@ -0,0 +1,115 @@
# SPDX-License-Identifier: GPL-2.0
from __future__ import annotations
import argparse
import copy
import os
import unittest
import yaml
from parameterized import parameterized
from podman_compose import PodmanCompose
from podman_compose import normalize_service
test_keys = ["command", "entrypoint"]
class TestCanMergeCmdEnt(unittest.TestCase):
@parameterized.expand([
({"$$$": []}, {"$$$": []}),
({"$$$": ["sh"]}, {"$$$": ["sh"]}),
({"$$$": ["sh", "-c", "date"]}, {"$$$": ["sh", "-c", "date"]}),
({"$$$": "sh"}, {"$$$": ["sh"]}),
({"$$$": "sleep infinity"}, {"$$$": ["sleep", "infinity"]}),
(
{"$$$": "bash -c 'sleep infinity'"},
{"$$$": ["bash", "-c", "sleep infinity"]},
),
])
def test_normalize_service(self, input_template, expected_template):
for key in test_keys:
test_input, _, expected = template_to_expression(
input_template, {}, expected_template, key
)
self.assertEqual(normalize_service(test_input), expected)
@parameterized.expand([
({}, {"$$$": []}, {"$$$": []}),
({"$$$": []}, {}, {"$$$": []}),
({"$$$": []}, {"$$$": "sh-2"}, {"$$$": ["sh-2"]}),
({"$$$": "sh-2"}, {"$$$": []}, {"$$$": []}),
({}, {"$$$": "sh"}, {"$$$": ["sh"]}),
({"$$$": "sh"}, {}, {"$$$": ["sh"]}),
({"$$$": "sh-1"}, {"$$$": "sh-2"}, {"$$$": ["sh-2"]}),
({"$$$": ["sh-1"]}, {"$$$": "sh-2"}, {"$$$": ["sh-2"]}),
({"$$$": "sh-1"}, {"$$$": ["sh-2"]}, {"$$$": ["sh-2"]}),
({"$$$": "sh-1"}, {"$$$": ["sh-2", "sh-3"]}, {"$$$": ["sh-2", "sh-3"]}),
({"$$$": ["sh-1"]}, {"$$$": ["sh-2", "sh-3"]}, {"$$$": ["sh-2", "sh-3"]}),
({"$$$": ["sh-1", "sh-2"]}, {"$$$": ["sh-3", "sh-4"]}, {"$$$": ["sh-3", "sh-4"]}),
({}, {"$$$": ["sh-3", "sh 4"]}, {"$$$": ["sh-3", "sh 4"]}),
({"$$$": "sleep infinity"}, {"$$$": "sh"}, {"$$$": ["sh"]}),
({"$$$": "sh"}, {"$$$": "sleep infinity"}, {"$$$": ["sleep", "infinity"]}),
(
{},
{"$$$": "bash -c 'sleep infinity'"},
{"$$$": ["bash", "-c", "sleep infinity"]},
),
])
def test_parse_compose_file_when_multiple_composes(
self, base_template, override_template, expected_template
):
for key in test_keys:
base, override, expected = template_to_expression(
base_template, override_template, expected_template, key
)
compose_test_1 = {"services": {"test-service": base}}
compose_test_2 = {"services": {"test-service": override}}
dump_yaml(compose_test_1, "test-compose-1.yaml")
dump_yaml(compose_test_2, "test-compose-2.yaml")
podman_compose = PodmanCompose()
set_args(podman_compose, ["test-compose-1.yaml", "test-compose-2.yaml"])
podman_compose._parse_compose_file() # pylint: disable=protected-access
actual = {}
if podman_compose.services:
podman_compose.services["test-service"].pop("_deps")
actual = podman_compose.services["test-service"]
self.assertEqual(actual, expected)
def template_to_expression(base, override, expected, key):
base_copy = copy.deepcopy(base)
override_copy = copy.deepcopy(override)
expected_copy = copy.deepcopy(expected)
expected_copy[key] = expected_copy.pop("$$$")
if "$$$" in base:
base_copy[key] = base_copy.pop("$$$")
if "$$$" in override:
override_copy[key] = override_copy.pop("$$$")
return base_copy, override_copy, expected_copy
def set_args(podman_compose: PodmanCompose, file_names: list[str]) -> None:
podman_compose.global_args = argparse.Namespace()
podman_compose.global_args.file = file_names
podman_compose.global_args.project_name = None
podman_compose.global_args.env_file = None
podman_compose.global_args.profile = []
podman_compose.global_args.in_pod = True
podman_compose.global_args.no_normalize = None
def dump_yaml(compose: dict, name: str) -> None:
with open(name, "w", encoding="utf-8") as outfile:
yaml.safe_dump(compose, outfile, default_flow_style=False)
def test_clean_test_yamls() -> None:
test_files = ["test-compose-1.yaml", "test-compose-2.yaml"]
for file in test_files:
if os.path.exists(file):
os.remove(file)

View File

@ -0,0 +1,46 @@
# SPDX-License-Identifier: GPL-2.0
import argparse
import unittest
from podman_compose import compose_exec_args
class TestComposeExecArgs(unittest.TestCase):
def test_minimal(self):
cnt = get_minimal_container()
args = get_minimal_args()
result = compose_exec_args(cnt, "container_name", args)
expected = ["--interactive", "--tty", "container_name"]
self.assertEqual(result, expected)
def test_additional_env_value_equals(self):
cnt = get_minimal_container()
args = get_minimal_args()
args.env = ["key=valuepart1=valuepart2"]
result = compose_exec_args(cnt, "container_name", args)
expected = [
"--interactive",
"--tty",
"--env",
"key=valuepart1=valuepart2",
"container_name",
]
self.assertEqual(result, expected)
def get_minimal_container():
return {}
def get_minimal_args():
return argparse.Namespace(
T=None,
cnt_command=None,
env=None,
privileged=None,
user=None,
workdir=None,
)

View File

@ -0,0 +1,60 @@
# SPDX-License-Identifier: GPL-2.0
import argparse
import unittest
from podman_compose import PodmanCompose
from podman_compose import compose_run_update_container_from_args
class TestComposeRunUpdateContainerFromArgs(unittest.TestCase):
def test_minimal(self):
cnt = get_minimal_container()
compose = get_minimal_compose()
args = get_minimal_args()
compose_run_update_container_from_args(compose, cnt, args)
expected_cnt = {"name": "default_name", "tty": True}
self.assertEqual(cnt, expected_cnt)
def test_additional_env_value_equals(self):
cnt = get_minimal_container()
compose = get_minimal_compose()
args = get_minimal_args()
args.env = ["key=valuepart1=valuepart2"]
compose_run_update_container_from_args(compose, cnt, args)
expected_cnt = {
"environment": {
"key": "valuepart1=valuepart2",
},
"name": "default_name",
"tty": True,
}
self.assertEqual(cnt, expected_cnt)
def get_minimal_container():
return {}
def get_minimal_compose():
return PodmanCompose()
def get_minimal_args():
return argparse.Namespace(
T=None,
cnt_command=None,
entrypoint=None,
env=None,
name="default_name",
rm=None,
service=None,
service_ports=None,
user=None,
volume=None,
workdir=None,
)

View File

@ -0,0 +1,163 @@
# SPDX-License-Identifier: GPL-2.0
import unittest
from unittest import mock
from podman_compose import container_to_args
def create_compose_mock(project_name="test_project_name"):
compose = mock.Mock()
compose.project_name = project_name
compose.dirname = "test_dirname"
compose.container_names_by_service.get = mock.Mock(return_value=None)
compose.prefer_volume_over_mount = False
compose.default_net = None
compose.networks = {}
return compose
def get_minimal_container():
return {
"name": "project_name_service_name1",
"service_name": "service_name",
"image": "busybox",
}
class TestContainerToArgs(unittest.IsolatedAsyncioTestCase):
async def test_minimal(self):
c = create_compose_mock()
cnt = get_minimal_container()
args = await container_to_args(c, cnt)
self.assertEqual(
args,
[
"--name=project_name_service_name1",
"-d",
"--network=bridge",
"--network-alias=service_name",
"busybox",
],
)
async def test_runtime(self):
c = create_compose_mock()
cnt = get_minimal_container()
cnt["runtime"] = "runsc"
args = await container_to_args(c, cnt)
self.assertEqual(
args,
[
"--name=project_name_service_name1",
"-d",
"--network=bridge",
"--network-alias=service_name",
"--runtime",
"runsc",
"busybox",
],
)
async def test_sysctl_list(self):
c = create_compose_mock()
cnt = get_minimal_container()
cnt["sysctls"] = [
"net.core.somaxconn=1024",
"net.ipv4.tcp_syncookies=0",
]
args = await container_to_args(c, cnt)
self.assertEqual(
args,
[
"--name=project_name_service_name1",
"-d",
"--network=bridge",
"--network-alias=service_name",
"--sysctl",
"net.core.somaxconn=1024",
"--sysctl",
"net.ipv4.tcp_syncookies=0",
"busybox",
],
)
async def test_sysctl_map(self):
c = create_compose_mock()
cnt = get_minimal_container()
cnt["sysctls"] = {
"net.core.somaxconn": 1024,
"net.ipv4.tcp_syncookies": 0,
}
args = await container_to_args(c, cnt)
self.assertEqual(
args,
[
"--name=project_name_service_name1",
"-d",
"--network=bridge",
"--network-alias=service_name",
"--sysctl",
"net.core.somaxconn=1024",
"--sysctl",
"net.ipv4.tcp_syncookies=0",
"busybox",
],
)
async def test_sysctl_wrong_type(self):
c = create_compose_mock()
cnt = get_minimal_container()
# check whether wrong types are correctly rejected
for wrong_type in [True, 0, 0.0, "wrong", ()]:
with self.assertRaises(TypeError):
cnt["sysctls"] = wrong_type
await container_to_args(c, cnt)
async def test_pid(self):
c = create_compose_mock()
cnt = get_minimal_container()
cnt["pid"] = "host"
args = await container_to_args(c, cnt)
self.assertEqual(
args,
[
"--name=project_name_service_name1",
"-d",
"--network=bridge",
"--network-alias=service_name",
"--pid",
"host",
"busybox",
],
)
async def test_http_proxy(self):
c = create_compose_mock()
cnt = get_minimal_container()
cnt["http_proxy"] = False
args = await container_to_args(c, cnt)
self.assertEqual(
args,
[
"--name=project_name_service_name1",
"-d",
"--http-proxy=false",
"--network=bridge",
"--network-alias=service_name",
"busybox",
],
)

View File

@ -0,0 +1,298 @@
import unittest
from parameterized import parameterized
from podman_compose import get_net_args
from .test_container_to_args import create_compose_mock
PROJECT_NAME = "test_project_name"
SERVICE_NAME = "service_name"
CONTAINER_NAME = f"{PROJECT_NAME}_{SERVICE_NAME}_1"
def get_networked_compose(num_networks=1):
compose = create_compose_mock(PROJECT_NAME)
for network in range(num_networks):
compose.networks[f"net{network}"] = {
"driver": "bridge",
"ipam": {
"config": [
{"subnet": f"192.168.{network}.0/24"},
{"subnet": f"fd00:{network}::/64"},
]
},
"enable_ipv6": True,
}
return compose
def get_minimal_container():
return {
"name": CONTAINER_NAME,
"service_name": SERVICE_NAME,
"image": "busybox",
}
class TestGetNetArgs(unittest.TestCase):
def test_minimal(self):
compose = get_networked_compose()
container = get_minimal_container()
expected_args = [
"--network=bridge",
f"--network-alias={SERVICE_NAME}",
]
args = get_net_args(compose, container)
self.assertListEqual(expected_args, args)
def test_one_net(self):
compose = get_networked_compose()
container = get_minimal_container()
container["networks"] = {"net0": {}}
expected_args = [
f"--network={PROJECT_NAME}_net0",
f"--network-alias={SERVICE_NAME}",
]
args = get_net_args(compose, container)
self.assertListEqual(expected_args, args)
def test_alias(self):
compose = get_networked_compose()
container = get_minimal_container()
container["networks"] = {"net0": {}}
container["_aliases"] = ["alias1", "alias2"]
expected_args = [
f"--network={PROJECT_NAME}_net0",
f"--network-alias={SERVICE_NAME}",
"--network-alias=alias1",
"--network-alias=alias2",
]
args = get_net_args(compose, container)
self.assertListEqual(expected_args, args)
def test_one_ipv4(self):
ip = "192.168.0.42"
compose = get_networked_compose()
container = get_minimal_container()
container["networks"] = {"net0": {"ipv4_address": ip}}
expected_args = [
f"--network={PROJECT_NAME}_net0",
f"--ip={ip}",
f"--network-alias={SERVICE_NAME}",
]
args = get_net_args(compose, container)
self.assertEqual(expected_args, args)
def test_one_ipv6(self):
ipv6_address = "fd00:0::42"
compose = get_networked_compose()
container = get_minimal_container()
container["networks"] = {"net0": {"ipv6_address": ipv6_address}}
expected_args = [
f"--network={PROJECT_NAME}_net0",
f"--ip6={ipv6_address}",
f"--network-alias={SERVICE_NAME}",
]
args = get_net_args(compose, container)
self.assertListEqual(expected_args, args)
def test_one_mac(self):
mac = "00:11:22:33:44:55"
compose = get_networked_compose()
container = get_minimal_container()
container["networks"] = {"net0": {}}
container["mac_address"] = mac
expected_args = [
f"--network={PROJECT_NAME}_net0",
f"--mac-address={mac}",
f"--network-alias={SERVICE_NAME}",
]
args = get_net_args(compose, container)
self.assertListEqual(expected_args, args)
def test_one_mac_two_nets(self):
mac = "00:11:22:33:44:55"
compose = get_networked_compose(num_networks=6)
container = get_minimal_container()
container["networks"] = {"net0": {}, "net1": {}}
container["mac_address"] = mac
expected_args = [
f"--network={PROJECT_NAME}_net0:mac={mac}",
f"--network={PROJECT_NAME}_net1",
f"--network-alias={SERVICE_NAME}",
]
args = get_net_args(compose, container)
self.assertListEqual(expected_args, args)
def test_two_nets_as_dict(self):
compose = get_networked_compose(num_networks=2)
container = get_minimal_container()
container["networks"] = {"net0": {}, "net1": {}}
expected_args = [
f"--network={PROJECT_NAME}_net0",
f"--network={PROJECT_NAME}_net1",
f"--network-alias={SERVICE_NAME}",
]
args = get_net_args(compose, container)
self.assertListEqual(expected_args, args)
def test_two_nets_as_list(self):
compose = get_networked_compose(num_networks=2)
container = get_minimal_container()
container["networks"] = ["net0", "net1"]
expected_args = [
f"--network={PROJECT_NAME}_net0",
f"--network={PROJECT_NAME}_net1",
f"--network-alias={SERVICE_NAME}",
]
args = get_net_args(compose, container)
self.assertListEqual(expected_args, args)
def test_two_ipv4(self):
ip0 = "192.168.0.42"
ip1 = "192.168.1.42"
compose = get_networked_compose(num_networks=2)
container = get_minimal_container()
container["networks"] = {"net0": {"ipv4_address": ip0}, "net1": {"ipv4_address": ip1}}
expected_args = [
f"--network={PROJECT_NAME}_net0:ip={ip0}",
f"--network={PROJECT_NAME}_net1:ip={ip1}",
f"--network-alias={SERVICE_NAME}",
]
args = get_net_args(compose, container)
self.assertListEqual(expected_args, args)
def test_two_ipv6(self):
ip0 = "fd00:0::42"
ip1 = "fd00:1::42"
compose = get_networked_compose(num_networks=2)
container = get_minimal_container()
container["networks"] = {"net0": {"ipv6_address": ip0}, "net1": {"ipv6_address": ip1}}
expected_args = [
f"--network={PROJECT_NAME}_net0:ip={ip0}",
f"--network={PROJECT_NAME}_net1:ip={ip1}",
f"--network-alias={SERVICE_NAME}",
]
args = get_net_args(compose, container)
self.assertListEqual(expected_args, args)
# custom extension; not supported by docker-compose
def test_two_mac(self):
mac0 = "00:00:00:00:00:01"
mac1 = "00:00:00:00:00:02"
compose = get_networked_compose(num_networks=2)
container = get_minimal_container()
container["networks"] = {
"net0": {"x-podman.mac_address": mac0},
"net1": {"x-podman.mac_address": mac1},
}
expected_args = [
f"--network={PROJECT_NAME}_net0:mac={mac0}",
f"--network={PROJECT_NAME}_net1:mac={mac1}",
f"--network-alias={SERVICE_NAME}",
]
args = get_net_args(compose, container)
self.assertListEqual(expected_args, args)
def test_mixed_mac(self):
ip4_0 = "192.168.0.42"
ip4_1 = "192.168.1.42"
ip4_2 = "192.168.2.42"
mac_0 = "00:00:00:00:00:01"
mac_1 = "00:00:00:00:00:02"
compose = get_networked_compose(num_networks=3)
container = get_minimal_container()
container["networks"] = {
"net0": {"ipv4_address": ip4_0},
"net1": {"ipv4_address": ip4_1, "x-podman.mac_address": mac_0},
"net2": {"ipv4_address": ip4_2},
}
container["mac_address"] = mac_1
expected_exception = (
r"specifying mac_address on both container and network level " r"is not supported"
)
self.assertRaisesRegex(RuntimeError, expected_exception, get_net_args, compose, container)
def test_mixed_config(self):
ip4_0 = "192.168.0.42"
ip4_1 = "192.168.1.42"
ip6_0 = "fd00:0::42"
ip6_2 = "fd00:2::42"
mac = "00:11:22:33:44:55"
compose = get_networked_compose(num_networks=4)
container = get_minimal_container()
container["networks"] = {
"net0": {"ipv4_address": ip4_0, "ipv6_address": ip6_0},
"net1": {"ipv4_address": ip4_1},
"net2": {"ipv6_address": ip6_2},
"net3": {},
}
container["mac_address"] = mac
expected_args = [
f"--network={PROJECT_NAME}_net0:ip={ip4_0},ip={ip6_0},mac={mac}",
f"--network={PROJECT_NAME}_net1:ip={ip4_1}",
f"--network={PROJECT_NAME}_net2:ip={ip6_2}",
f"--network={PROJECT_NAME}_net3",
f"--network-alias={SERVICE_NAME}",
]
args = get_net_args(compose, container)
self.assertListEqual(expected_args, args)
@parameterized.expand([
("bridge", ["--network=bridge", f"--network-alias={SERVICE_NAME}"]),
("host", ["--network=host"]),
("none", []),
("slirp4netns", ["--network=slirp4netns"]),
("slirp4netns:cidr=10.42.0.0/24", ["--network=slirp4netns:cidr=10.42.0.0/24"]),
("private", ["--network=private"]),
("pasta", ["--network=pasta"]),
("pasta:--ipv4-only,-a,10.0.2.0", ["--network=pasta:--ipv4-only,-a,10.0.2.0"]),
("ns:my_namespace", ["--network=ns:my_namespace"]),
("container:my_container", ["--network=container:my_container"]),
])
def test_network_modes(self, network_mode, expected_args):
compose = get_networked_compose()
container = get_minimal_container()
container["network_mode"] = network_mode
args = get_net_args(compose, container)
self.assertListEqual(expected_args, args)
def test_network_mode_invalid(self):
compose = get_networked_compose()
container = get_minimal_container()
container["network_mode"] = "invalid_mode"
with self.assertRaises(SystemExit):
get_net_args(compose, container)
def test_network__mode_service(self):
compose = get_networked_compose()
compose.container_names_by_service = {
"service_1": ["container_1"],
"service_2": ["container_2"],
}
container = get_minimal_container()
container["network_mode"] = "service:service_2"
expected_args = ["--network=container:container_2"]
args = get_net_args(compose, container)
self.assertListEqual(expected_args, args)

View File

@ -0,0 +1,167 @@
import unittest
from podman_compose import get_network_create_args
class TestGetNetworkCreateArgs(unittest.TestCase):
def test_minimal(self):
net_desc = {
"labels": [],
"internal": False,
"driver": None,
"driver_opts": {},
"ipam": {"config": []},
"enable_ipv6": False,
}
proj_name = "test_project"
net_name = "test_network"
expected_args = [
"create",
"--label",
f"io.podman.compose.project={proj_name}",
"--label",
f"com.docker.compose.project={proj_name}",
net_name,
]
args = get_network_create_args(net_desc, proj_name, net_name)
self.assertEqual(args, expected_args)
def test_ipv6(self):
net_desc = {
"labels": [],
"internal": False,
"driver": None,
"driver_opts": {},
"ipam": {"config": []},
"enable_ipv6": True,
}
proj_name = "test_project"
net_name = "test_network"
expected_args = [
"create",
"--label",
f"io.podman.compose.project={proj_name}",
"--label",
f"com.docker.compose.project={proj_name}",
"--ipv6",
net_name,
]
args = get_network_create_args(net_desc, proj_name, net_name)
self.assertEqual(args, expected_args)
def test_bridge(self):
net_desc = {
"labels": [],
"internal": False,
"driver": "bridge",
"driver_opts": {"opt1": "value1", "opt2": "value2"},
"ipam": {"config": []},
"enable_ipv6": False,
}
proj_name = "test_project"
net_name = "test_network"
expected_args = [
"create",
"--label",
f"io.podman.compose.project={proj_name}",
"--label",
f"com.docker.compose.project={proj_name}",
"--driver",
"bridge",
"--opt",
"opt1=value1",
"--opt",
"opt2=value2",
net_name,
]
args = get_network_create_args(net_desc, proj_name, net_name)
self.assertEqual(args, expected_args)
def test_ipam_driver(self):
net_desc = {
"labels": [],
"internal": False,
"driver": None,
"driver_opts": {},
"ipam": {
"driver": "default",
"config": [
{
"subnet": "192.168.0.0/24",
"ip_range": "192.168.0.2/24",
"gateway": "192.168.0.1",
}
],
},
}
proj_name = "test_project"
net_name = "test_network"
expected_args = [
"create",
"--label",
f"io.podman.compose.project={proj_name}",
"--label",
f"com.docker.compose.project={proj_name}",
"--ipam-driver",
"default",
"--subnet",
"192.168.0.0/24",
"--ip-range",
"192.168.0.2/24",
"--gateway",
"192.168.0.1",
net_name,
]
args = get_network_create_args(net_desc, proj_name, net_name)
self.assertEqual(args, expected_args)
def test_complete(self):
net_desc = {
"labels": ["label1", "label2"],
"internal": True,
"driver": "bridge",
"driver_opts": {"opt1": "value1", "opt2": "value2"},
"ipam": {
"driver": "default",
"config": [
{
"subnet": "192.168.0.0/24",
"ip_range": "192.168.0.2/24",
"gateway": "192.168.0.1",
}
],
},
"enable_ipv6": True,
}
proj_name = "test_project"
net_name = "test_network"
expected_args = [
"create",
"--label",
f"io.podman.compose.project={proj_name}",
"--label",
f"com.docker.compose.project={proj_name}",
"--label",
"label1",
"--label",
"label2",
"--internal",
"--driver",
"bridge",
"--opt",
"opt1=value1",
"--opt",
"opt2=value2",
"--ipam-driver",
"default",
"--ipv6",
"--subnet",
"192.168.0.0/24",
"--ip-range",
"192.168.0.2/24",
"--gateway",
"192.168.0.1",
net_name,
]
args = get_network_create_args(net_desc, proj_name, net_name)
self.assertEqual(args, expected_args)

View File

@ -0,0 +1,271 @@
# SPDX-License-Identifier: GPL-2.0
# pylint: disable=protected-access
from __future__ import annotations
import argparse
import os
import unittest
import yaml
from parameterized import parameterized
from podman_compose import PodmanCompose
from podman_compose import normalize_final
from podman_compose import normalize_service_final
cwd = os.path.abspath(".")
class TestNormalizeFinalBuild(unittest.TestCase):
cases_simple_normalization = [
({"image": "test-image"}, {"image": "test-image"}),
(
{"build": "."},
{
"build": {"context": cwd, "dockerfile": "Dockerfile"},
},
),
(
{"build": "../relative"},
{
"build": {
"context": os.path.normpath(os.path.join(cwd, "../relative")),
"dockerfile": "Dockerfile",
},
},
),
(
{"build": "./relative"},
{
"build": {
"context": os.path.normpath(os.path.join(cwd, "./relative")),
"dockerfile": "Dockerfile",
},
},
),
(
{"build": "/workspace/absolute"},
{
"build": {
"context": "/workspace/absolute",
"dockerfile": "Dockerfile",
},
},
),
(
{
"build": {
"dockerfile": "Dockerfile",
},
},
{
"build": {
"context": cwd,
"dockerfile": "Dockerfile",
},
},
),
(
{
"build": {
"context": ".",
},
},
{
"build": {
"context": cwd,
"dockerfile": "Dockerfile",
},
},
),
(
{
"build": {"context": "../", "dockerfile": "test-dockerfile"},
},
{
"build": {
"context": os.path.normpath(os.path.join(cwd, "../")),
"dockerfile": "test-dockerfile",
},
},
),
(
{
"build": {"context": ".", "dockerfile": "./dev/test-dockerfile"},
},
{
"build": {
"context": cwd,
"dockerfile": "./dev/test-dockerfile",
},
},
),
]
@parameterized.expand(cases_simple_normalization)
def test_normalize_service_final_returns_absolute_path_in_context(self, input, expected):
# Tests that [service.build] is normalized after merges
project_dir = cwd
self.assertEqual(normalize_service_final(input, project_dir), expected)
@parameterized.expand(cases_simple_normalization)
def test_normalize_returns_absolute_path_in_context(self, input, expected):
project_dir = cwd
compose_test = {"services": {"test-service": input}}
compose_expected = {"services": {"test-service": expected}}
self.assertEqual(normalize_final(compose_test, project_dir), compose_expected)
@parameterized.expand(cases_simple_normalization)
def test_parse_compose_file_when_single_compose(self, input, expected):
compose_test = {"services": {"test-service": input}}
dump_yaml(compose_test, "test-compose.yaml")
podman_compose = PodmanCompose()
set_args(podman_compose, ["test-compose.yaml"], no_normalize=None)
podman_compose._parse_compose_file()
actual_compose = {}
if podman_compose.services:
podman_compose.services["test-service"].pop("_deps")
actual_compose = podman_compose.services["test-service"]
self.assertEqual(actual_compose, expected)
@parameterized.expand([
(
{},
{"build": "."},
{"build": {"context": cwd, "dockerfile": "Dockerfile"}},
),
(
{"build": "."},
{},
{"build": {"context": cwd, "dockerfile": "Dockerfile"}},
),
(
{"build": "/workspace/absolute"},
{"build": "./relative"},
{
"build": {
"context": os.path.normpath(os.path.join(cwd, "./relative")),
"dockerfile": "Dockerfile",
}
},
),
(
{"build": "./relative"},
{"build": "/workspace/absolute"},
{"build": {"context": "/workspace/absolute", "dockerfile": "Dockerfile"}},
),
(
{"build": "./relative"},
{"build": "/workspace/absolute"},
{"build": {"context": "/workspace/absolute", "dockerfile": "Dockerfile"}},
),
(
{"build": {"dockerfile": "test-dockerfile"}},
{},
{"build": {"context": cwd, "dockerfile": "test-dockerfile"}},
),
(
{},
{"build": {"dockerfile": "test-dockerfile"}},
{"build": {"context": cwd, "dockerfile": "test-dockerfile"}},
),
(
{},
{"build": {"dockerfile": "test-dockerfile"}},
{"build": {"context": cwd, "dockerfile": "test-dockerfile"}},
),
(
{"build": {"dockerfile": "test-dockerfile-1"}},
{"build": {"dockerfile": "test-dockerfile-2"}},
{"build": {"context": cwd, "dockerfile": "test-dockerfile-2"}},
),
(
{"build": "/workspace/absolute"},
{"build": {"dockerfile": "test-dockerfile"}},
{"build": {"context": "/workspace/absolute", "dockerfile": "test-dockerfile"}},
),
(
{"build": {"dockerfile": "test-dockerfile"}},
{"build": "/workspace/absolute"},
{"build": {"context": "/workspace/absolute", "dockerfile": "test-dockerfile"}},
),
(
{"build": {"dockerfile": "./test-dockerfile-1"}},
{"build": {"dockerfile": "./test-dockerfile-2", "args": ["ENV1=1"]}},
{
"build": {
"context": cwd,
"dockerfile": "./test-dockerfile-2",
"args": ["ENV1=1"],
}
},
),
(
{"build": {"dockerfile": "./test-dockerfile-1", "args": ["ENV1=1"]}},
{"build": {"dockerfile": "./test-dockerfile-2"}},
{
"build": {
"context": cwd,
"dockerfile": "./test-dockerfile-2",
"args": ["ENV1=1"],
}
},
),
(
{"build": {"dockerfile": "./test-dockerfile-1", "args": ["ENV1=1"]}},
{"build": {"dockerfile": "./test-dockerfile-2", "args": ["ENV2=2"]}},
{
"build": {
"context": cwd,
"dockerfile": "./test-dockerfile-2",
"args": ["ENV1=1", "ENV2=2"],
}
},
),
])
def test_parse_when_multiple_composes(self, input, override, expected):
compose_test_1 = {"services": {"test-service": input}}
compose_test_2 = {"services": {"test-service": override}}
dump_yaml(compose_test_1, "test-compose-1.yaml")
dump_yaml(compose_test_2, "test-compose-2.yaml")
podman_compose = PodmanCompose()
set_args(
podman_compose,
["test-compose-1.yaml", "test-compose-2.yaml"],
no_normalize=None,
)
podman_compose._parse_compose_file()
actual_compose = {}
if podman_compose.services:
podman_compose.services["test-service"].pop("_deps")
actual_compose = podman_compose.services["test-service"]
self.assertEqual(actual_compose, expected)
def set_args(podman_compose: PodmanCompose, file_names: list[str], no_normalize: bool) -> None:
podman_compose.global_args = argparse.Namespace()
podman_compose.global_args.file = file_names
podman_compose.global_args.project_name = None
podman_compose.global_args.env_file = None
podman_compose.global_args.profile = []
podman_compose.global_args.in_pod = True
podman_compose.global_args.no_normalize = no_normalize
def dump_yaml(compose: dict, name: str) -> None:
# Path(Path.cwd()/"subdirectory").mkdir(parents=True, exist_ok=True)
with open(name, "w", encoding="utf-8") as outfile:
yaml.safe_dump(compose, outfile, default_flow_style=False)
def test_clean_test_yamls() -> None:
test_files = ["test-compose-1.yaml", "test-compose-2.yaml", "test-compose.yaml"]
for file in test_files:
if os.path.exists(file):
os.remove(file)

View File

@ -1,20 +1,20 @@
import pytest
# SPDX-License-Identifier: GPL-2.0
# pylint: disable=redefined-outer-name
import unittest
from podman_compose import parse_short_mount
@pytest.fixture
def multi_propagation_mount_str():
return "/foo/bar:/baz:U,Z"
def test_parse_short_mount_multi_propagation(multi_propagation_mount_str):
expected = {
"type": "bind",
"source": "/foo/bar",
"target": "/baz",
"bind": {
"propagation": "U,Z",
},
}
assert parse_short_mount(multi_propagation_mount_str, "/") == expected
class ParseShortMountTests(unittest.TestCase):
def test_multi_propagation(self):
self.assertEqual(
parse_short_mount("/foo/bar:/baz:U,Z", "/"),
{
"type": "bind",
"source": "/foo/bar",
"target": "/baz",
"bind": {
"propagation": "U,Z",
},
},
)

View File

@ -3,3 +3,7 @@ universal = 1
[metadata]
version = attr: podman_compose.__version__
[flake8]
# The GitHub editor is 127 chars wide
max-line-length=127

View File

@ -1,25 +1,27 @@
# SPDX-License-Identifier: GPL-2.0
import os
from setuptools import setup
try:
readme = open(os.path.join(os.path.dirname(__file__), "README.md")).read()
except:
readme = ""
README = open(os.path.join(os.path.dirname(__file__), "README.md"), encoding="utf-8").read()
except: # noqa: E722 # pylint: disable=bare-except
README = ""
setup(
name="podman-compose",
description="A script to run docker-compose.yml using podman",
long_description=readme,
long_description=README,
long_description_content_type="text/markdown",
classifiers=[
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Intended Audience :: Developers",
"Operating System :: OS Independent",
"Development Status :: 3 - Alpha",
@ -38,19 +40,10 @@ setup(
"pyyaml",
"python-dotenv",
],
extras_require={
"devel": [
"flake8",
"black",
"pylint",
"pre-commit",
]
}
extras_require={"devel": ["ruff", "pre-commit", "coverage", "parameterized"]},
# test_suite='tests',
# tests_require=[
# 'coverage',
# 'pytest-cov',
# 'pytest',
# 'tox',
# ]
)

View File

@ -1,9 +1,33 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
-e .
coverage==7.4.3
parameterized==0.9.0
pytest==8.0.2
tox==4.13.0
ruff==0.3.1
pylint==3.1.0
coverage
pytest-cov
pytest
tox
black
# The packages below are transitive dependencies of the packages above and are included here
# to make testing reproducible.
# To refresh, create a new virtualenv and do:
# pip install -r requirements.txt -r test-requirements.txt
# pip freeze > test-requirements.txt
# and edit test-requirements.txt to add this comment
astroid==3.1.0
cachetools==5.3.3
chardet==5.2.0
colorama==0.4.6
dill==0.3.8
distlib==0.3.8
filelock==3.13.1
iniconfig==2.0.0
isort==5.13.2
mccabe==0.7.0
packaging==23.2
platformdirs==4.2.0
pluggy==1.4.0
pyproject-api==1.6.1
python-dotenv==1.0.1
PyYAML==6.0.1
tomlkit==0.12.4
virtualenv==20.25.1

12
tests/__init__.py Normal file
View File

@ -0,0 +1,12 @@
import os
import subprocess
def create_base_test_image():
subprocess.check_call(
['podman', 'build', '-t', 'nopush/podman-compose-test', '.'],
cwd=os.path.join(os.path.dirname(__file__), "base_image"),
)
create_base_test_image()

View File

@ -0,0 +1,6 @@
FROM docker.io/library/debian:bookworm-slim
RUN apt-get update \
&& apt-get install -y \
dumb-init \
busybox \
wget

View File

@ -0,0 +1,22 @@
# Test podman-compose with build (fail scenario)
```shell
podman-compose build || echo $?
```
expected output would be something like
```
STEP 1/3: FROM busybox
STEP 2/3: RUN this_command_does_not_exist
/bin/sh: this_command_does_not_exist: not found
Error: building at STEP "RUN this_command_does_not_exist": while running runtime: exit status 127
exit code: 127
```
Expected `podman-compose` exit code:
```shell
echo $?
127
```

View File

@ -0,0 +1,3 @@
FROM busybox
RUN this_command_does_not_exist
CMD ["sh"]

View File

@ -0,0 +1,5 @@
version: "3"
services:
test:
build: ./context
image: build-fail-img

View File

@ -0,0 +1,9 @@
FROM busybox
RUN --mount=type=secret,required=true,id=build_secret \
ls -l /run/secrets/ && cat /run/secrets/build_secret
RUN --mount=type=secret,required=true,id=build_secret,target=/tmp/secret \
ls -l /run/secrets/ /tmp/ && cat /tmp/secret
CMD [ 'echo', 'nothing here' ]

View File

@ -0,0 +1,22 @@
version: "3.8"
services:
test:
image: test
secrets:
- run_secret # implicitly mount to /run/secrets/run_secret
- source: run_secret
target: /tmp/run_secret2 # explicit mount point
build:
context: .
secrets:
- build_secret # can be mounted in Dockerfile with "RUN --mount=type=secret,id=build_secret"
- source: build_secret
target: build_secret2 # rename to build_secret2
secrets:
build_secret:
file: ./my_secret
run_secret:
file: ./my_secret

View File

@ -0,0 +1,18 @@
version: "3.8"
services:
test:
image: test
build:
context: .
secrets:
# invalid target argument
#
# According to https://github.com/compose-spec/compose-spec/blob/master/build.md, target is
# supposed to be the "name of a *file* to be mounted in /run/secrets/". Not a path.
- source: build_secret
target: /build_secret
secrets:
build_secret:
file: ./my_secret

View File

@ -0,0 +1 @@
important-secret-is-important

View File

@ -1,4 +1,4 @@
```
podman-compose run --rm sleep /bin/sh -c 'wget -O - http://localhost:8000/hosts'
podman-compose run --rm sleep /bin/sh -c 'wget -O - http://web:8000/hosts'
```

View File

@ -1,21 +1,22 @@
version: "3.7"
services:
web:
image: busybox
command: ["/bin/busybox", "httpd", "-f", "-h", "/etc/", "-p", "8000"]
image: nopush/podman-compose-test
command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", "/etc/", "-p", "8000"]
tmpfs:
- /run
- /tmp
sleep:
image: busybox
command: ["/bin/busybox", "sh", "-c", "sleep 3600"]
depends_on: "web"
image: nopush/podman-compose-test
command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 3600"]
depends_on:
- "web"
tmpfs:
- /run
- /tmp
sleep2:
image: busybox
command: ["/bin/busybox", "sh", "-c", "sleep 3600"]
image: nopush/podman-compose-test
command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 3600"]
depends_on:
- sleep
tmpfs:

View File

@ -0,0 +1,9 @@
running the following commands should always give podman-rocks-123
```
podman-compose -f project/container-compose.yaml --env-file env-files/project-1.env up
```
```
podman-compose -f $(pwd)/project/container-compose.yaml --env-file $(pwd)/env-files/project-1.env up
```

View File

@ -0,0 +1 @@
ZZVAR1=podman-rocks-123

View File

@ -0,0 +1,9 @@
services:
app:
image: busybox
command: ["/bin/busybox", "sh", "-c", "env | grep ZZ"]
tmpfs:
- /run
- /tmp
environment:
ZZVAR1: $ZZVAR1

View File

@ -1,20 +1,20 @@
version: "3"
services:
too_long:
image: busybox
command: ["/bin/busybox", "sh", "-c", "sleep 3600; exit 0"]
image: nopush/podman-compose-test
command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 3600; exit 0"]
tmpfs:
- /run
- /tmp
sh1:
image: busybox
command: ["/bin/busybox", "sh", "-c", "sleep 5; exit 1"]
image: nopush/podman-compose-test
command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 1; exit 1"]
tmpfs:
- /run
- /tmp
sh2:
image: busybox
command: ["/bin/busybox", "sh", "-c", "sleep 5; exit 2"]
image: nopush/podman-compose-test
command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 1; exit 2"]
tmpfs:
- /run
- /tmp

View File

@ -0,0 +1,7 @@
services:
webapp_default:
webapp_special:
image: nopush/podman-compose-test
volumes:
- "/data"

View File

@ -0,0 +1,10 @@
version: "3"
services:
web:
image: nopush/podman-compose-test
extends:
file: common-services.yml
service: webapp_default
environment:
- DEBUG=1
cpu_shares: 5

View File

@ -1 +1 @@
FROM busybox as base
FROM nopush/podman-compose-test as base

View File

@ -0,0 +1,7 @@
version: '3.6'
services:
web:
image: nopush/podman-compose-test
command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", ".", "-p", "8003"]

View File

@ -0,0 +1,4 @@
version: '3.6'
include:
- docker-compose.base.yaml

View File

@ -0,0 +1,61 @@
version: "3"
networks:
shared-network:
driver: bridge
ipam:
config:
- subnet: "172.19.1.0/24"
internal-network:
driver: bridge
ipam:
config:
- subnet: "172.19.2.0/24"
services:
web1:
image: busybox
hostname: web1
command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"]
working_dir: /var/www/html
networks:
shared-network:
ipv4_address: "172.19.1.10"
x-podman.mac_address: "02:01:01:00:01:01"
internal-network:
ipv4_address: "172.19.2.10"
x-podman.mac_address: "02:01:01:00:02:01"
volumes:
- ./test1.txt:/var/www/html/index.txt:ro,z
web2:
image: busybox
hostname: web2
command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"]
working_dir: /var/www/html
mac_address: "02:01:01:00:02:02"
networks:
internal-network:
ipv4_address: "172.19.2.11"
volumes:
- ./test2.txt:/var/www/html/index.txt:ro,z
web3:
image: busybox
hostname: web2
command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"]
working_dir: /var/www/html
networks:
internal-network:
volumes:
- ./test3.txt:/var/www/html/index.txt:ro,z
web4:
image: busybox
hostname: web2
command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"]
working_dir: /var/www/html
networks:
internal-network:
shared-network:
ipv4_address: "172.19.1.13"
volumes:
- ./test4.txt:/var/www/html/index.txt:ro,z

View File

@ -0,0 +1 @@
test1

View File

@ -0,0 +1 @@
test2

View File

@ -0,0 +1 @@
test3

View File

@ -0,0 +1 @@
test4

View File

@ -0,0 +1,6 @@
version: "3"
services:
serv:
image: busybox
pid: host
command: sh -c "ps all"

View File

@ -1,18 +1,18 @@
version: "3"
services:
web1:
image: busybox
image: nopush/podman-compose-test
hostname: web1
command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"]
command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"]
working_dir: /var/www/html
ports:
- 8001:8001
volumes:
- ./test1.txt:/var/www/html/index.txt:ro,z
web2:
image: busybox
image: nopush/podman-compose-test
hostname: web2
command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8002"]
command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8002"]
working_dir: /var/www/html
ports:
- 8002:8002

View File

@ -0,0 +1,24 @@
version: "3"
services:
default-service:
image: nopush/podman-compose-test
command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", "/etc/", "-p", "8000"]
tmpfs:
- /run
- /tmp
service-1:
image: nopush/podman-compose-test
command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", "/etc/", "-p", "8000"]
tmpfs:
- /run
- /tmp
profiles:
- profile-1
service-2:
image: nopush/podman-compose-test
command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", "/etc/", "-p", "8000"]
tmpfs:
- /run
- /tmp
profiles:
- profile-2

View File

@ -1,61 +1,95 @@
# SPDX-License-Identifier: GPL-2.0
import os
import unittest
from pathlib import Path
import subprocess
from .test_utils import RunSubprocessMixin
def capture(command):
proc = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
out, err = proc.communicate()
return out, err, proc.returncode
def base_path():
"""Returns the base path for the project"""
return Path(__file__).parent.parent
def test_podman_compose_extends_w_file_subdir():
"""
Test that podman-compose can execute podman-compose -f <file> up with extended File which
includes a build context
:return:
"""
main_path = Path(__file__).parent.parent
def test_path():
"""Returns the path to the tests directory"""
return os.path.join(base_path(), "tests")
command_up = [
"python3",
str(main_path.joinpath("podman_compose.py")),
"-f",
str(main_path.joinpath("tests", "extends_w_file_subdir", "docker-compose.yml")),
"up",
"-d",
]
command_check_container = [
"podman",
"container",
"ps",
"--all",
"--format",
'"{{.Image}}"',
]
def podman_compose_path():
"""Returns the path to the podman compose script"""
return os.path.join(base_path(), "podman_compose.py")
command_down = [
"podman",
"rmi",
"--force",
"localhost/subdir_test:me",
"docker.io/library/busybox",
]
out, err, returncode = capture(command_up)
assert 0 == returncode
# check container was created and exists
out, err, returncode = capture(command_check_container)
assert 0 == returncode
assert out == b'"localhost/subdir_test:me"\n'
out, err, returncode = capture(command_down)
# cleanup test image(tags)
assert 0 == returncode
# check container did not exists anymore
out, err, returncode = capture(command_check_container)
assert 0 == returncode
assert out == b""
class TestPodmanCompose(unittest.TestCase, RunSubprocessMixin):
def test_extends_w_file_subdir(self):
"""
Test that podman-compose can execute podman-compose -f <file> up with extended File which
includes a build context
:return:
"""
main_path = Path(__file__).parent.parent
command_up = [
"coverage",
"run",
str(main_path.joinpath("podman_compose.py")),
"-f",
str(main_path.joinpath("tests", "extends_w_file_subdir", "docker-compose.yml")),
"up",
"-d",
]
command_check_container = [
"coverage",
"run",
str(main_path.joinpath("podman_compose.py")),
"-f",
str(main_path.joinpath("tests", "extends_w_file_subdir", "docker-compose.yml")),
"ps",
"--format",
'{{.Image}}',
]
self.run_subprocess_assert_returncode(command_up)
# check container was created and exists
out, _ = self.run_subprocess_assert_returncode(command_check_container)
self.assertEqual(out, b'localhost/subdir_test:me\n')
# cleanup test image(tags)
self.run_subprocess_assert_returncode([
str(main_path.joinpath("podman_compose.py")),
"-f",
str(main_path.joinpath("tests", "extends_w_file_subdir", "docker-compose.yml")),
"down",
])
self.run_subprocess_assert_returncode([
"podman",
"rmi",
"--force",
"localhost/subdir_test:me",
])
# check container did not exists anymore
out, _ = self.run_subprocess_assert_returncode(command_check_container)
self.assertEqual(out, b'')
def test_extends_w_empty_service(self):
"""
Test that podman-compose can execute podman-compose -f <file> up with extended File which
includes an empty service. (e.g. if the file is used as placeholder for more complex
configurations.)
"""
main_path = Path(__file__).parent.parent
command_up = [
"python3",
str(main_path.joinpath("podman_compose.py")),
"-f",
str(main_path.joinpath("tests", "extends_w_empty_service", "docker-compose.yml")),
"up",
"-d",
]
self.run_subprocess_assert_returncode(command_up)

View File

@ -0,0 +1,90 @@
# SPDX-License-Identifier: GPL-2.0
"""Test how secrets in files are passed to podman."""
import os
import subprocess
import unittest
from .test_podman_compose import podman_compose_path
from .test_podman_compose import test_path
def compose_yaml_path():
""" "Returns the path to the compose file used for this test module"""
return os.path.join(test_path(), "build_secrets")
class TestComposeBuildSecrets(unittest.TestCase):
def test_run_secret(self):
"""podman run should receive file secrets as --volume
See build_secrets/docker-compose.yaml for secret names and mount points (aka targets)
"""
cmd = (
"coverage",
"run",
podman_compose_path(),
"--dry-run",
"--verbose",
"-f",
os.path.join(compose_yaml_path(), "docker-compose.yaml"),
"run",
"test",
)
p = subprocess.run(
cmd, stdout=subprocess.PIPE, check=False, stderr=subprocess.STDOUT, text=True
)
self.assertEqual(p.returncode, 0)
secret_path = os.path.join(compose_yaml_path(), "my_secret")
self.assertIn(f"--volume {secret_path}:/run/secrets/run_secret:ro,rprivate,rbind", p.stdout)
self.assertIn(f"--volume {secret_path}:/tmp/run_secret2:ro,rprivate,rbind", p.stdout)
def test_build_secret(self):
"""podman build should receive secrets as --secret, so that they can be used inside the
Dockerfile in "RUN --mount=type=secret ..." commands.
"""
cmd = (
"coverage",
"run",
podman_compose_path(),
"--dry-run",
"--verbose",
"-f",
os.path.join(compose_yaml_path(), "docker-compose.yaml"),
"build",
)
p = subprocess.run(
cmd, stdout=subprocess.PIPE, check=False, stderr=subprocess.STDOUT, text=True
)
self.assertEqual(p.returncode, 0)
secret_path = os.path.join(compose_yaml_path(), "my_secret")
self.assertIn(f"--secret id=build_secret,src={secret_path}", p.stdout)
self.assertIn(f"--secret id=build_secret2,src={secret_path}", p.stdout)
def test_invalid_build_secret(self):
"""build secrets in docker-compose file can only have a target argument without directory
component
"""
cmd = (
"coverage",
"run",
podman_compose_path(),
"--dry-run",
"--verbose",
"-f",
os.path.join(compose_yaml_path(), "docker-compose.yaml.invalid"),
"build",
)
p = subprocess.run(
cmd, stdout=subprocess.PIPE, check=False, stderr=subprocess.STDOUT, text=True
)
self.assertNotEqual(p.returncode, 0)
self.assertIn(
'ValueError: ERROR: Build secret "build_secret" has invalid target "/build_secret"',
p.stdout,
)

View File

@ -0,0 +1,93 @@
# SPDX-License-Identifier: GPL-2.0
"""Test how ulimits are applied in podman-compose build."""
import os
import subprocess
import unittest
from .test_podman_compose import podman_compose_path
from .test_podman_compose import test_path
def compose_yaml_path():
""" "Returns the path to the compose file used for this test module"""
return os.path.join(test_path(), "ulimit_build")
class TestComposeBuildUlimits(unittest.TestCase):
def test_build_ulimits_ulimit1(self):
"""podman build should receive and apply limits when building service ulimit1"""
cmd = (
"coverage",
"run",
podman_compose_path(),
"--verbose",
"-f",
os.path.join(compose_yaml_path(), "docker-compose.yaml"),
"build",
"--no-cache",
"ulimit1",
)
p = subprocess.run(
cmd, stdout=subprocess.PIPE, check=False, stderr=subprocess.STDOUT, text=True
)
self.assertEqual(p.returncode, 0)
self.assertIn("--ulimit nofile=1001", p.stdout)
self.assertIn("soft nofile limit: 1001", p.stdout)
self.assertIn("hard nofile limit: 1001", p.stdout)
def test_build_ulimits_ulimit2(self):
"""podman build should receive and apply limits when building service ulimit2"""
cmd = (
"coverage",
"run",
podman_compose_path(),
"--verbose",
"-f",
os.path.join(compose_yaml_path(), "docker-compose.yaml"),
"build",
"--no-cache",
"ulimit2",
)
p = subprocess.run(
cmd, stdout=subprocess.PIPE, check=False, stderr=subprocess.STDOUT, text=True
)
self.assertEqual(p.returncode, 0)
self.assertIn("--ulimit nofile=1002", p.stdout)
self.assertIn("--ulimit nproc=1002:2002", p.stdout)
self.assertIn("soft process limit: 1002", p.stdout)
self.assertIn("hard process limit: 2002", p.stdout)
self.assertIn("soft nofile limit: 1002", p.stdout)
self.assertIn("hard nofile limit: 1002", p.stdout)
def test_build_ulimits_ulimit3(self):
"""podman build should receive and apply limits when building service ulimit3"""
cmd = (
"coverage",
"run",
podman_compose_path(),
"--verbose",
"-f",
os.path.join(compose_yaml_path(), "docker-compose.yaml"),
"build",
"--no-cache",
"ulimit3",
)
p = subprocess.run(
cmd, stdout=subprocess.PIPE, check=False, stderr=subprocess.STDOUT, text=True
)
self.assertEqual(p.returncode, 0)
self.assertIn("--ulimit nofile=1003", p.stdout)
self.assertIn("--ulimit nproc=1003:2003", p.stdout)
self.assertIn("soft process limit: 1003", p.stdout)
self.assertIn("hard process limit: 2003", p.stdout)
self.assertIn("soft nofile limit: 1003", p.stdout)
self.assertIn("hard nofile limit: 1003", p.stdout)

View File

@ -0,0 +1,82 @@
# SPDX-License-Identifier: GPL-2.0
"""
test_podman_compose_config.py
Tests the podman-compose config command which is used to return defined compose services.
"""
# pylint: disable=redefined-outer-name
import os
import unittest
from parameterized import parameterized
from .test_podman_compose import podman_compose_path
from .test_podman_compose import test_path
from .test_utils import RunSubprocessMixin
def profile_compose_file():
""" "Returns the path to the `profile` compose file used for this test module"""
return os.path.join(test_path(), "profile", "docker-compose.yml")
class TestComposeConfig(unittest.TestCase, RunSubprocessMixin):
def test_config_no_profiles(self):
"""
Tests podman-compose config command without profile enablement.
"""
config_cmd = [
"coverage",
"run",
podman_compose_path(),
"-f",
profile_compose_file(),
"config",
]
out, _ = self.run_subprocess_assert_returncode(config_cmd)
string_output = out.decode("utf-8")
self.assertIn("default-service", string_output)
self.assertNotIn("service-1", string_output)
self.assertNotIn("service-2", string_output)
@parameterized.expand(
[
(
["--profile", "profile-1", "config"],
{"default-service": True, "service-1": True, "service-2": False},
),
(
["--profile", "profile-2", "config"],
{"default-service": True, "service-1": False, "service-2": True},
),
(
["--profile", "profile-1", "--profile", "profile-2", "config"],
{"default-service": True, "service-1": True, "service-2": True},
),
],
)
def test_config_profiles(self, profiles, expected_services):
"""
Tests podman-compose
:param profiles: The enabled profiles for the parameterized test.
:param expected_services: Dictionary used to model the expected "enabled" services in the
profile. Key = service name, Value = True if the service is enabled, otherwise False.
"""
config_cmd = ["coverage", "run", podman_compose_path(), "-f", profile_compose_file()]
config_cmd.extend(profiles)
out, _ = self.run_subprocess_assert_returncode(config_cmd)
actual_output = out.decode("utf-8")
self.assertEqual(len(expected_services), 3)
actual_services = {}
for service, _ in expected_services.items():
actual_services[service] = service in actual_output
self.assertEqual(expected_services, actual_services)

View File

@ -0,0 +1,62 @@
# SPDX-License-Identifier: GPL-2.0
import unittest
from pathlib import Path
from .test_utils import RunSubprocessMixin
class TestPodmanComposeInclude(unittest.TestCase, RunSubprocessMixin):
def test_podman_compose_include(self):
"""
Test that podman-compose can execute podman-compose -f <file> up with include
:return:
"""
main_path = Path(__file__).parent.parent
command_up = [
"coverage",
"run",
str(main_path.joinpath("podman_compose.py")),
"-f",
str(main_path.joinpath("tests", "include", "docker-compose.yaml")),
"up",
"-d",
]
command_check_container = [
"podman",
"ps",
"-a",
"--filter",
"label=io.podman.compose.project=include",
"--format",
'"{{.Image}}"',
]
command_container_id = [
"podman",
"ps",
"-a",
"--filter",
"label=io.podman.compose.project=include",
"--format",
'"{{.ID}}"',
]
command_down = ["podman", "rm", "--force", "CONTAINER_ID"]
self.run_subprocess_assert_returncode(command_up)
out, _ = self.run_subprocess_assert_returncode(command_check_container)
self.assertEqual(out, b'"localhost/nopush/podman-compose-test:latest"\n')
# Get container ID to remove it
out, _ = self.run_subprocess_assert_returncode(command_container_id)
self.assertNotEqual(out, b"")
container_id = out.decode().strip().replace('"', "")
command_down[3] = container_id
out, _ = self.run_subprocess_assert_returncode(command_down)
# cleanup test image(tags)
self.assertNotEqual(out, b"")
# check container did not exists anymore
out, _ = self.run_subprocess_assert_returncode(command_check_container)
self.assertEqual(out, b"")

View File

@ -0,0 +1,116 @@
# SPDX-License-Identifier: GPL-2.0
"""
test_podman_compose_networks.py
Tests the podman networking parameters
"""
# pylint: disable=redefined-outer-name
import os
import unittest
from .test_podman_compose import podman_compose_path
from .test_podman_compose import test_path
from .test_utils import RunSubprocessMixin
class TestPodmanComposeNetwork(RunSubprocessMixin, unittest.TestCase):
@staticmethod
def compose_file():
"""Returns the path to the compose file used for this test module"""
return os.path.join(test_path(), "nets_test_ip", "docker-compose.yml")
def teardown(self):
"""
Ensures that the services within the "profile compose file" are removed between
each test case.
"""
# run the test case
yield
down_cmd = [
"coverage",
"run",
podman_compose_path(),
"-f",
self.compose_file(),
"kill",
"-a",
]
self.run_subprocess(down_cmd)
def test_networks(self):
up_cmd = [
"coverage",
"run",
podman_compose_path(),
"-f",
self.compose_file(),
"up",
"-d",
"--force-recreate",
]
self.run_subprocess_assert_returncode(up_cmd)
check_cmd = [
podman_compose_path(),
"-f",
self.compose_file(),
"ps",
"--format",
'"{{.Names}}"',
]
out, _ = self.run_subprocess_assert_returncode(check_cmd)
self.assertIn(b"nets_test_ip_web1_1", out)
self.assertIn(b"nets_test_ip_web2_1", out)
expected_wget = {
"172.19.1.10": "test1",
"172.19.2.10": "test1",
"172.19.2.11": "test2",
"web3": "test3",
"172.19.1.13": "test4",
}
for service in ("web1", "web2"):
for ip, expect in expected_wget.items():
wget_cmd = [
podman_compose_path(),
"-f",
self.compose_file(),
"exec",
service,
"wget",
"-q",
"-O-",
f"http://{ip}:8001/index.txt",
]
out, _ = self.run_subprocess_assert_returncode(wget_cmd)
self.assertEqual(f"{expect}\r\n", out.decode('utf-8'))
expected_macip = {
"web1": {
"eth0": ["172.19.1.10", "02:01:01:00:01:01"],
"eth1": ["172.19.2.10", "02:01:01:00:02:01"],
},
"web2": {"eth0": ["172.19.2.11", "02:01:01:00:02:02"]},
}
for service, interfaces in expected_macip.items():
ip_cmd = [
podman_compose_path(),
"-f",
self.compose_file(),
"exec",
service,
"ip",
"addr",
"show",
]
out, _ = self.run_subprocess_assert_returncode(ip_cmd)
for interface, values in interfaces.items():
ip, mac = values
self.assertIn(f"ether {mac}", out.decode('utf-8'))
self.assertIn(f"inet {ip}/", out.decode('utf-8'))

View File

@ -0,0 +1,189 @@
# SPDX-License-Identifier: GPL-2.0
"""
test_podman_compose_up_down.py
Tests the podman compose up and down commands used to create and remove services.
"""
# pylint: disable=redefined-outer-name
import os
import unittest
from .test_podman_compose import podman_compose_path
from .test_podman_compose import test_path
from .test_utils import RunSubprocessMixin
class TestPodmanCompose(unittest.TestCase, RunSubprocessMixin):
def test_exit_from(self):
up_cmd = [
"coverage",
"run",
podman_compose_path(),
"-f",
os.path.join(test_path(), "exit-from", "docker-compose.yaml"),
"up",
]
self.run_subprocess_assert_returncode(up_cmd + ["--exit-code-from", "sh1"], 1)
self.run_subprocess_assert_returncode(up_cmd + ["--exit-code-from", "sh2"], 2)
def test_run(self):
"""
This will test depends_on as well
"""
run_cmd = [
"coverage",
"run",
podman_compose_path(),
"-f",
os.path.join(test_path(), "deps", "docker-compose.yaml"),
"run",
"--rm",
"sleep",
"/bin/sh",
"-c",
"wget -q -O - http://web:8000/hosts",
]
out, _ = self.run_subprocess_assert_returncode(run_cmd)
self.assertIn(b'127.0.0.1\tlocalhost', out)
# Run it again to make sure we can run it twice. I saw an issue where a second run, with
# the container left up, would fail
run_cmd = [
"coverage",
"run",
podman_compose_path(),
"-f",
os.path.join(test_path(), "deps", "docker-compose.yaml"),
"run",
"--rm",
"sleep",
"/bin/sh",
"-c",
"wget -q -O - http://web:8000/hosts",
]
out, _ = self.run_subprocess_assert_returncode(run_cmd)
self.assertIn(b'127.0.0.1\tlocalhost', out)
# This leaves a container running. Not sure it's intended, but it matches docker-compose
down_cmd = [
"coverage",
"run",
podman_compose_path(),
"-f",
os.path.join(test_path(), "deps", "docker-compose.yaml"),
"down",
]
self.run_subprocess_assert_returncode(down_cmd)
def test_up_with_ports(self):
up_cmd = [
"coverage",
"run",
podman_compose_path(),
"-f",
os.path.join(test_path(), "ports", "docker-compose.yml"),
"up",
"-d",
"--force-recreate",
]
down_cmd = [
"coverage",
"run",
podman_compose_path(),
"-f",
os.path.join(test_path(), "ports", "docker-compose.yml"),
"down",
"--volumes",
]
try:
self.run_subprocess_assert_returncode(up_cmd)
finally:
self.run_subprocess_assert_returncode(down_cmd)
def test_down_with_vols(self):
up_cmd = [
"coverage",
"run",
podman_compose_path(),
"-f",
os.path.join(test_path(), "vol", "docker-compose.yaml"),
"up",
"-d",
]
down_cmd = [
"coverage",
"run",
podman_compose_path(),
"-f",
os.path.join(test_path(), "vol", "docker-compose.yaml"),
"down",
"--volumes",
]
try:
self.run_subprocess_assert_returncode(["podman", "volume", "create", "my-app-data"])
self.run_subprocess_assert_returncode([
"podman",
"volume",
"create",
"actual-name-of-volume",
])
self.run_subprocess_assert_returncode(up_cmd)
self.run_subprocess(["podman", "inspect", "volume", ""])
finally:
out, _, return_code = self.run_subprocess(down_cmd)
self.run_subprocess(["podman", "volume", "rm", "my-app-data"])
self.run_subprocess(["podman", "volume", "rm", "actual-name-of-volume"])
self.assertEqual(return_code, 0)
def test_down_with_orphans(self):
container_id, _ = self.run_subprocess_assert_returncode([
"podman",
"run",
"--rm",
"-d",
"nopush/podman-compose-test",
"dumb-init",
"/bin/busybox",
"httpd",
"-f",
"-h",
"/etc/",
"-p",
"8000",
])
down_cmd = [
"coverage",
"run",
podman_compose_path(),
"-f",
os.path.join(test_path(), "ports", "docker-compose.yml"),
"down",
"--volumes",
"--remove-orphans",
]
self.run_subprocess_assert_returncode(down_cmd)
self.run_subprocess_assert_returncode(
[
"podman",
"container",
"exists",
container_id.decode("utf-8"),
],
1,
)

View File

@ -0,0 +1,91 @@
# SPDX-License-Identifier: GPL-2.0
"""
test_podman_compose_up_down.py
Tests the podman compose up and down commands used to create and remove services.
"""
# pylint: disable=redefined-outer-name
import os
import unittest
from parameterized import parameterized
from .test_podman_compose import podman_compose_path
from .test_podman_compose import test_path
from .test_utils import RunSubprocessMixin
def profile_compose_file():
""" "Returns the path to the `profile` compose file used for this test module"""
return os.path.join(test_path(), "profile", "docker-compose.yml")
class TestUpDown(unittest.TestCase, RunSubprocessMixin):
def tearDown(self):
"""
Ensures that the services within the "profile compose file" are removed between each test
case.
"""
# run the test case
down_cmd = [
"coverage",
"run",
podman_compose_path(),
"--profile",
"profile-1",
"--profile",
"profile-2",
"-f",
profile_compose_file(),
"down",
]
self.run_subprocess(down_cmd)
@parameterized.expand(
[
(
["--profile", "profile-1", "up", "-d"],
{"default-service": True, "service-1": True, "service-2": False},
),
(
["--profile", "profile-2", "up", "-d"],
{"default-service": True, "service-1": False, "service-2": True},
),
(
["--profile", "profile-1", "--profile", "profile-2", "up", "-d"],
{"default-service": True, "service-1": True, "service-2": True},
),
],
)
def test_up(self, profiles, expected_services):
up_cmd = [
"coverage",
"run",
podman_compose_path(),
"-f",
profile_compose_file(),
]
up_cmd.extend(profiles)
self.run_subprocess_assert_returncode(up_cmd)
check_cmd = [
"podman",
"container",
"ps",
"--format",
'"{{.Names}}"',
]
out, _ = self.run_subprocess_assert_returncode(check_cmd)
self.assertEqual(len(expected_services), 3)
actual_output = out.decode("utf-8")
actual_services = {}
for service, _ in expected_services.items():
actual_services[service] = service in actual_output
self.assertEqual(expected_services, actual_services)

38
tests/test_utils.py Normal file
View File

@ -0,0 +1,38 @@
# SPDX-License-Identifier: GPL-2.0
import os
import subprocess
import time
class RunSubprocessMixin:
def is_debug_enabled(self):
return "TESTS_DEBUG" in os.environ
def run_subprocess(self, args):
begin = time.time()
if self.is_debug_enabled():
print("TEST_CALL", args)
proc = subprocess.Popen(
args,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
out, err = proc.communicate()
if self.is_debug_enabled():
print("TEST_CALL completed", time.time() - begin)
print("STDOUT:", out.decode('utf-8'))
print("STDERR:", err.decode('utf-8'))
return out, err, proc.returncode
def run_subprocess_assert_returncode(self, args, expected_returncode=0):
out, err, returncode = self.run_subprocess(args)
decoded_out = out.decode('utf-8')
decoded_err = err.decode('utf-8')
self.assertEqual(
returncode,
expected_returncode,
f"Invalid return code of process {returncode} != {expected_returncode}\n"
f"stdout: {decoded_out}\nstderr: {decoded_err}\n",
)
return out, err

View File

@ -0,0 +1,15 @@
version: "3.7"
services:
touch:
image: busybox
command: 'touch /mnt/test'
volumes:
- ./:/mnt
user: 999:999
x-podman:
uidmaps:
- "0:1:1"
- "999:0:1"
gidmaps:
- "0:1:1"
- "999:0:1"

View File

@ -0,0 +1,5 @@
FROM busybox
COPY ./ulimit.sh /bin/ulimit.sh
RUN /bin/ulimit.sh

View File

@ -0,0 +1,26 @@
version: "3"
services:
ulimit1:
image: ulimit_build_test
build:
context: ./
dockerfile: Dockerfile
ulimits: nofile=1001
ulimit2:
image: ulimit_build_test
build:
context: ./
dockerfile: Dockerfile
ulimits:
- nproc=1002:2002
- nofile=1002
ulimit3:
image: ulimit_build_test
build:
context: ./
dockerfile: Dockerfile
ulimits:
nofile: 1003
nproc:
soft: 1003
hard: 2003

6
tests/ulimit_build/ulimit.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/sh
echo "soft process limit:" $(ulimit -S -u)
echo "hard process limit:" $(ulimit -H -u)
echo "soft nofile limit:" $(ulimit -S -n)
echo "hard nofile limit:" $(ulimit -H -n)

View File

@ -1,8 +1,8 @@
version: "3"
services:
web:
image: busybox
command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8000"]
image: nopush/podman-compose-test
command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8000"]
working_dir: /var/www/html
restart: always
volumes:
@ -11,21 +11,21 @@ services:
- /run
- /tmp
web1:
image: busybox
command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"]
image: nopush/podman-compose-test
command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"]
restart: unless-stopped
working_dir: /var/www/html
volumes:
- myvol1:/var/www/html:ro,z
web2:
image: busybox
command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8002"]
image: nopush/podman-compose-test
command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8002"]
working_dir: /var/www/html
volumes:
- myvol2:/var/www/html:ro
web3:
image: busybox
command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8003"]
image: nopush/podman-compose-test
command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8003"]
working_dir: /var/www/html
volumes:
- myvol2:/var/www/html

View File

@ -0,0 +1,7 @@
version: "3"
services:
web:
volumes:
- ./override.txt:/var/www/html/index.html:ro,z
- ./override.txt:/var/www/html/index2.html:z
- ./override.txt:/var/www/html/index3.html

View File

@ -0,0 +1,11 @@
version: "3"
services:
web:
image: busybox
command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8080"]
ports:
- 8080:8080
volumes:
- ./index.txt:/var/www/html/index.html:ro,z
- ./index.txt:/var/www/html/index2.html
- ./index.txt:/var/www/html/index3.html:ro

View File

@ -0,0 +1 @@
The file from docker-compose.yaml

View File

@ -0,0 +1 @@
The file from docker-compose.override.yaml