245 Commits

Author SHA1 Message Date
d38b26bb01 Release 1.2.0
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-06-26 10:43:28 +03:00
22a4ad5806 Merge pull request #975 from p12tic/changelog
Add release notes for v1.2.0
2024-06-26 10:41:59 +03:00
37e2cb28d4 Add release notes for v1.2.0
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-06-26 10:39:48 +03:00
0cd3902c5f Merge pull request #974 from p12tic/newsfragments
Use newsfragments pattern for the release notes
2024-06-26 10:37:54 +03:00
6ef759c6fd Use newsfragments pattern for the release notes
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-06-26 10:21:03 +03:00
16cbcf4152 Merge pull request #956 from Genzer/fix/loading-.env-breaking-since-1.1.0
Load .env from Compose file's directory and cwd
2024-06-24 23:37:33 +03:00
67ce900885 Commit .env in tests/env-file-tests, bypassing root .gitignore
This commit adds a .gitignore in tests/env-file-tests to allow .env files
to be committed.

Fix: #937
Signed-off-by: Genzer <732937+Genzer@users.noreply.github.com>
2024-06-24 23:29:57 +03:00
4e9f76768c Load .env from Compose file's directory and cwd
This commit loads dotenv `.env` (exactly that name) from the following location (the later takes
precedence):

- The `.env` file in the Compose file's directory.
- The `.env` file in the current working directory (invoking podman-compose).

This preserves the behavior prior to 1.1.0 and to match with Docker Compose CLI.

Fix: https://github.com/containers/podman-compose/issues/937
Signed-off-by: Genzer <732937+Genzer@users.noreply.github.com>
2024-06-24 23:29:56 +03:00
84f7fdd7da Merge pull request #971 from mokibit/type-env-secret-support
Add support for environment variable secrets
2024-06-24 23:17:34 +03:00
405001b990 Fix comment
Signed-off-by: Monika Kairaityte <monika@kibit.lt>
2024-06-24 21:24:34 +03:00
6b1aeff55f Add unittests for type=env secret
Signed-off-by: Monika Kairaityte <monika@kibit.lt>
2024-06-24 21:24:34 +03:00
f06975b346 Update tests for type=env secret
Signed-off-by: Brett Calliss <brett@obligatory.email>
2024-06-24 20:12:44 +03:00
546cad5171 Add type=env secret support
Signed-off-by: Brett Calliss <brett@obligatory.email>
2024-06-24 20:12:44 +03:00
e07c28d127 Merge pull request #771 from wgnathanael/environment-precedence
Fix environment variable precedents
2024-06-22 20:00:08 +03:00
935029dc33 Fix environment variable precedents
Per https://docs.docker.com/compose/environment-variables/envvars-precedence/#advanced-example

Signed-off-by: nathanael.noblet <nathanael.noblet@willowglensystems.com>
2024-06-22 19:58:02 +03:00
80b2aa6ed0 Merge pull request #964 from mokibit/set-custom-in_pod-in-compose-file
Allow providing custom in_pod argument as a global compose file variable
2024-06-20 09:45:41 +03:00
360b85bf2d Allow providing custom in_pod argument as a global compose file variable
Default command line argument `in_pod` was set to True, but this breaks
the compose file for users who want to use `--userns` argument. This
commit sets default `in_pod` value to None, and later resolves whether
to create a pod by checking compose file, as new argument in compose
file x-podman is now available. Now it is convenient for users to pass
custom `in_pod` value (True or False) as a compose file argument when
command line value of `in_pod` is not provided.

Signed-off-by: Monika Kairaityte <monika@kibit.lt>
2024-06-20 09:42:22 +03:00
650a835eca Merge pull request #966 from ArthoPacini/enhance/stdin-docker-compose-support
Add ability to input docker-compose.yaml via stdin
2024-06-19 20:06:15 +03:00
82740cc311 Add ability to input docker-compose.yaml via stdin
Signed-off-by: Artho Pacini <eu@arthopacini.com>
2024-06-18 00:05:31 +03:00
0f645e4c70 Add ability to input docker-compose.yaml via stdin
Signed-off-by: Artho Pacini <eu@arthopacini.com>
2024-06-18 00:05:31 +03:00
3b15170ccf Changed the global parser help message for file input, to reflect changes for reading from stdin
Signed-off-by: Artho Pacini <eu@arthopacini.com>
2024-06-18 00:05:31 +03:00
3359380ec6 Add ability to input docker-compose.yaml via stdin
Signed-off-by: Artho Pacini <eu@arthopacini.com>
2024-06-18 00:05:31 +03:00
14f39e5b86 Merge pull request #965 from mokibit/fix-gidmap-typo
Fix x-podman.gidmap typo
2024-06-17 10:08:17 +03:00
e799a0b0ea Fix x-podman.gidmap typo
Signed-off-by: Monika Kairaityte <monika@kibit.lt>
2024-06-14 17:31:55 +03:00
6d8d3e94fe Merge pull request #960 from mokibit/github-verbose-integration-tests
github: Add verbose option to integration tests
2024-06-08 20:15:13 +03:00
65d1fdeaa3 github: Add verbose option to integration tests
Currently it is not possible to see which test output corresponds to
which test exactly. Now before each new test its test name is printed.

Signed-off-by: Monika Kairaityte <monika@kibit.lt>
2024-06-08 20:05:12 +03:00
d905a7c638 Merge pull request #949 from mokibit/multiline-env-file
Add support for multi-line environment files
2024-05-29 00:47:58 +03:00
2e8ed2f924 pytests: Add test for object required but path non existent
Signed-off-by: Monika Kairaityte <monika@kibit.lt>
2024-05-29 00:09:22 +03:00
040b73adab pytests: Add tests for several multi-line environment files
Signed-off-by: Monika Kairaityte <monika@kibit.lt>
2024-05-29 00:09:15 +03:00
f3e9a96c96 Fixes #908: Add support for multi-line environment files
Signed-off-by: Hedayat Vatankhah <hedayat.fwd@gmail.com>
2024-05-28 23:43:15 +03:00
04b107805a Merge pull request #954 from mokibit/fix-codespelling-update
github/workflows: Fix automatic codespelling update
2024-05-28 23:40:59 +03:00
2c5d00d3e7 github/workflows: Add codespellignore file to address false positives
Signed-off-by: Monika Kairaityte <monika@kibit.lt>
2024-05-28 20:20:33 +03:00
cac90f69b8 Merge pull request #946 from charliemirabile/selinux_tests
Missing SELinux tests
2024-05-22 10:17:05 +03:00
b513f50f30 test: add missing unit tests for selinux in verbose mount
Support for setting the selinux flags on a bind mount specified using
the verbose syntax was merged as part of #911, but at that time the PR
lacked unit tests. This commit adds the missing tests

Signed-off-by: charliemirabile <46761267+charliemirabile@users.noreply.github.com>
2024-05-21 19:43:03 -04:00
8f618b6fab Merge pull request #763 from otto-liljalaakso-nt/additional_contexts
Support additional_contexts
2024-05-21 19:49:23 +03:00
cac836b0f5 Support additional_contexts
Signed-off-by: Otto Liljalaakso <otto.liljalaakso@novatron.fi>
2024-05-21 19:44:37 +03:00
3bb305cef4 Merge pull request #945 from p12tic/split-gpu-test
test: Split test_gpu test
2024-05-21 12:25:11 +03:00
09034a0c38 test: Split test_gpu test
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-05-21 12:04:32 +03:00
e668a339ce Merge pull request #943 from HernandoR/fix/multi-sub-composes
Fix the test_include for multi subcomposes
2024-05-21 11:32:17 +03:00
0065082db9 refine the test_include for multi subcomposes
Signed-off-by: Zhen Liu <lzhen.dev@outlook.com>
2024-05-21 11:28:27 +03:00
5d4de80ab7 Merge pull request #911 from charliemirabile/selinux
Add support for selinux in verbose bind mount specification
2024-05-19 11:13:45 +03:00
23ad5c3ef7 Merge pull request #920 from mokeyish/gpu
Add supoort for enabling GPU access
2024-05-19 11:12:34 +03:00
45efe461b0 Merge pull request #941 from HernandoR/patch-1
Update podman_compose.py
2024-05-19 11:10:39 +03:00
4f73f2b79e fix: add include test file, edit the assertion
Signed-off-by: Zhen Liu <lzhen.dev@outlook.com>
2024-05-18 21:23:43 +08:00
1d64f2cf8c Update podman_compose.py
fix #940

Signed-off-by: Zhen Liu <lzhen.dev@outlook.com>
2024-05-18 20:33:10 +08:00
2ce6d1a1e7 Merge pull request #933 from hedayat/fix-build-error-log
Fix logging build error message
2024-05-13 16:24:15 +03:00
4e22faefd6 Fix logging build error message
Signed-off-by: Hedayat Vatankhah <hedayat.fwd@gmail.com>
2024-05-13 15:13:40 +03:00
7a2da76ab8 Merge pull request #724 from hedayat/fix-merge-depends-on
Fixes #723: merge short & long syntax of depends_on dependencies
2024-05-08 17:57:44 +03:00
79865c2e13 Add support for enabling GPU access
Signed-off-by: YISH <mokeyish@hotmail.com>
2024-05-07 10:32:24 +08:00
33d7d35a4d Merge pull request #851 from fccagou/fix-ipam-driver-default
fix(ipam_driver): do not pass --ipam-driver option when value set to …
2024-05-06 17:31:59 +03:00
c23a8b2cbd Do not pass --ipam-driver option when value set to default
fixes #852.

Signed-off-by: fccagou <me@fccagou.fr>
2024-05-06 17:00:17 +03:00
36a3d3c207 Merge pull request #925 from GerkinDev/feat/env-file-object
Fixes #897: support `env_file` as objects
2024-05-06 16:57:25 +03:00
b202a09501 Add support for env_file as objects
Fixes: https://github.com/containers/podman-compose/issues/897

Signed-off-by: Alexandre Germain <nihilivin@gmail.com>
2024-05-06 14:13:37 +03:00
35cbc49160 Merge pull request #928 from schugabe/patch-1
add await for create_pods call
2024-05-06 14:00:29 +03:00
5c4aa40032 add await for create_pods call
fixes log output podman_compose.py:2527: RuntimeWarning: coroutine 'create_pods' was never awaited

Signed-off-by: Johannes Kasberger <schugabe@gmx.at>
2024-05-06 11:02:45 +02:00
0ee7c2630a Merge pull request #641 from DaniruKun/devel
Add instructions on install from Homebrew
2024-05-04 18:03:43 +03:00
cef1785cd5 Add instructions on install from Homebrew
Signed-off-by: Daniils Petrovs <thedanpetrov@gmail.com>
2024-05-04 17:50:06 +03:00
b4cfef12e9 Merge pull request #926 from p12tic/cleanup-tests
Cleanup tests
2024-05-04 17:32:00 +03:00
b761050b0b tests: Merge multiple compose merging tests into single test class
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-05-04 17:30:09 +03:00
e1d0ea7b4e tests: Move normalize_service tests to a separate test class
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-05-04 17:29:04 +03:00
1430578568 tests: Simplify command and entrypoint normalization tests
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-05-04 17:28:59 +03:00
8f41cd3cdb Merge pull request #731 from g2p/patch-1
README: explain that netavark is an alternative to the dnsname plugin
2024-05-01 21:16:27 +03:00
a73dac2e39 Merge pull request #923 from winston-yallow/remove-sideeffect-from-systemd-registration
Don't create pods/container when registering systemd unit
2024-05-01 21:05:13 +03:00
d31a8b124d Don't create pods/container when registering systemd unit
Signed-off-by: Winston <44872771+winston-yallow@users.noreply.github.com>
2024-05-01 20:03:14 +02:00
5df4e786ee README: explain that netavark is an alternative to the dnsname plugin
This is a useful hint when the dnsname plugin is not packaged.

Signed-off-by: Gabriel de Perthuis <g2p.code@gmail.com>
2024-05-01 15:13:29 +02:00
27e27e9fe9 Merge pull request #918 from p12tic/fix-in-pod
Fix handling of --in-pod argument
2024-04-28 21:33:50 +03:00
70a0e2d003 Fix handling of --in-pod argument
Currently --in-pod handling is broken because the only way to set False
is by providing empty argument like "--in-pod=". As of Python 3.7 the
solution is to accept string and parse manually.

Co-authored-by: Randolph Sapp <res.sapp@gmail.com>
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-04-28 21:16:34 +03:00
58641f0545 Merge pull request #716 from Tayeh/images_cmd
add `podman-compose images` command
2024-04-28 20:37:07 +03:00
eea8bac496 Add images command
Signed-off-by: Mohammed Tayeh <m.tayeh94@gmail.com>
2024-04-28 19:04:23 +03:00
09a8a3edf9 Merge pull request #917 from p12tic/x-podman-keys
Migrate x-podman dictionary on container root to x-podman.* fields
2024-04-28 18:29:33 +03:00
a6c4263738 Add tests for x-podman.uidmaps and x-podman.gidmaps
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-04-28 18:25:04 +03:00
9599cc039e Migrate x-podman dictionary to x-podman.* fields in container root
Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
2024-04-28 18:24:37 +03:00
0a6c057486 Merge pull request #737 from ftyghome/feat_rootfs
Support podman's external rootfs management
2024-04-28 18:04:37 +03:00
2b4ecee082 Add docs for podman specific compose file extensions
Signed-off-by: GnSight <ftyg@live.com>
2024-04-28 17:55:39 +03:00
77f2e8e5b0 Support podman's external rootfs management
Signed-off-by: GnSight <ftyg@live.com>
2024-04-28 17:55:39 +03:00
12d46ca836 Merge pull request #916 from beledouxdenis/main-run-implement-publish
implement --publish in docker-compose run
2024-04-28 17:33:56 +03:00
72a94d5185 implement --publish in docker-compose run
Signed-off-by: Denis Ledoux <dle@odoo.com>
2024-04-28 00:54:35 +02:00
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
3e1f7d554b add tests for selinux with verbose bind mount
based on seccomp test. Without the selinux option, visiting localhost:8080
will give a 404 error because httpd cannot access the file, but with selinux: z
the context for the file will be appropriately updated so httpd can access it

Signed-off-by: charliemirabile <46761267+charliemirabile@users.noreply.github.com>
2024-04-10 15:29:03 -04:00
d7cf0966d3 add support for selinux in verbose mount
This corresponds to specifying the `z` or `Z` option in the third
portion of a terse mount specification (i.e. src:trg:z)

Signed-off-by: charliemirabile <46761267+charliemirabile@users.noreply.github.com>
2024-04-10 15:29:03 -04: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
1f35c00694 Add unit test for depends_on normalization as a dict
Signed-off-by: Hedayat Vatankhah <hedayat.fwd@gmail.com>
2024-04-08 23:31:41 +03:30
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
c31b4e2816 Fixes #723: merge short & long syntax of depends_on dependencies
Signed-off-by: Hedayat Vatankhah <hedayat.fwd@gmail.com>
2023-07-08 04:10:43 +03:30
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
102 changed files with 5218 additions and 1070 deletions

1
.codespellignore Normal file
View File

@ -0,0 +1 @@
assertIn

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

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

@ -0,0 +1,10 @@
## 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.
For any user-visible change please add a release note to newsfragments directory, e.g.
newsfragments/my_feature.feature. See newsfragments/README.md for more details.
All changes require additional unit tests.

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

@ -0,0 +1,22 @@
---
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
with:
ignore_words_file: .codespellignore

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: |
sudo apt-get update
sudo 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 -v 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

5
.gitignore vendored
View File

@ -47,6 +47,8 @@ coverage.xml
*.cover
.hypothesis/
.pytest_cache/
test-compose.yaml
test-compose-?.yaml
# Translations
*.mo
@ -103,3 +105,6 @@ venv.bak/
# mypy
.mypy_cache/
.vscode

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:
@ -11,7 +10,11 @@ This project focuses on:
This project only depends on:
* `podman`
* [podman dnsname plugin](https://github.com/containers/dnsname): It is usually found in the `podman-plugins` or `podman-dnsname` distro packages, those packages are not pulled by default and you need to install them. This allows containers to be able to resolve each other if they are on the same CNI network.
* [podman dnsname plugin](https://github.com/containers/dnsname): It is usually found in
the `podman-plugins` or `podman-dnsname` distro packages, those packages are not pulled
by default and you need to install them. This allows containers to be able to resolve
each other if they are on the same CNI network. This is not necessary when podman is using
netavark as a network backend.
* Python3
* [PyYAML](https://pyyaml.org/)
* [python-dotenv](https://pypi.org/project/python-dotenv/)
@ -49,9 +52,11 @@ like `hostnet`. If you desire that behavior, pass it the standard way like `netw
## Installation
### Pip
Install the latest stable version from PyPI:
```
```bash
pip3 install podman-compose
```
@ -59,14 +64,33 @@ pass `--user` to install inside regular user home without being root.
Or latest development version from GitHub:
```
pip3 install https://github.com/containers/podman-compose/archive/devel.tar.gz
```bash
pip3 install https://github.com/containers/podman-compose/archive/main.tar.gz
```
### Homebrew
```bash
brew install podman-compose
```
### Manual
```bash
curl -o /usr/local/bin/podman-compose https://raw.githubusercontent.com/containers/podman-compose/main/podman_compose.py
chmod +x /usr/local/bin/podman-compose
```
or inside your home
```bash
curl -o ~/.local/bin/podman-compose https://raw.githubusercontent.com/containers/podman-compose/main/podman_compose.py
chmod +x ~/.local/bin/podman-compose
```
or install from Fedora (starting from f31) repositories:
```
```bash
sudo dnf install podman-compose
```
@ -75,10 +99,9 @@ sudo dnf install podman-compose
We have included fully functional sample stacks inside `examples/` directory.
You can get more examples from [awesome-compose](https://github.com/docker/awesome-compose).
A quick example would be
```
```bash
cd examples/busybox
podman-compose --help
podman-compose up --help
@ -103,11 +126,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.

40
docs/Changelog-1.2.0.md Normal file
View File

@ -0,0 +1,40 @@
Version v1.2.0 (2024-06-26)
===========================
Bug fixes
---------
- Fixed handling of `--in-pod` argument. Previously it was hard to provide false value to it.
- podman-compose no longer creates pods when registering systemd unit.
- Fixed warning `RuntimeWarning: coroutine 'create_pods' was never awaited`
- Fixed error when setting up IPAM network with default driver.
- Fixed support for having list and dictionary `depends_on` sections in related compose files.
- Fixed logging of failed build message.
- Fixed support for multiple entries in `include` section.
- Fixed environment variable precedence order.
Changes
-------
- `x-podman` dictionary in container root has been migrated to `x-podman.*` fields in container root.
New features
------------
- Added support for `--publish` in `podman-compose run`.
- Added support for Podman external root filesystem management (`--rootfs` option).
- Added support for `podman-compose images` command.
- Added support for `env_file` being configured via dictionaries.
- Added support for enabling GPU access.
- Added support for selinux in verbose mount specification.
- Added support for `additional_contexts` section.
- Added support for multi-line environment files.
- Added support for passing contents of `podman-compose.yml` via stdin.
- Added support for specifying the value for `--in-pod` setting in `podman-compose.yml` file.
- Added support for environmental secrets.
Documentation
-------------
- Added instructions on how to install podman-compose on Homebrew.
- Added explanation that netavark is an alternative to dnsname plugin

111
docs/Extensions.md Normal file
View File

@ -0,0 +1,111 @@
# Podman specific extensions to the docker-compose format
Podman-compose supports the following extension to the docker-compose format. These extensions
are generally specified under fields with "x-podman" prefix in the compose file.
## Container management
The following extension keys are available under container configuration:
* `x-podman.uidmap` - Run the container in a new user namespace using the supplied UID mapping.
* `x-podman.gidmap` - Run the container in a new user namespace using the supplied GID mapping.
* `x-podman.rootfs` - Run the container without requiring any image management; the rootfs of the
container is assumed to be managed externally.
For example, the following docker-compose.yml allows running a podman container with externally managed rootfs.
```yml
version: "3"
services:
my_service:
command: ["/bin/busybox"]
x-podman.rootfs: "/path/to/rootfs"
```
For explanations of these extensions, please refer to the [Podman Documentation](https://docs.podman.io/).
## 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.
## Custom pods management
Podman-compose can have containers in pods. This can be controlled by extension key x-podman in_pod.
It allows providing custom value for --in-pod and is especially relevant when --userns has to be set.
For example, the following docker-compose.yml allows using userns_mode by overriding the default
value of --in-pod (unless it was specifically provided by "--in-pod=True" in command line interface).
```yml
version: "3"
services:
cont:
image: nopush/podman-compose-test
userns_mode: keep-id:uid=1000
command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-p", "8080"]
x-podman:
in_pod: false
```

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

@ -1,7 +1,7 @@
# pylint: disable=import-error
# pylint: disable=unused-import
import os
import asyncio # noqa: F401
import os
import aioredis
from aiohttp import web

View File

@ -0,0 +1,11 @@
services:
test:
image: nvidia/cuda:12.3.1-base-ubuntu20.04
command: nvidia-smi
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]

13
newsfragments/README.txt Normal file
View File

@ -0,0 +1,13 @@
This is the directory for news fragments used by towncrier: https://github.com/hawkowl/towncrier
You create a news fragment in this directory when you make a change, and the file gets removed from
this directory when the news is published.
towncrier has a few standard types of news fragments, signified by the file extension. These are:
.feature: Signifying a new feature.
.bugfix: Signifying a bug fix.
.doc: Signifying a documentation improvement.
.removal: Signifying a deprecation or removal of public API.
.change: Signifying a change of behavior
.misc: Miscellaneous change

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,168 @@
# 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
class TestCanMergeBuild(unittest.TestCase):
@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)
# $$$ is a placeholder for either command or entrypoint
@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_keys_command_entrypoint(
self, base_template, override_template, expected_template
):
for key in ['command', 'entrypoint']:
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 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_bool = 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 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 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,76 @@
# 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 test_publish_ports(self):
cnt = get_minimal_container()
compose = get_minimal_compose()
args = get_minimal_args()
args.publish = ["1111", "2222:2222"]
compose_run_update_container_from_args(compose, cnt, args)
expected_cnt = {
"name": "default_name",
"ports": ["1111", "2222:2222"],
"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,
publish=None,
service_ports=None,
user=None,
volume=None,
workdir=None,
)

View File

@ -0,0 +1,558 @@
# SPDX-License-Identifier: GPL-2.0
import unittest
from os import path
from unittest import mock
from parameterized import parameterized
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",
],
)
async def test_uidmaps_extension_old_path(self):
c = create_compose_mock()
cnt = get_minimal_container()
cnt['x-podman'] = {'uidmaps': ['1000:1000:1']}
with self.assertRaises(ValueError):
await container_to_args(c, cnt)
async def test_uidmaps_extension(self):
c = create_compose_mock()
cnt = get_minimal_container()
cnt['x-podman.uidmaps'] = ['1000:1000:1', '1001:1001:2']
args = await container_to_args(c, cnt)
self.assertEqual(
args,
[
"--name=project_name_service_name1",
"-d",
"--network=bridge",
"--network-alias=service_name",
'--uidmap',
'1000:1000:1',
'--uidmap',
'1001:1001:2',
"busybox",
],
)
async def test_gidmaps_extension(self):
c = create_compose_mock()
cnt = get_minimal_container()
cnt['x-podman.gidmaps'] = ['1000:1000:1', '1001:1001:2']
args = await container_to_args(c, cnt)
self.assertEqual(
args,
[
"--name=project_name_service_name1",
"-d",
"--network=bridge",
"--network-alias=service_name",
'--gidmap',
'1000:1000:1',
'--gidmap',
'1001:1001:2',
"busybox",
],
)
async def test_rootfs_extension(self):
c = create_compose_mock()
cnt = get_minimal_container()
del cnt["image"]
cnt["x-podman.rootfs"] = "/path/to/rootfs"
args = await container_to_args(c, cnt)
self.assertEqual(
args,
[
"--name=project_name_service_name1",
"-d",
"--network=bridge",
"--network-alias=service_name",
"--rootfs",
"/path/to/rootfs",
],
)
async def test_env_file_str(self):
c = create_compose_mock()
cnt = get_minimal_container()
env_file = path.realpath('tests/env-file-tests/env-files/project-1.env')
cnt['env_file'] = env_file
args = await container_to_args(c, cnt)
self.assertEqual(
args,
[
"--name=project_name_service_name1",
"-d",
"-e",
"ZZVAR1=podman-rocks-123",
"-e",
"ZZVAR2=podman-rocks-124",
"-e",
"ZZVAR3=podman-rocks-125",
"--network=bridge",
"--network-alias=service_name",
"busybox",
],
)
async def test_env_file_str_not_exists(self):
c = create_compose_mock()
cnt = get_minimal_container()
cnt['env_file'] = 'notexists'
with self.assertRaises(ValueError):
await container_to_args(c, cnt)
async def test_env_file_str_array_one_path(self):
c = create_compose_mock()
cnt = get_minimal_container()
env_file = path.realpath('tests/env-file-tests/env-files/project-1.env')
cnt['env_file'] = [env_file]
args = await container_to_args(c, cnt)
self.assertEqual(
args,
[
"--name=project_name_service_name1",
"-d",
"-e",
"ZZVAR1=podman-rocks-123",
"-e",
"ZZVAR2=podman-rocks-124",
"-e",
"ZZVAR3=podman-rocks-125",
"--network=bridge",
"--network-alias=service_name",
"busybox",
],
)
async def test_env_file_str_array_two_paths(self):
c = create_compose_mock()
cnt = get_minimal_container()
env_file = path.realpath('tests/env-file-tests/env-files/project-1.env')
env_file_2 = path.realpath('tests/env-file-tests/env-files/project-2.env')
cnt['env_file'] = [env_file, env_file_2]
args = await container_to_args(c, cnt)
self.assertEqual(
args,
[
"--name=project_name_service_name1",
"-d",
"-e",
"ZZVAR1=podman-rocks-123",
"-e",
"ZZVAR2=podman-rocks-124",
"-e",
"ZZVAR3=podman-rocks-125",
"-e",
"ZZVAR1=podman-rocks-223",
"-e",
"ZZVAR2=podman-rocks-224",
"--network=bridge",
"--network-alias=service_name",
"busybox",
],
)
async def test_env_file_obj_required(self):
c = create_compose_mock()
cnt = get_minimal_container()
env_file = path.realpath('tests/env-file-tests/env-files/project-1.env')
cnt['env_file'] = {'path': env_file, 'required': True}
args = await container_to_args(c, cnt)
self.assertEqual(
args,
[
"--name=project_name_service_name1",
"-d",
"-e",
"ZZVAR1=podman-rocks-123",
"-e",
"ZZVAR2=podman-rocks-124",
"-e",
"ZZVAR3=podman-rocks-125",
"--network=bridge",
"--network-alias=service_name",
"busybox",
],
)
async def test_env_file_obj_required_non_existent_path(self):
c = create_compose_mock()
cnt = get_minimal_container()
cnt['env_file'] = {'path': 'not-exists', 'required': True}
with self.assertRaises(ValueError):
await container_to_args(c, cnt)
async def test_env_file_obj_optional(self):
c = create_compose_mock()
cnt = get_minimal_container()
cnt['env_file'] = {'path': 'not-exists', 'required': False}
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_gpu_count_all(self):
c = create_compose_mock()
cnt = get_minimal_container()
cnt["command"] = ["nvidia-smi"]
cnt["deploy"] = {"resources": {"reservations": {"devices": [{}]}}}
cnt["deploy"]["resources"]["reservations"]["devices"][0] = {
"driver": "nvidia",
"count": "all",
"capabilities": ["gpu"],
}
args = await container_to_args(c, cnt)
self.assertEqual(
args,
[
"--name=project_name_service_name1",
"-d",
"--network=bridge",
"--network-alias=service_name",
"--device",
"nvidia.com/gpu=all",
"--security-opt=label=disable",
"busybox",
"nvidia-smi",
],
)
async def test_gpu_count_specific(self):
c = create_compose_mock()
cnt = get_minimal_container()
cnt["command"] = ["nvidia-smi"]
cnt["deploy"] = {
"resources": {
"reservations": {
"devices": [
{
"driver": "nvidia",
"count": 2,
"capabilities": ["gpu"],
}
]
}
}
}
args = await container_to_args(c, cnt)
self.assertEqual(
args,
[
"--name=project_name_service_name1",
"-d",
"--network=bridge",
"--network-alias=service_name",
"--device",
"nvidia.com/gpu=0",
"--device",
"nvidia.com/gpu=1",
"--security-opt=label=disable",
"busybox",
"nvidia-smi",
],
)
async def test_gpu_device_ids_all(self):
c = create_compose_mock()
cnt = get_minimal_container()
cnt["command"] = ["nvidia-smi"]
cnt["deploy"] = {
"resources": {
"reservations": {
"devices": [
{
"driver": "nvidia",
"device_ids": "all",
"capabilities": ["gpu"],
}
]
}
}
}
args = await container_to_args(c, cnt)
self.assertEqual(
args,
[
"--name=project_name_service_name1",
"-d",
"--network=bridge",
"--network-alias=service_name",
"--device",
"nvidia.com/gpu=all",
"--security-opt=label=disable",
"busybox",
"nvidia-smi",
],
)
async def test_gpu_device_ids_specific(self):
c = create_compose_mock()
cnt = get_minimal_container()
cnt["command"] = ["nvidia-smi"]
cnt["deploy"] = {
"resources": {
"reservations": {
"devices": [
{
"driver": "nvidia",
"device_ids": [1, 3],
"capabilities": ["gpu"],
}
]
}
}
}
args = await container_to_args(c, cnt)
self.assertEqual(
args,
[
"--name=project_name_service_name1",
"-d",
"--network=bridge",
"--network-alias=service_name",
"--device",
"nvidia.com/gpu=1",
"--device",
"nvidia.com/gpu=3",
"--security-opt=label=disable",
"busybox",
"nvidia-smi",
],
)
@parameterized.expand([
(False, "z", ["--mount", "type=bind,source=./foo,destination=/mnt,z"]),
(False, "Z", ["--mount", "type=bind,source=./foo,destination=/mnt,Z"]),
(True, "z", ["-v", "./foo:/mnt:z"]),
(True, "Z", ["-v", "./foo:/mnt:Z"]),
])
async def test_selinux_volume(self, prefer_volume, selinux_type, expected_additional_args):
c = create_compose_mock()
c.prefer_volume_over_mount = prefer_volume
cnt = get_minimal_container()
# This is supposed to happen during `_parse_compose_file`
# but that is probably getting skipped during testing
cnt["_service"] = cnt["service_name"]
cnt["volumes"] = [
{
"type": "bind",
"source": "./foo",
"target": "/mnt",
"bind": {
"selinux": selinux_type,
},
}
]
args = await container_to_args(c, cnt)
self.assertEqual(
args,
[
"--name=project_name_service_name1",
"-d",
*expected_additional_args,
"--network=bridge",
"--network-alias=service_name",
"busybox",
],
)

View File

@ -0,0 +1,91 @@
# SPDX-License-Identifier: GPL-2.0
import unittest
from podman_compose import container_to_args
from .test_container_to_args import create_compose_mock
from .test_container_to_args import get_minimal_container
class TestContainerToArgsSecrets(unittest.IsolatedAsyncioTestCase):
async def test_pass_secret_as_env_variable(self):
c = create_compose_mock()
c.declared_secrets = {
"my_secret": {"external": "true"} # must have external or name value
}
cnt = get_minimal_container()
cnt["secrets"] = [
{
"source": "my_secret",
"target": "ENV_SECRET",
"type": "env",
},
]
args = await container_to_args(c, cnt)
self.assertEqual(
args,
[
"--name=project_name_service_name1",
"-d",
"--network=bridge",
"--network-alias=service_name",
"--secret",
"my_secret,type=env,target=ENV_SECRET",
"busybox",
],
)
async def test_secret_as_env_external_true_has_no_name(self):
c = create_compose_mock()
c.declared_secrets = {
"my_secret": {
"name": "my_secret", # must have external or name value
}
}
cnt = get_minimal_container()
cnt["_service"] = "test-service"
cnt["secrets"] = [
{
"source": "my_secret",
"target": "ENV_SECRET",
"type": "env",
}
]
args = await container_to_args(c, cnt)
self.assertEqual(
args,
[
"--name=project_name_service_name1",
"-d",
"--network=bridge",
"--network-alias=service_name",
"--secret",
"my_secret,type=env,target=ENV_SECRET",
"busybox",
],
)
async def test_pass_secret_as_env_variable_no_external(self):
c = create_compose_mock()
c.declared_secrets = {
"my_secret": {} # must have external or name value
}
cnt = get_minimal_container()
cnt["_service"] = "test-service"
cnt["secrets"] = [
{
"source": "my_secret",
"target": "ENV_SECRET",
"type": "env",
}
]
with self.assertRaises(ValueError) as context:
await container_to_args(c, cnt)
self.assertIn('ERROR: unparsable secret: ', str(context.exception))

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,203 @@
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_default(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}",
"--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_ipam_driver(self):
net_desc = {
"labels": [],
"internal": False,
"driver": None,
"driver_opts": {},
"ipam": {
"driver": "someipamdriver",
"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",
"someipamdriver",
"--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": "someipamdriver",
"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",
"someipamdriver",
"--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,43 @@
import copy
from podman_compose import normalize_service
test_cases_simple = [
(
{"depends_on": "my_service"},
{"depends_on": {"my_service": {"condition": "service_started"}}},
),
(
{"depends_on": ["my_service"]},
{"depends_on": {"my_service": {"condition": "service_started"}}},
),
(
{"depends_on": ["my_service1", "my_service2"]},
{
"depends_on": {
"my_service1": {"condition": "service_started"},
"my_service2": {"condition": "service_started"},
},
},
),
(
{"depends_on": {"my_service": {"condition": "service_started"}}},
{"depends_on": {"my_service": {"condition": "service_started"}}},
),
(
{"depends_on": {"my_service": {"condition": "service_healthy"}}},
{"depends_on": {"my_service": {"condition": "service_healthy"}}},
),
]
def test_normalize_service_simple():
for test_case, expected in copy.deepcopy(test_cases_simple):
test_original = copy.deepcopy(test_case)
test_case = normalize_service(test_case)
test_result = expected == test_case
if not test_result:
print("test: ", test_original)
print("expected: ", expected)
print("actual: ", test_case)
assert test_result

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_bool = 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

@ -0,0 +1,70 @@
# SPDX-License-Identifier: GPL-2.0
import unittest
from parameterized import parameterized
from podman_compose import normalize_service
class TestNormalizeService(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"}},
),
(
{"build": {"additional_contexts": ["ctx=../ctx", "ctx2=../ctx2"]}},
{"build": {"additional_contexts": ["ctx=../ctx", "ctx2=../ctx2"]}},
),
(
{"build": {"additional_contexts": {"ctx": "../ctx", "ctx2": "../ctx2"}}},
{"build": {"additional_contexts": ["ctx=../ctx", "ctx2=../ctx2"]}},
),
])
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([
([], []),
(["sh"], ["sh"]),
(["sh", "-c", "date"], ["sh", "-c", "date"]),
("sh", ["sh"]),
("sleep infinity", ["sleep", "infinity"]),
(
"bash -c 'sleep infinity'",
["bash", "-c", "sleep infinity"],
),
])
def test_command_like(self, input, expected):
for key in ['command', 'entrypoint']:
input_service = {}
input_service[key] = input
expected_service = {}
expected_service[key] = expected
self.assertEqual(normalize_service(input_service), expected_service)

View File

@ -1,21 +1,20 @@
# SPDX-License-Identifier: GPL-2.0
# pylint: disable=redefined-outer-name
import pytest
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

@ -1,10 +1,11 @@
# SPDX-License-Identifier: GPL-2.0
import os
from setuptools import setup
try:
README = open(
os.path.join(os.path.dirname(__file__), "README.md"), encoding="utf-8"
).read()
README = open(os.path.join(os.path.dirname(__file__), "README.md"), encoding="utf-8").read()
except: # noqa: E722 # pylint: disable=bare-except
README = ""
@ -16,12 +17,11 @@ setup(
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",
@ -40,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,14 @@
# Test podman-compose with build.additional_contexts
```
podman-compose build
podman-compose up
podman-compose down
```
expected output would be
```
[dict] | Data for dict
[list] | Data for list
```

View File

@ -0,0 +1 @@
Data for dict

View File

@ -0,0 +1 @@
Data for list

View File

@ -0,0 +1,3 @@
FROM busybox
COPY --from=data data.txt /data/data.txt
CMD ["busybox", "cat", "/data/data.txt"]

View File

@ -0,0 +1,12 @@
version: "3.7"
services:
dict:
build:
context: .
additional_contexts:
data: ../data_for_dict
list:
build:
context: .
additional_contexts:
- data=../data_for_list

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,25 +0,0 @@
"""conftest.py
Defines global pytest fixtures available to all tests.
"""
import pytest
from pathlib import Path
import os
@pytest.fixture
def base_path():
"""Returns the base path for the project"""
return Path(__file__).parent.parent
@pytest.fixture
def test_path(base_path):
"""Returns the path to the tests directory"""
return os.path.join(base_path, "tests")
@pytest.fixture
def podman_compose_path(base_path):
"""Returns the path to the podman compose script"""
return os.path.join(base_path, "podman_compose.py")

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,2 @@
ZZVAR1='This value is overwritten by env-file-tests/.env'
ZZVAR3='This value is loaded from env-file-tests/.env'

4
tests/env-file-tests/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
# This overrides the repository root .gitignore (ignoring all .env).
# The .env files in this directory are important for the test cases.
!.env
!project/.env

View File

@ -0,0 +1,37 @@
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
```
```
podman-compose -f $(pwd)/project/container-compose.env-file-flat.yaml up
```
```
podman-compose -f $(pwd)/project/container-compose.env-file-obj.yaml up
```
```
podman-compose -f $(pwd)/project/container-compose.env-file-obj-optional.yaml up
```
based on environment variable precedent this command should give podman-rocks-321
```
ZZVAR1=podman-rocks-321 podman-compose -f $(pwd)/project/container-compose.yaml --env-file $(pwd)/env-files/project-1.env up
```
_The below test should print three environment variables_
```
podman-compose -f $(pwd)/project/container-compose.load-.env-in-project.yaml run --rm app
ZZVAR1=This value is overwritten by env-file-tests/.env
ZZVAR2=This value is loaded from .env in project/ directory
ZZVAR3=This value is loaded from env-file-tests/.env
```

View File

@ -0,0 +1,3 @@
ZZVAR1=podman-rocks-123
ZZVAR2=podman-rocks-124
ZZVAR3=podman-rocks-125

View File

@ -0,0 +1,2 @@
ZZVAR1=podman-rocks-223
ZZVAR2=podman-rocks-224

View File

@ -0,0 +1,2 @@
ZZVAR1='This value is loaded but should be overwritten'
ZZVAR2='This value is loaded from .env in project/ directory'

View File

@ -0,0 +1,9 @@
services:
app:
image: busybox
command: ["/bin/busybox", "sh", "-c", "env | grep ZZ"]
tmpfs:
- /run
- /tmp
env_file:
- ../env-files/project-1.env

View File

@ -0,0 +1,11 @@
services:
app:
image: busybox
command: ["/bin/busybox", "sh", "-c", "env | grep ZZ"]
tmpfs:
- /run
- /tmp
env_file:
- path: ../env-files/project-1.env
- path: ../env-files/project-2.env
required: false

View File

@ -0,0 +1,9 @@
services:
app:
image: busybox
command: ["/bin/busybox", "sh", "-c", "env | grep ZZ"]
tmpfs:
- /run
- /tmp
env_file:
- path: ../env-files/project-1.env

View File

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

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,9 @@
version: "3"
services:
cont:
image: nopush/podman-compose-test
userns_mode: keep-id:uid=1000
command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-p", "8080"]
x-podman:
in_pod: false

View File

@ -0,0 +1,6 @@
version: "3"
services:
cont:
image: nopush/podman-compose-test
userns_mode: keep-id:uid=1000
command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-p", "8080"]

View File

@ -0,0 +1,9 @@
version: "3"
services:
cont:
image: nopush/podman-compose-test
userns_mode: keep-id:uid=1000
command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-p", "8080"]
x-podman:
in_pod: true

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,6 @@
version: '3.6'
services:
web2:
image: nopush/podman-compose-test
command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", ".", "-p", "8004"]

View File

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

View File

@ -0,0 +1,15 @@
version: '3'
# --ipam-driver must not be pass when driver is "default"
networks:
ipam_test_default:
ipam:
driver: default
config:
- subnet: 172.19.0.0/24
services:
testipam:
image: busybox
command: ["echo", "ipamtest"]

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

@ -1,24 +1,24 @@
version: "3"
services:
default-service:
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
service-1:
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
profiles:
- profile-1
service-2:
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
profiles:
- profile-2
- profile-2

View File

@ -31,6 +31,9 @@ services:
uid: '103'
gid: '103'
mode: 400
- source: my_secret
target: ENV_SECRET
type: env
secrets:
my_secret:
@ -43,4 +46,3 @@ secrets:
name: my_secret_3
file_secret:
file: ./my_secret

View File

@ -4,3 +4,4 @@ ls -la /run/secrets/*
ls -la /etc/custom_location
cat /run/secrets/*
cat /etc/custom_location
env | grep SECRET

View File

@ -0,0 +1,14 @@
version: "3"
services:
web1:
image: busybox
command: httpd -f -p 80 -h /var/www/html
volumes:
- type: bind
source: ./docker-compose.yml
target: /var/www/html/index.html
bind:
selinux: z
ports:
- "8080:80"

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, _, returncode = capture(command_up)
assert 0 == returncode
# check container was created and exists
out, _, returncode = capture(command_check_container)
assert 0 == returncode
assert out == b'"localhost/subdir_test:me"\n'
out, _, returncode = capture(command_down)
# cleanup test image(tags)
assert 0 == returncode
# check container did not exists anymore
out, _, 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,44 @@
# SPDX-License-Identifier: GPL-2.0
"""Test how additional contexts 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(), "additional_contexts", "project")
class TestComposeBuildAdditionalContexts(unittest.TestCase):
def test_build_additional_context(self):
"""podman build should receive additional contexts as --build-context
See additional_context/project/docker-compose.yaml for context paths
"""
cmd = (
"coverage",
"run",
podman_compose_path(),
"--dry-run",
"--verbose",
"-f",
os.path.join(compose_yaml_path(), "docker-compose.yml"),
"build",
)
p = subprocess.run(
cmd,
stdout=subprocess.PIPE,
check=False,
stderr=subprocess.STDOUT,
text=True,
)
self.assertEqual(p.returncode, 0)
self.assertIn("--build-context=data=../data_for_dict", p.stdout)
self.assertIn("--build-context=data=../data_for_list", p.stdout)

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

@ -1,77 +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.
"""
import pytest
# pylint: disable=redefined-outer-name
import os
from test_podman_compose import capture
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
@pytest.fixture
def profile_compose_file(test_path):
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")
return os.path.join(test_path(), "profile", "docker-compose.yml")
def test_config_no_profiles(podman_compose_path, profile_compose_file):
"""
Tests podman-compose config command without profile enablement.
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",
]
:param podman_compose_path: The fixture used to specify the path to the podman compose file.
:param profile_compose_file: The fixtued used to specify the path to the "profile" compose used in the test.
"""
config_cmd = ["python3", podman_compose_path, "-f", profile_compose_file, "config"]
out, _ = self.run_subprocess_assert_returncode(config_cmd)
out, err, return_code = capture(config_cmd)
assert return_code == 0
string_output = out.decode("utf-8")
self.assertIn("default-service", string_output)
self.assertNotIn("service-1", string_output)
self.assertNotIn("service-2", string_output)
string_output = out.decode("utf-8")
assert "default-service" in string_output
assert "service-1" not in string_output
assert "service-2" not in 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)
@pytest.mark.parametrize(
"profiles, expected_services",
[
(
["--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(
podman_compose_path, profile_compose_file, profiles, expected_services
):
"""
Tests podman-compose
:param podman_compose_path: The fixture used to specify the path to the podman compose file.
:param profile_compose_file: The fixtued used to specify the path to the "profile" compose used in the test.
: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 = ["python3", podman_compose_path, "-f", profile_compose_file]
config_cmd.extend(profiles)
actual_output = out.decode("utf-8")
out, err, return_code = capture(config_cmd)
assert return_code == 0
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
assert len(expected_services) == 3
actual_services = {}
for service, expected_check in expected_services.items():
actual_services[service] = service in actual_output
assert expected_services == actual_services
self.assertEqual(expected_services, actual_services)

View File

@ -0,0 +1,442 @@
# SPDX-License-Identifier: GPL-2.0
import os
import unittest
from pathlib import Path
from .test_utils import RunSubprocessMixin
def base_path():
"""Returns the base path for the project"""
return Path(__file__).parent.parent
def test_path():
"""Returns the path to the tests directory"""
return os.path.join(base_path(), "tests")
def podman_compose_path():
"""Returns the path to the podman compose script"""
return os.path.join(base_path(), "podman_compose.py")
# If a compose file has userns_mode set, setting in_pod to True, results in error.
# Default in_pod setting is True, unless compose file provides otherwise.
# Compose file provides custom in_pod option, which can be overridden by command line in_pod option.
# Test all combinations of command line argument in_pod and compose file argument in_pod.
class TestPodmanComposeInPod(unittest.TestCase, RunSubprocessMixin):
# compose file provides x-podman in_pod=false
def test_x_podman_in_pod_false_command_line_in_pod_not_exists(self):
"""
Test that podman-compose will not create a pod, when x-podman in_pod=false and command line
does not provide this option
"""
main_path = Path(__file__).parent.parent
command_up = [
"python3",
str(main_path.joinpath("podman_compose.py")),
"-f",
str(
main_path.joinpath("tests", "in_pod", "custom_x-podman_false", "docker-compose.yml")
),
"up",
"-d",
]
down_cmd = [
"python3",
podman_compose_path(),
"-f",
str(
main_path.joinpath("tests", "in_pod", "custom_x-podman_false", "docker-compose.yml")
),
"down",
]
try:
self.run_subprocess_assert_returncode(command_up)
finally:
self.run_subprocess_assert_returncode(down_cmd)
command_rm_pod = ["podman", "pod", "rm", "pod_custom_x-podman_false"]
# throws an error, can not actually find this pod because it was not created
self.run_subprocess_assert_returncode(command_rm_pod, expected_returncode=1)
def test_x_podman_in_pod_false_command_line_in_pod_true(self):
"""
Test that podman-compose does not allow pod creating even with command line in_pod=True
when --userns and --pod are set together: throws an error
"""
main_path = Path(__file__).parent.parent
# FIXME: creates a pod anyway, although it should not
command_up = [
"python3",
str(main_path.joinpath("podman_compose.py")),
"--in-pod=True",
"-f",
str(
main_path.joinpath("tests", "in_pod", "custom_x-podman_false", "docker-compose.yml")
),
"up",
"-d",
]
try:
out, err = self.run_subprocess_assert_returncode(command_up)
self.assertEqual(b"Error: --userns and --pod cannot be set together" in err, True)
finally:
command_rm_pod = ["podman", "pod", "rm", "pod_custom_x-podman_false"]
# should throw an error of not being able to find this pod (because it should not have
# been created) and have expected_returncode=1 (see FIXME above)
self.run_subprocess_assert_returncode(command_rm_pod)
def test_x_podman_in_pod_false_command_line_in_pod_false(self):
"""
Test that podman-compose will not create a pod as command line sets in_pod=False
"""
main_path = Path(__file__).parent.parent
command_up = [
"python3",
str(main_path.joinpath("podman_compose.py")),
"--in-pod=False",
"-f",
str(
main_path.joinpath("tests", "in_pod", "custom_x-podman_false", "docker-compose.yml")
),
"up",
"-d",
]
down_cmd = [
"python3",
podman_compose_path(),
"-f",
str(
main_path.joinpath("tests", "in_pod", "custom_x-podman_false", "docker-compose.yml")
),
"down",
]
try:
self.run_subprocess_assert_returncode(command_up)
finally:
self.run_subprocess_assert_returncode(down_cmd)
command_rm_pod = ["podman", "pod", "rm", "pod_custom_x-podman_false"]
# can not actually find this pod because it was not created
self.run_subprocess_assert_returncode(command_rm_pod, 1)
def test_x_podman_in_pod_false_command_line_in_pod_empty_string(self):
"""
Test that podman-compose will not create a pod, when x-podman in_pod=false and command line
command line in_pod=""
"""
main_path = Path(__file__).parent.parent
command_up = [
"python3",
str(main_path.joinpath("podman_compose.py")),
"--in-pod=",
"-f",
str(
main_path.joinpath("tests", "in_pod", "custom_x-podman_false", "docker-compose.yml")
),
"up",
"-d",
]
down_cmd = [
"python3",
podman_compose_path(),
"-f",
str(
main_path.joinpath("tests", "in_pod", "custom_x-podman_false", "docker-compose.yml")
),
"down",
]
try:
self.run_subprocess_assert_returncode(command_up)
finally:
self.run_subprocess_assert_returncode(down_cmd)
command_rm_pod = ["podman", "pod", "rm", "pod_custom_x-podman_false"]
# can not actually find this pod because it was not created
self.run_subprocess_assert_returncode(command_rm_pod, 1)
# compose file provides x-podman in_pod=true
def test_x_podman_in_pod_true_command_line_in_pod_not_exists(self):
"""
Test that podman-compose does not allow pod creating when --userns and --pod are set
together even when x-podman in_pod=true: throws an error
"""
main_path = Path(__file__).parent.parent
# FIXME: creates a pod anyway, although it should not
# Container is not created, so command 'down' is not needed
command_up = [
"python3",
str(main_path.joinpath("podman_compose.py")),
"-f",
str(
main_path.joinpath("tests", "in_pod", "custom_x-podman_true", "docker-compose.yml")
),
"up",
"-d",
]
try:
out, err = self.run_subprocess_assert_returncode(command_up)
self.assertEqual(b"Error: --userns and --pod cannot be set together" in err, True)
finally:
command_rm_pod = ["podman", "pod", "rm", "pod_custom_x-podman_true"]
# should throw an error of not being able to find this pod (it should not have been
# created) and have expected_returncode=1 (see FIXME above)
self.run_subprocess_assert_returncode(command_rm_pod)
def test_x_podman_in_pod_true_command_line_in_pod_true(self):
"""
Test that podman-compose does not allow pod creating when --userns and --pod are set
together even when x-podman in_pod=true and and command line in_pod=True: throws an error
"""
main_path = Path(__file__).parent.parent
# FIXME: creates a pod anyway, although it should not
# Container is not created, so command 'down' is not needed
command_up = [
"python3",
str(main_path.joinpath("podman_compose.py")),
"--in-pod=True",
"-f",
str(
main_path.joinpath("tests", "in_pod", "custom_x-podman_true", "docker-compose.yml")
),
"up",
"-d",
]
try:
out, err = self.run_subprocess_assert_returncode(command_up)
self.assertEqual(b"Error: --userns and --pod cannot be set together" in err, True)
finally:
command_rm_pod = ["podman", "pod", "rm", "pod_custom_x-podman_true"]
# should throw an error of not being able to find this pod (because it should not have
# been created) and have expected_returncode=1 (see FIXME above)
self.run_subprocess_assert_returncode(command_rm_pod)
def test_x_podman_in_pod_true_command_line_in_pod_false(self):
"""
Test that podman-compose will not create a pod as command line sets in_pod=False
"""
main_path = Path(__file__).parent.parent
command_up = [
"python3",
str(main_path.joinpath("podman_compose.py")),
"--in-pod=False",
"-f",
str(
main_path.joinpath("tests", "in_pod", "custom_x-podman_true", "docker-compose.yml")
),
"up",
"-d",
]
down_cmd = [
"python3",
podman_compose_path(),
"-f",
str(
main_path.joinpath("tests", "in_pod", "custom_x-podman_true", "docker-compose.yml")
),
"down",
]
try:
self.run_subprocess_assert_returncode(command_up)
finally:
self.run_subprocess_assert_returncode(down_cmd)
command_rm_pod = ["podman", "pod", "rm", "pod_custom_x-podman_false"]
# can not actually find this pod because it was not created
self.run_subprocess_assert_returncode(command_rm_pod, 1)
def test_x_podman_in_pod_true_command_line_in_pod_empty_string(self):
"""
Test that podman-compose does not allow pod creating when --userns and --pod are set
together even when x-podman in_pod=true and command line in_pod="": throws an error
"""
main_path = Path(__file__).parent.parent
# FIXME: creates a pod anyway, although it should not
# Container is not created, so command 'down' is not needed
command_up = [
"python3",
str(main_path.joinpath("podman_compose.py")),
"--in-pod=",
"-f",
str(
main_path.joinpath("tests", "in_pod", "custom_x-podman_true", "docker-compose.yml")
),
"up",
"-d",
]
try:
out, err = self.run_subprocess_assert_returncode(command_up)
self.assertEqual(b"Error: --userns and --pod cannot be set together" in err, True)
finally:
command_rm_pod = ["podman", "pod", "rm", "pod_custom_x-podman_true"]
# should throw an error of not being able to find this pod (because it should not have
# been created) and have expected_returncode=1 (see FIXME above)
self.run_subprocess_assert_returncode(command_rm_pod)
# compose file does not provide x-podman in_pod
def test_x_podman_in_pod_not_exists_command_line_in_pod_not_exists(self):
"""
Test that podman-compose does not allow pod creating when --userns and --pod are set
together: throws an error
"""
main_path = Path(__file__).parent.parent
# FIXME: creates a pod anyway, although it should not
# Container is not created, so command 'down' is not needed
command_up = [
"python3",
str(main_path.joinpath("podman_compose.py")),
"-f",
str(
main_path.joinpath(
"tests", "in_pod", "custom_x-podman_not_exists", "docker-compose.yml"
)
),
"up",
"-d",
]
try:
out, err = self.run_subprocess_assert_returncode(command_up)
self.assertEqual(b"Error: --userns and --pod cannot be set together" in err, True)
finally:
command_rm_pod = ["podman", "pod", "rm", "pod_custom_x-podman_not_exists"]
# should throw an error of not being able to find this pod (it should not have been
# created) and have expected_returncode=1 (see FIXME above)
self.run_subprocess_assert_returncode(command_rm_pod)
def test_x_podman_in_pod_not_exists_command_line_in_pod_true(self):
"""
Test that podman-compose does not allow pod creating when --userns and --pod are set
together even when x-podman in_pod=true: throws an error
"""
main_path = Path(__file__).parent.parent
# FIXME: creates a pod anyway, although it should not
# Container was not created, so command 'down' is not needed
command_up = [
"python3",
str(main_path.joinpath("podman_compose.py")),
"--in-pod=True",
"-f",
str(
main_path.joinpath(
"tests", "in_pod", "custom_x-podman_not_exists", "docker-compose.yml"
)
),
"up",
"-d",
]
try:
out, err = self.run_subprocess_assert_returncode(command_up)
self.assertEqual(b"Error: --userns and --pod cannot be set together" in err, True)
finally:
command_rm_pod = ["podman", "pod", "rm", "pod_custom_x-podman_not_exists"]
# should throw an error of not being able to find this pod (because it should not have
# been created) and have expected_returncode=1 (see FIXME above)
self.run_subprocess_assert_returncode(command_rm_pod)
def test_x_podman_in_pod_not_exists_command_line_in_pod_false(self):
"""
Test that podman-compose will not create a pod as command line sets in_pod=False
"""
main_path = Path(__file__).parent.parent
command_up = [
"python3",
str(main_path.joinpath("podman_compose.py")),
"--in-pod=False",
"-f",
str(
main_path.joinpath(
"tests", "in_pod", "custom_x-podman_not_exists", "docker-compose.yml"
)
),
"up",
"-d",
]
down_cmd = [
"python3",
podman_compose_path(),
"-f",
str(
main_path.joinpath(
"tests", "in_pod", "custom_x-podman_not_exists", "docker-compose.yml"
)
),
"down",
]
try:
self.run_subprocess_assert_returncode(command_up)
finally:
self.run_subprocess_assert_returncode(down_cmd)
command_rm_pod = ["podman", "pod", "rm", "pod_custom_x-podman_not_exists"]
# can not actually find this pod because it was not created
self.run_subprocess_assert_returncode(command_rm_pod, 1)
def test_x_podman_in_pod_not_exists_command_line_in_pod_empty_string(self):
"""
Test that podman-compose does not allow pod creating when --userns and --pod are set
together: throws an error
"""
main_path = Path(__file__).parent.parent
# FIXME: creates a pod anyway, although it should not
# Container was not created, so command 'down' is not needed
command_up = [
"python3",
str(main_path.joinpath("podman_compose.py")),
"--in-pod=",
"-f",
str(
main_path.joinpath(
"tests", "in_pod", "custom_x-podman_not_exists", "docker-compose.yml"
)
),
"up",
"-d",
]
try:
out, err = self.run_subprocess_assert_returncode(command_up)
self.assertEqual(b"Error: --userns and --pod cannot be set together" in err, True)
finally:
command_rm_pod = ["podman", "pod", "rm", "pod_custom_x-podman_not_exists"]
# should throw an error of not being able to find this pod (because it should not have
# been created) and have expected_returncode=1 (see FIXME above)
self.run_subprocess_assert_returncode(command_rm_pod)

View File

@ -0,0 +1,64 @@
# 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"]
self.run_subprocess_assert_returncode(command_up)
out, _ = self.run_subprocess_assert_returncode(command_check_container)
expected_output = b'"localhost/nopush/podman-compose-test:latest"\n' * 2
self.assertEqual(out, expected_output)
# Get container ID to remove it
out, _ = self.run_subprocess_assert_returncode(command_container_id)
self.assertNotEqual(out, b"")
container_ids = out.decode().strip().split("\n")
container_ids = [container_id.replace('"', "") for container_id in container_ids]
command_down.extend(container_ids)
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

@ -1,88 +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.
"""
import pytest
# pylint: disable=redefined-outer-name
import os
from test_podman_compose import capture
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
@pytest.fixture
def profile_compose_file(test_path):
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")
return os.path.join(test_path(), "profile", "docker-compose.yml")
@pytest.fixture(autouse=True)
def teardown(podman_compose_path, profile_compose_file):
"""
Ensures that the services within the "profile compose file" are removed between each test case.
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
:param podman_compose_path: The path to the podman compose script.
:param profile_compose_file: The path to the compose file used for this test module.
"""
# run the test case
yield
down_cmd = [
"coverage",
"run",
podman_compose_path(),
"--profile",
"profile-1",
"--profile",
"profile-2",
"-f",
profile_compose_file(),
"down",
]
self.run_subprocess(down_cmd)
down_cmd = [
"python3",
podman_compose_path,
"--profile",
"profile-1",
"--profile",
"profile-2",
"-f",
profile_compose_file,
"down",
]
capture(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)
@pytest.mark.parametrize(
"profiles, expected_services",
[
(
["--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(podman_compose_path, profile_compose_file, profiles, expected_services):
up_cmd = [
"python3",
podman_compose_path,
"-f",
profile_compose_file,
]
up_cmd.extend(profiles)
check_cmd = [
"podman",
"container",
"ps",
"--format",
'"{{.Names}}"',
]
out, _ = self.run_subprocess_assert_returncode(check_cmd)
out, err, return_code = capture(up_cmd)
assert return_code == 0
self.assertEqual(len(expected_services), 3)
actual_output = out.decode("utf-8")
check_cmd = [
"podman",
"container",
"ps",
"--format",
'"{{.Names}}"',
]
out, err, return_code = capture(check_cmd)
assert return_code == 0
actual_services = {}
for service, _ in expected_services.items():
actual_services[service] = service in actual_output
assert len(expected_services) == 3
actual_output = out.decode("utf-8")
actual_services = {}
for service, expected_check in expected_services.items():
actual_services[service] = service in actual_output
assert expected_services == actual_services
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,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

Some files were not shown because too many files have changed in this diff Show More