forked from extern/podman-compose
Compare commits
206 Commits
Author | SHA1 | Date | |
---|---|---|---|
d38b26bb01 | |||
22a4ad5806 | |||
37e2cb28d4 | |||
0cd3902c5f | |||
6ef759c6fd | |||
16cbcf4152 | |||
67ce900885 | |||
4e9f76768c | |||
84f7fdd7da | |||
405001b990 | |||
6b1aeff55f | |||
f06975b346 | |||
546cad5171 | |||
e07c28d127 | |||
935029dc33 | |||
80b2aa6ed0 | |||
360b85bf2d | |||
650a835eca | |||
82740cc311 | |||
0f645e4c70 | |||
3b15170ccf | |||
3359380ec6 | |||
14f39e5b86 | |||
e799a0b0ea | |||
6d8d3e94fe | |||
65d1fdeaa3 | |||
d905a7c638 | |||
2e8ed2f924 | |||
040b73adab | |||
f3e9a96c96 | |||
04b107805a | |||
2c5d00d3e7 | |||
cac90f69b8 | |||
b513f50f30 | |||
8f618b6fab | |||
cac836b0f5 | |||
3bb305cef4 | |||
09034a0c38 | |||
e668a339ce | |||
0065082db9 | |||
5d4de80ab7 | |||
23ad5c3ef7 | |||
45efe461b0 | |||
4f73f2b79e | |||
1d64f2cf8c | |||
2ce6d1a1e7 | |||
4e22faefd6 | |||
7a2da76ab8 | |||
79865c2e13 | |||
33d7d35a4d | |||
c23a8b2cbd | |||
36a3d3c207 | |||
b202a09501 | |||
35cbc49160 | |||
5c4aa40032 | |||
0ee7c2630a | |||
cef1785cd5 | |||
b4cfef12e9 | |||
b761050b0b | |||
e1d0ea7b4e | |||
1430578568 | |||
8f41cd3cdb | |||
a73dac2e39 | |||
d31a8b124d | |||
5df4e786ee | |||
27e27e9fe9 | |||
70a0e2d003 | |||
58641f0545 | |||
eea8bac496 | |||
09a8a3edf9 | |||
a6c4263738 | |||
9599cc039e | |||
0a6c057486 | |||
2b4ecee082 | |||
77f2e8e5b0 | |||
12d46ca836 | |||
72a94d5185 | |||
2681566580 | |||
62ce087c6e | |||
c97f003732 | |||
c88558b4ec | |||
131010bc9d | |||
1da2f85a90 | |||
cdcedeb6b2 | |||
3e1f7d554b | |||
d7cf0966d3 | |||
e893d06313 | |||
1f35c00694 | |||
9a5b43907f | |||
6c09ce710e | |||
953534a71a | |||
6feff244db | |||
9fd4cf43c1 | |||
65849c95e2 | |||
9baea704d7 | |||
bdff78dcba | |||
45ca1f994f | |||
91fbea3d89 | |||
2743d690d2 | |||
dd34a90068 | |||
6103df78fb | |||
07128f6b37 | |||
81d81fb303 | |||
b263dc1a7d | |||
078ee7b649 | |||
a6e3ae7b31 | |||
1e9cf1dff0 | |||
d704622522 | |||
bbfff78e25 | |||
abb0cb1649 | |||
d53df307f8 | |||
c351f99da5 | |||
829cde0de9 | |||
f18c8092cc | |||
43059dc16f | |||
da63048775 | |||
7987a7185e | |||
5e55df890c | |||
fde7995cb8 | |||
c592596a5e | |||
688ee9a104 | |||
c3a152e68f | |||
bd60bc9f21 | |||
9d8b0b8632 | |||
ee013316c0 | |||
f2f5483a74 | |||
c4fa8f7a16 | |||
4c270b9116 | |||
c5f7f550f9 | |||
c98cbaaaf0 | |||
f2f2f15a3b | |||
91d316ff1f | |||
121b5f6ea1 | |||
969edb88d0 | |||
bba1f33d51 | |||
970e4ac4ab | |||
a9c335bf82 | |||
e67c52f2c4 | |||
e0fc9a7546 | |||
2cdfb3e6d4 | |||
60137eb7f9 | |||
a7a993faef | |||
8ec5e0372a | |||
da520e299b | |||
1e9e2ee776 | |||
dcb6cdb55a | |||
0f693ee584 | |||
d468f85fd1 | |||
cf90ab2858 | |||
d2fa80196f | |||
247774822c | |||
6e65a73ab9 | |||
f95ca7aca7 | |||
cdf5961bf2 | |||
0c0e77c246 | |||
e6fc585702 | |||
bb2338e447 | |||
a8db898ac6 | |||
1ba1c364b9 | |||
15ae2140d4 | |||
ed39523342 | |||
4e43606df3 | |||
91052cb2d9 | |||
a6e0092627 | |||
59a1fa3942 | |||
36139fb282 | |||
b0da6f82d3 | |||
b1e4324d41 | |||
94df95acea | |||
3a5a283cf6 | |||
16a90e2bce | |||
7c81044860 | |||
5b571942e0 | |||
de8f545f07 | |||
1a24cde608 | |||
a90da4dfaf | |||
6841619b9c | |||
27c8cebbdc | |||
c84b4c33fc | |||
0614687840 | |||
a494f20e15 | |||
6af7a2d691 | |||
872664b727 | |||
f4dc5f3b93 | |||
ac5034065d | |||
b34f699adb | |||
3d68d2432d | |||
2c6c1be197 | |||
f81cbe39b5 | |||
91737ee0a6 | |||
9d1407ba90 | |||
b65d4a3916 | |||
23fe9e7e1d | |||
7539257ee8 | |||
9e29891aa7 | |||
a967cab02b | |||
a5c354d60b | |||
e4e5b7d461 | |||
e0edd5dac1 | |||
831caa6276 | |||
9ac33392a0 | |||
c5be5bae90 | |||
c6a1c4c432 | |||
3c9628b462 | |||
38b13a34ea | |||
c31b4e2816 |
1
.codespellignore
Normal file
1
.codespellignore
Normal file
@ -0,0 +1 @@
|
|||||||
|
assertIn
|
4
.codespellrc
Normal file
4
.codespellrc
Normal 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
2
.coveragerc
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[run]
|
||||||
|
parallel=True
|
19
.editorconfig
Normal file
19
.editorconfig
Normal 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
10
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal 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
22
.github/workflows/codespell.yml
vendored
Normal 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
|
41
.github/workflows/pylint.yml
vendored
41
.github/workflows/pylint.yml
vendored
@ -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.7", "3.8", "3.9", "3.10", "3.11"]
|
|
||||||
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')
|
|
36
.github/workflows/pytest.yml
vendored
36
.github/workflows/pytest.yml
vendored
@ -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
25
.github/workflows/static-checks.yml
vendored
Normal 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
40
.github/workflows/test.yml
vendored
Normal 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
5
.gitignore
vendored
@ -47,6 +47,8 @@ coverage.xml
|
|||||||
*.cover
|
*.cover
|
||||||
.hypothesis/
|
.hypothesis/
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
|
test-compose.yaml
|
||||||
|
test-compose-?.yaml
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
*.mo
|
*.mo
|
||||||
@ -103,3 +105,6 @@ venv.bak/
|
|||||||
|
|
||||||
# mypy
|
# mypy
|
||||||
.mypy_cache/
|
.mypy_cache/
|
||||||
|
|
||||||
|
|
||||||
|
.vscode
|
||||||
|
@ -30,3 +30,7 @@ repos:
|
|||||||
"-sn", # Don't display the score
|
"-sn", # Don't display the score
|
||||||
"--rcfile=.pylintrc", # Link to your config file
|
"--rcfile=.pylintrc", # Link to your config file
|
||||||
]
|
]
|
||||||
|
- repo: https://github.com/codespell-project/codespell
|
||||||
|
rev: v2.2.5
|
||||||
|
hooks:
|
||||||
|
- id: codespell
|
||||||
|
159
CONTRIBUTING.md
159
CONTRIBUTING.md
@ -2,113 +2,140 @@
|
|||||||
|
|
||||||
## Who can contribute?
|
## Who can contribute?
|
||||||
|
|
||||||
- Users that found a bug
|
- Users that found a bug,
|
||||||
- Users that wants to propose new functionalities or enhancements
|
- Users that want to propose new functionalities or enhancements,
|
||||||
- Users that want to help other users to troubleshoot their environments
|
- Users that want to help other users to troubleshoot their environments,
|
||||||
- Developers that want to fix bugs
|
- Developers that want to fix bugs,
|
||||||
- Developers that want to implement new functionalities or enhancements
|
- Developers that want to implement new functionalities or enhancements.
|
||||||
|
|
||||||
## Branches
|
## 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.
|
Changes to the `stable` branch are managed by the repository maintainers.
|
||||||
|
|
||||||
## Development environment setup
|
## Development environment setup
|
||||||
|
|
||||||
Note: Some steps are OPTIONAL but all are RECOMMENDED.
|
Note: Some steps are OPTIONAL but all are RECOMMENDED.
|
||||||
|
|
||||||
1. Fork the project repo and clone it
|
1. Fork the project repository and clone it:
|
||||||
```shell
|
|
||||||
$ git clone https://github.com/USERNAME/podman-compose.git
|
```shell
|
||||||
$ cd podman-compose
|
$ 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. (OPTIONAL) Create a Python virtual environment. Example using
|
||||||
```
|
[virtualenv wrapper](https://virtualenvwrapper.readthedocs.io/en/latest/):
|
||||||
2. Install the project runtime and development requirements
|
|
||||||
```shell
|
```shell
|
||||||
$ pip install '.[devel]'
|
$ mkvirtualenv podman-compose
|
||||||
```
|
```
|
||||||
3. (OPTIONAL) Install `pre-commit` git hook scripts (https://pre-commit.com/#3-install-the-git-hook-scripts)
|
|
||||||
```shell
|
3. Install the project runtime and development requirements:
|
||||||
$ pre-commit install
|
|
||||||
```
|
```shell
|
||||||
4. Create a new branch, develop and add tests when possible
|
$ pip install '.[devel]'
|
||||||
5. Run linting & testing before commiting code. Ensure all the hooks are passing.
|
```
|
||||||
```shell
|
|
||||||
$ pre-commit run --all-files
|
4. (OPTIONAL) Install `pre-commit` git hook scripts
|
||||||
```
|
(https://pre-commit.com/#3-install-the-git-hook-scripts):
|
||||||
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
|
```shell
|
||||||
- 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`
|
$ pre-commit install
|
||||||
7. Open a PR to `containers/podman-compose:devel` and wait for a maintainer to review your work.
|
```
|
||||||
|
|
||||||
|
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
|
## Adding new commands
|
||||||
|
|
||||||
To add a command you need to add a function that is decorated
|
To add a command, you need to add a function that is decorated with `@cmd_run`.
|
||||||
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
|
|
||||||
|
|
||||||
```
|
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')
|
@cmd_run(podman_compose, 'build', 'build images defined in the stack')
|
||||||
def compose_build(compose, args):
|
async def compose_build(compose, args):
|
||||||
compose.podman.run(['build', 'something'])
|
await compose.podman.run(['build', 'something'])
|
||||||
```
|
```
|
||||||
|
|
||||||
## Command arguments parsing
|
## Command arguments parsing
|
||||||
|
|
||||||
Add a function that accept `parser` which is an instance from `argparse`.
|
To add arguments to be parsed by a command, you need to add a function that is decorated with
|
||||||
In side that function you can call `parser.add_argument()`.
|
`@cmd_parse` which accepts the compose instance and the command's name (as a string list or as a
|
||||||
The function decorated with `@cmd_parse` accepting the compose instance,
|
single string).
|
||||||
and command names (as a list or as a string).
|
|
||||||
You can do this multiple times.
|
|
||||||
|
|
||||||
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')
|
@cmd_parse(podman_compose, 'build')
|
||||||
def compose_build_parse(parser):
|
def compose_build_parse(parser):
|
||||||
parser.add_argument("--pull",
|
parser.add_argument("--pull",
|
||||||
help="attempt to pull a newer version of the image", action='store_true')
|
help="attempt to pull a newer version of the image", action='store_true')
|
||||||
parser.add_argument("--pull-always",
|
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`
|
If you need to call `podman-compose down` from `podman-compose up`, do something like:
|
||||||
do something like:
|
|
||||||
|
|
||||||
```
|
```python
|
||||||
@cmd_run(podman_compose, 'up', 'up desc')
|
@cmd_run(podman_compose, 'up', 'up desc')
|
||||||
def compose_up(compose, args):
|
async def compose_up(compose, args):
|
||||||
compose.commands['down'](compose, args)
|
await compose.commands['down'](compose, args)
|
||||||
# or
|
# or
|
||||||
compose.commands['down'](argparse.Namespace(foo=123))
|
await compose.commands['down'](argparse.Namespace(foo=123))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Missing Commands (help needed)
|
## Missing Commands (help needed)
|
||||||
|
|
||||||
```
|
```
|
||||||
bundle Generate a Docker bundle from the Compose file
|
bundle Generate a Docker bundle from the Compose file
|
||||||
config Validate and view the Compose file
|
|
||||||
create Create services
|
create Create services
|
||||||
events Receive real time events from containers
|
events Receive real time events from containers
|
||||||
images List images
|
images List images
|
||||||
logs View output from containers
|
|
||||||
port Print the public port for a port binding
|
|
||||||
ps List containers
|
|
||||||
rm Remove stopped containers
|
rm Remove stopped containers
|
||||||
run Run a one-off command
|
|
||||||
scale Set number of containers for a service
|
scale Set number of containers for a service
|
||||||
top Display the running processes
|
top Display the running processes
|
||||||
```
|
```
|
||||||
|
47
README.md
47
README.md
@ -1,6 +1,5 @@
|
|||||||
# Podman Compose
|
# Podman Compose
|
||||||
## [](https://github.com/containers/podman-compose/actions/workflows/pylint.yml) [](https://github.com/containers/podman-compose/actions/workflows/pytest.yml)
|
## [](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.
|
An implementation of [Compose Spec](https://compose-spec.io/) with [Podman](https://podman.io/) backend.
|
||||||
This project focuses on:
|
This project focuses on:
|
||||||
@ -11,7 +10,11 @@ This project focuses on:
|
|||||||
This project only depends on:
|
This project only depends on:
|
||||||
|
|
||||||
* `podman`
|
* `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
|
* Python3
|
||||||
* [PyYAML](https://pyyaml.org/)
|
* [PyYAML](https://pyyaml.org/)
|
||||||
* [python-dotenv](https://pypi.org/project/python-dotenv/)
|
* [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
|
## Installation
|
||||||
|
|
||||||
|
### Pip
|
||||||
|
|
||||||
Install the latest stable version from PyPI:
|
Install the latest stable version from PyPI:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
pip3 install podman-compose
|
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:
|
Or latest development version from GitHub:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
pip3 install https://github.com/containers/podman-compose/archive/devel.tar.gz
|
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:
|
or install from Fedora (starting from f31) repositories:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
sudo dnf install podman-compose
|
sudo dnf install podman-compose
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -75,10 +99,9 @@ sudo dnf install podman-compose
|
|||||||
We have included fully functional sample stacks inside `examples/` directory.
|
We have included fully functional sample stacks inside `examples/` directory.
|
||||||
You can get more examples from [awesome-compose](https://github.com/docker/awesome-compose).
|
You can get more examples from [awesome-compose](https://github.com/docker/awesome-compose).
|
||||||
|
|
||||||
|
|
||||||
A quick example would be
|
A quick example would be
|
||||||
|
|
||||||
```
|
```bash
|
||||||
cd examples/busybox
|
cd examples/busybox
|
||||||
podman-compose --help
|
podman-compose --help
|
||||||
podman-compose up --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
|
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
|
that are meant to test as many cases as we can to make sure we are compatible
|
||||||
|
|
||||||
### Unit tests with pytest
|
### Unit tests with unittest
|
||||||
run a pytest with following command
|
run a unittest with following command
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
python -m pytest pytests
|
python -m unittest pytests/*.py
|
||||||
```
|
```
|
||||||
|
|
||||||
# Contributing guide
|
# Contributing guide
|
||||||
|
33
docs/Changelog-1.1.0.md
Normal file
33
docs/Changelog-1.1.0.md
Normal 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
40
docs/Changelog-1.2.0.md
Normal 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
111
docs/Extensions.md
Normal 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
|
||||||
|
```
|
@ -1,7 +1,7 @@
|
|||||||
# pylint: disable=import-error
|
# pylint: disable=import-error
|
||||||
# pylint: disable=unused-import
|
# pylint: disable=unused-import
|
||||||
import os
|
|
||||||
import asyncio # noqa: F401
|
import asyncio # noqa: F401
|
||||||
|
import os
|
||||||
|
|
||||||
import aioredis
|
import aioredis
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
11
examples/nvidia-smi/docker-compose.yaml
Normal file
11
examples/nvidia-smi/docker-compose.yaml
Normal 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
13
newsfragments/README.txt
Normal 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
|
1387
podman_compose.py
1387
podman_compose.py
File diff suppressed because it is too large
Load Diff
15
pyproject.toml
Normal file
15
pyproject.toml
Normal 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
0
pytests/__init__.py
Normal file
@ -1,67 +1,19 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
import copy
|
import copy
|
||||||
import os
|
import os
|
||||||
import argparse
|
import unittest
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
from podman_compose import normalize_service, PodmanCompose
|
from parameterized import parameterized
|
||||||
|
|
||||||
|
from podman_compose import PodmanCompose
|
||||||
|
|
||||||
|
|
||||||
test_cases_simple = [
|
class TestCanMergeBuild(unittest.TestCase):
|
||||||
({"test": "test"}, {"test": "test"}),
|
@parameterized.expand([
|
||||||
({"build": "."}, {"build": {"context": "."}}),
|
|
||||||
({"build": "./dir-1"}, {"build": {"context": "./dir-1"}}),
|
|
||||||
({"build": {"context": "./dir-1"}}, {"build": {"context": "./dir-1"}}),
|
|
||||||
(
|
|
||||||
{"build": {"dockerfile": "dockerfile-1"}},
|
|
||||||
{"build": {"dockerfile": "dockerfile-1"}},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
{"build": {"context": "./dir-1", "dockerfile": "dockerfile-1"}},
|
|
||||||
{"build": {"context": "./dir-1", "dockerfile": "dockerfile-1"}},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def test_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
|
|
||||||
|
|
||||||
|
|
||||||
test_cases_sub_dir = [
|
|
||||||
({"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():
|
|
||||||
for test_case, expected in copy.deepcopy(test_cases_sub_dir):
|
|
||||||
test_original = copy.deepcopy(test_case)
|
|
||||||
test_case = normalize_service(test_case, sub_dir="./sub_dir")
|
|
||||||
test_result = expected == test_case
|
|
||||||
if not test_result:
|
|
||||||
print("test: ", test_original)
|
|
||||||
print("expected: ", expected)
|
|
||||||
print("actual: ", test_case)
|
|
||||||
assert test_result
|
|
||||||
|
|
||||||
|
|
||||||
test_cases_merges = [
|
|
||||||
({}, {}, {}),
|
({}, {}, {}),
|
||||||
({}, {"test": "test"}, {"test": "test"}),
|
({}, {"test": "test"}, {"test": "test"}),
|
||||||
({"test": "test"}, {}, {"test": "test"}),
|
({"test": "test"}, {}, {"test": "test"}),
|
||||||
@ -116,13 +68,10 @@ test_cases_merges = [
|
|||||||
{"build": {"dockerfile": "./dockerfile-1", "args": ["ENV2=2"]}},
|
{"build": {"dockerfile": "./dockerfile-1", "args": ["ENV2=2"]}},
|
||||||
{"build": {"dockerfile": "./dockerfile-1", "args": ["ENV1=1", "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}}
|
||||||
def test__parse_compose_file_when_multiple_composes() -> None:
|
compose_test_2 = {"services": {"test-service": override}}
|
||||||
for test_input, test_override, expected_result in copy.deepcopy(test_cases_merges):
|
|
||||||
compose_test_1 = {"services": {"test-service": test_input}}
|
|
||||||
compose_test_2 = {"services": {"test-service": test_override}}
|
|
||||||
dump_yaml(compose_test_1, "test-compose-1.yaml")
|
dump_yaml(compose_test_1, "test-compose-1.yaml")
|
||||||
dump_yaml(compose_test_2, "test-compose-2.yaml")
|
dump_yaml(compose_test_2, "test-compose-2.yaml")
|
||||||
|
|
||||||
@ -135,15 +84,53 @@ def test__parse_compose_file_when_multiple_composes() -> None:
|
|||||||
if podman_compose.services:
|
if podman_compose.services:
|
||||||
podman_compose.services["test-service"].pop("_deps")
|
podman_compose.services["test-service"].pop("_deps")
|
||||||
actual_compose = podman_compose.services["test-service"]
|
actual_compose = podman_compose.services["test-service"]
|
||||||
if actual_compose != expected_result:
|
self.assertEqual(actual_compose, expected)
|
||||||
print("compose: ", test_input)
|
|
||||||
print("override: ", test_override)
|
|
||||||
print("expected: ", expected_result)
|
|
||||||
print("actual: ", actual_compose)
|
|
||||||
|
|
||||||
compose_expected = expected_result
|
# $$$ 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")
|
||||||
|
|
||||||
assert compose_expected == actual_compose
|
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:
|
def set_args(podman_compose: PodmanCompose, file_names: list[str]) -> None:
|
||||||
@ -152,7 +139,7 @@ def set_args(podman_compose: PodmanCompose, file_names: list[str]) -> None:
|
|||||||
podman_compose.global_args.project_name = None
|
podman_compose.global_args.project_name = None
|
||||||
podman_compose.global_args.env_file = None
|
podman_compose.global_args.env_file = None
|
||||||
podman_compose.global_args.profile = []
|
podman_compose.global_args.profile = []
|
||||||
podman_compose.global_args.in_pod = True
|
podman_compose.global_args.in_pod_bool = True
|
||||||
podman_compose.global_args.no_normalize = True
|
podman_compose.global_args.no_normalize = True
|
||||||
|
|
||||||
|
|
||||||
@ -161,6 +148,19 @@ def dump_yaml(compose: dict, name: str) -> None:
|
|||||||
yaml.safe_dump(compose, outfile, default_flow_style=False)
|
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:
|
def test_clean_test_yamls() -> None:
|
||||||
test_files = ["test-compose-1.yaml", "test-compose-2.yaml"]
|
test_files = ["test-compose-1.yaml", "test-compose-2.yaml"]
|
||||||
for file in test_files:
|
for file in test_files:
|
||||||
|
@ -1,122 +0,0 @@
|
|||||||
import copy
|
|
||||||
import os
|
|
||||||
import argparse
|
|
||||||
import yaml
|
|
||||||
from podman_compose import normalize_service, PodmanCompose
|
|
||||||
|
|
||||||
test_keys = ["command", "entrypoint"]
|
|
||||||
|
|
||||||
test_cases_normalise_pre_merge = [
|
|
||||||
({"$$$": []}, {"$$$": []}),
|
|
||||||
({"$$$": ["sh"]}, {"$$$": ["sh"]}),
|
|
||||||
({"$$$": ["sh", "-c", "date"]}, {"$$$": ["sh", "-c", "date"]}),
|
|
||||||
({"$$$": "sh"}, {"$$$": ["sh"]}),
|
|
||||||
({"$$$": "sleep infinity"}, {"$$$": ["sleep", "infinity"]}),
|
|
||||||
(
|
|
||||||
{"$$$": "bash -c 'sleep infinity'"},
|
|
||||||
{"$$$": ["bash", "-c", "sleep infinity"]},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
test_cases_merges = [
|
|
||||||
({}, {"$$$": []}, {"$$$": []}),
|
|
||||||
({"$$$": []}, {}, {"$$$": []}),
|
|
||||||
({"$$$": []}, {"$$$": "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 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_normalize_service():
|
|
||||||
for test_input_template, expected_template in test_cases_normalise_pre_merge:
|
|
||||||
for key in test_keys:
|
|
||||||
test_input, _, expected = template_to_expression(
|
|
||||||
test_input_template, {}, expected_template, key
|
|
||||||
)
|
|
||||||
test_input = normalize_service(test_input)
|
|
||||||
test_result = expected == test_input
|
|
||||||
if not test_result:
|
|
||||||
print("base_template: ", test_input_template)
|
|
||||||
print("expected: ", expected)
|
|
||||||
print("actual: ", test_input)
|
|
||||||
assert test_result
|
|
||||||
|
|
||||||
|
|
||||||
def test__parse_compose_file_when_multiple_composes() -> None:
|
|
||||||
for base_template, override_template, expected_template in copy.deepcopy(
|
|
||||||
test_cases_merges
|
|
||||||
):
|
|
||||||
for key in test_keys:
|
|
||||||
base, override, expected = template_to_expression(
|
|
||||||
base_template, override_template, expected_template, key
|
|
||||||
)
|
|
||||||
compose_test_1 = {"services": {"test-service": base}}
|
|
||||||
compose_test_2 = {"services": {"test-service": override}}
|
|
||||||
dump_yaml(compose_test_1, "test-compose-1.yaml")
|
|
||||||
dump_yaml(compose_test_2, "test-compose-2.yaml")
|
|
||||||
|
|
||||||
podman_compose = PodmanCompose()
|
|
||||||
set_args(podman_compose, ["test-compose-1.yaml", "test-compose-2.yaml"])
|
|
||||||
|
|
||||||
podman_compose._parse_compose_file() # pylint: disable=protected-access
|
|
||||||
|
|
||||||
actual = {}
|
|
||||||
if podman_compose.services:
|
|
||||||
podman_compose.services["test-service"].pop("_deps")
|
|
||||||
actual = podman_compose.services["test-service"]
|
|
||||||
if actual != expected:
|
|
||||||
print("compose: ", base)
|
|
||||||
print("override: ", override)
|
|
||||||
print("result: ", expected)
|
|
||||||
|
|
||||||
assert expected == actual
|
|
||||||
|
|
||||||
|
|
||||||
def set_args(podman_compose: PodmanCompose, file_names: list[str]) -> None:
|
|
||||||
podman_compose.global_args = argparse.Namespace()
|
|
||||||
podman_compose.global_args.file = file_names
|
|
||||||
podman_compose.global_args.project_name = None
|
|
||||||
podman_compose.global_args.env_file = None
|
|
||||||
podman_compose.global_args.profile = []
|
|
||||||
podman_compose.global_args.in_pod = True
|
|
||||||
podman_compose.global_args.no_normalize = None
|
|
||||||
|
|
||||||
|
|
||||||
def dump_yaml(compose: dict, name: str) -> None:
|
|
||||||
with open(name, "w", encoding="utf-8") as outfile:
|
|
||||||
yaml.safe_dump(compose, outfile, default_flow_style=False)
|
|
||||||
|
|
||||||
|
|
||||||
def test_clean_test_yamls() -> None:
|
|
||||||
test_files = ["test-compose-1.yaml", "test-compose-2.yaml"]
|
|
||||||
for file in test_files:
|
|
||||||
if os.path.exists(file):
|
|
||||||
os.remove(file)
|
|
46
pytests/test_compose_exec_args.py
Normal file
46
pytests/test_compose_exec_args.py
Normal 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,
|
||||||
|
)
|
76
pytests/test_compose_run_update_container_from_args.py
Normal file
76
pytests/test_compose_run_update_container_from_args.py
Normal 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,
|
||||||
|
)
|
558
pytests/test_container_to_args.py
Normal file
558
pytests/test_container_to_args.py
Normal 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",
|
||||||
|
],
|
||||||
|
)
|
91
pytests/test_container_to_args_secrets.py
Normal file
91
pytests/test_container_to_args_secrets.py
Normal 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))
|
298
pytests/test_get_net_args.py
Normal file
298
pytests/test_get_net_args.py
Normal 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)
|
203
pytests/test_get_network_create_args.py
Normal file
203
pytests/test_get_network_create_args.py
Normal 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)
|
43
pytests/test_normalize_depends_on.py
Normal file
43
pytests/test_normalize_depends_on.py
Normal 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
|
@ -1,19 +1,23 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import copy
|
|
||||||
import os
|
import os
|
||||||
|
import unittest
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
from podman_compose import (
|
from parameterized import parameterized
|
||||||
normalize_service,
|
|
||||||
normalize,
|
from podman_compose import PodmanCompose
|
||||||
normalize_final,
|
from podman_compose import normalize_final
|
||||||
normalize_service_final,
|
from podman_compose import normalize_service_final
|
||||||
PodmanCompose,
|
|
||||||
)
|
|
||||||
|
|
||||||
cwd = os.path.abspath(".")
|
cwd = os.path.abspath(".")
|
||||||
test_cases_simple_normalization = [
|
|
||||||
|
|
||||||
|
class TestNormalizeFinalBuild(unittest.TestCase):
|
||||||
|
cases_simple_normalization = [
|
||||||
({"image": "test-image"}, {"image": "test-image"}),
|
({"image": "test-image"}, {"image": "test-image"}),
|
||||||
(
|
(
|
||||||
{"build": "."},
|
{"build": "."},
|
||||||
@ -96,34 +100,24 @@ test_cases_simple_normalization = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@parameterized.expand(cases_simple_normalization)
|
||||||
#
|
def test_normalize_service_final_returns_absolute_path_in_context(self, input, expected):
|
||||||
# [service.build] is normalised after merges
|
# Tests that [service.build] is normalized after merges
|
||||||
#
|
|
||||||
def test_normalize_service_final_returns_absolute_path_in_context() -> None:
|
|
||||||
project_dir = cwd
|
project_dir = cwd
|
||||||
for test_input, expected_service in copy.deepcopy(test_cases_simple_normalization):
|
self.assertEqual(normalize_service_final(input, project_dir), expected)
|
||||||
actual_service = normalize_service_final(test_input, project_dir)
|
|
||||||
assert expected_service == actual_service
|
|
||||||
|
|
||||||
|
@parameterized.expand(cases_simple_normalization)
|
||||||
def test_normalize_returns_absolute_path_in_context() -> None:
|
def test_normalize_returns_absolute_path_in_context(self, input, expected):
|
||||||
project_dir = cwd
|
project_dir = cwd
|
||||||
for test_input, expected_result in copy.deepcopy(test_cases_simple_normalization):
|
compose_test = {"services": {"test-service": input}}
|
||||||
compose_test = {"services": {"test-service": test_input}}
|
compose_expected = {"services": {"test-service": expected}}
|
||||||
compose_expected = {"services": {"test-service": expected_result}}
|
self.assertEqual(normalize_final(compose_test, project_dir), compose_expected)
|
||||||
actual_compose = normalize_final(compose_test, project_dir)
|
|
||||||
assert compose_expected == actual_compose
|
|
||||||
|
|
||||||
|
@parameterized.expand(cases_simple_normalization)
|
||||||
#
|
def test_parse_compose_file_when_single_compose(self, input, expected):
|
||||||
# running full parse over single compose files
|
compose_test = {"services": {"test-service": input}}
|
||||||
#
|
|
||||||
def test__parse_compose_file_when_single_compose() -> None:
|
|
||||||
for test_input, expected_result in copy.deepcopy(test_cases_simple_normalization):
|
|
||||||
compose_test = {"services": {"test-service": test_input}}
|
|
||||||
dump_yaml(compose_test, "test-compose.yaml")
|
dump_yaml(compose_test, "test-compose.yaml")
|
||||||
|
|
||||||
podman_compose = PodmanCompose()
|
podman_compose = PodmanCompose()
|
||||||
@ -135,14 +129,9 @@ def test__parse_compose_file_when_single_compose() -> None:
|
|||||||
if podman_compose.services:
|
if podman_compose.services:
|
||||||
podman_compose.services["test-service"].pop("_deps")
|
podman_compose.services["test-service"].pop("_deps")
|
||||||
actual_compose = podman_compose.services["test-service"]
|
actual_compose = podman_compose.services["test-service"]
|
||||||
if actual_compose != expected_result:
|
self.assertEqual(actual_compose, expected)
|
||||||
print("compose: ", test_input)
|
|
||||||
print("result: ", expected_result)
|
|
||||||
|
|
||||||
assert expected_result == actual_compose
|
@parameterized.expand([
|
||||||
|
|
||||||
|
|
||||||
test_cases_with_merges = [
|
|
||||||
(
|
(
|
||||||
{},
|
{},
|
||||||
{"build": "."},
|
{"build": "."},
|
||||||
@ -236,18 +225,10 @@ test_cases_with_merges = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
])
|
||||||
|
def test_parse_when_multiple_composes(self, input, override, expected):
|
||||||
|
compose_test_1 = {"services": {"test-service": input}}
|
||||||
#
|
compose_test_2 = {"services": {"test-service": override}}
|
||||||
# running full parse over merged
|
|
||||||
#
|
|
||||||
def test__parse_compose_file_when_multiple_composes() -> None:
|
|
||||||
for test_input, test_override, expected_result in copy.deepcopy(
|
|
||||||
test_cases_with_merges
|
|
||||||
):
|
|
||||||
compose_test_1 = {"services": {"test-service": test_input}}
|
|
||||||
compose_test_2 = {"services": {"test-service": test_override}}
|
|
||||||
dump_yaml(compose_test_1, "test-compose-1.yaml")
|
dump_yaml(compose_test_1, "test-compose-1.yaml")
|
||||||
dump_yaml(compose_test_2, "test-compose-2.yaml")
|
dump_yaml(compose_test_2, "test-compose-2.yaml")
|
||||||
|
|
||||||
@ -264,24 +245,16 @@ def test__parse_compose_file_when_multiple_composes() -> None:
|
|||||||
if podman_compose.services:
|
if podman_compose.services:
|
||||||
podman_compose.services["test-service"].pop("_deps")
|
podman_compose.services["test-service"].pop("_deps")
|
||||||
actual_compose = podman_compose.services["test-service"]
|
actual_compose = podman_compose.services["test-service"]
|
||||||
if actual_compose != expected_result:
|
self.assertEqual(actual_compose, expected)
|
||||||
print("compose: ", test_input)
|
|
||||||
print("override: ", test_override)
|
|
||||||
print("result: ", expected_result)
|
|
||||||
compose_expected = expected_result
|
|
||||||
|
|
||||||
assert compose_expected == actual_compose
|
|
||||||
|
|
||||||
|
|
||||||
def set_args(
|
def set_args(podman_compose: PodmanCompose, file_names: list[str], no_normalize: bool) -> None:
|
||||||
podman_compose: PodmanCompose, file_names: list[str], no_normalize: bool
|
|
||||||
) -> None:
|
|
||||||
podman_compose.global_args = argparse.Namespace()
|
podman_compose.global_args = argparse.Namespace()
|
||||||
podman_compose.global_args.file = file_names
|
podman_compose.global_args.file = file_names
|
||||||
podman_compose.global_args.project_name = None
|
podman_compose.global_args.project_name = None
|
||||||
podman_compose.global_args.env_file = None
|
podman_compose.global_args.env_file = None
|
||||||
podman_compose.global_args.profile = []
|
podman_compose.global_args.profile = []
|
||||||
podman_compose.global_args.in_pod = True
|
podman_compose.global_args.in_pod_bool = True
|
||||||
podman_compose.global_args.no_normalize = no_normalize
|
podman_compose.global_args.no_normalize = no_normalize
|
||||||
|
|
||||||
|
|
||||||
|
70
pytests/test_normalize_service.py
Normal file
70
pytests/test_normalize_service.py
Normal 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)
|
@ -1,21 +1,20 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
# pylint: disable=redefined-outer-name
|
# pylint: disable=redefined-outer-name
|
||||||
import pytest
|
import unittest
|
||||||
|
|
||||||
from podman_compose import parse_short_mount
|
from podman_compose import parse_short_mount
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
class ParseShortMountTests(unittest.TestCase):
|
||||||
def multi_propagation_mount_str():
|
def test_multi_propagation(self):
|
||||||
return "/foo/bar:/baz:U,Z"
|
self.assertEqual(
|
||||||
|
parse_short_mount("/foo/bar:/baz:U,Z", "/"),
|
||||||
|
{
|
||||||
def test_parse_short_mount_multi_propagation(multi_propagation_mount_str):
|
|
||||||
expected = {
|
|
||||||
"type": "bind",
|
"type": "bind",
|
||||||
"source": "/foo/bar",
|
"source": "/foo/bar",
|
||||||
"target": "/baz",
|
"target": "/baz",
|
||||||
"bind": {
|
"bind": {
|
||||||
"propagation": "U,Z",
|
"propagation": "U,Z",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
assert parse_short_mount(multi_propagation_mount_str, "/") == expected
|
)
|
||||||
|
18
setup.py
18
setup.py
@ -1,10 +1,11 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
try:
|
try:
|
||||||
README = open(
|
README = open(os.path.join(os.path.dirname(__file__), "README.md"), encoding="utf-8").read()
|
||||||
os.path.join(os.path.dirname(__file__), "README.md"), encoding="utf-8"
|
|
||||||
).read()
|
|
||||||
except: # noqa: E722 # pylint: disable=bare-except
|
except: # noqa: E722 # pylint: disable=bare-except
|
||||||
README = ""
|
README = ""
|
||||||
|
|
||||||
@ -39,19 +40,10 @@ setup(
|
|||||||
"pyyaml",
|
"pyyaml",
|
||||||
"python-dotenv",
|
"python-dotenv",
|
||||||
],
|
],
|
||||||
extras_require={
|
extras_require={"devel": ["ruff", "pre-commit", "coverage", "parameterized"]},
|
||||||
"devel": [
|
|
||||||
"flake8",
|
|
||||||
"black",
|
|
||||||
"pylint",
|
|
||||||
"pre-commit",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
# test_suite='tests',
|
# test_suite='tests',
|
||||||
# tests_require=[
|
# tests_require=[
|
||||||
# 'coverage',
|
# 'coverage',
|
||||||
# 'pytest-cov',
|
|
||||||
# 'pytest',
|
|
||||||
# 'tox',
|
# 'tox',
|
||||||
# ]
|
# ]
|
||||||
)
|
)
|
||||||
|
@ -1,9 +1,33 @@
|
|||||||
# The order of packages is significant, because pip processes them in the order
|
-e .
|
||||||
# of appearance. Changing the order has an impact on the overall integration
|
coverage==7.4.3
|
||||||
# process, which may cause wedges in the gate later.
|
parameterized==0.9.0
|
||||||
|
pytest==8.0.2
|
||||||
|
tox==4.13.0
|
||||||
|
ruff==0.3.1
|
||||||
|
pylint==3.1.0
|
||||||
|
|
||||||
coverage
|
# The packages below are transitive dependencies of the packages above and are included here
|
||||||
pytest-cov
|
# to make testing reproducible.
|
||||||
pytest
|
# To refresh, create a new virtualenv and do:
|
||||||
tox
|
# pip install -r requirements.txt -r test-requirements.txt
|
||||||
black
|
# 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
12
tests/__init__.py
Normal 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()
|
14
tests/additional_contexts/README.md
Normal file
14
tests/additional_contexts/README.md
Normal 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
|
||||||
|
```
|
1
tests/additional_contexts/data_for_dict/data.txt
Normal file
1
tests/additional_contexts/data_for_dict/data.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Data for dict
|
1
tests/additional_contexts/data_for_list/data.txt
Normal file
1
tests/additional_contexts/data_for_list/data.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Data for list
|
3
tests/additional_contexts/project/Dockerfile
Normal file
3
tests/additional_contexts/project/Dockerfile
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
FROM busybox
|
||||||
|
COPY --from=data data.txt /data/data.txt
|
||||||
|
CMD ["busybox", "cat", "/data/data.txt"]
|
12
tests/additional_contexts/project/docker-compose.yml
Normal file
12
tests/additional_contexts/project/docker-compose.yml
Normal 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
|
6
tests/base_image/Dockerfile
Normal file
6
tests/base_image/Dockerfile
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
FROM docker.io/library/debian:bookworm-slim
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y \
|
||||||
|
dumb-init \
|
||||||
|
busybox \
|
||||||
|
wget
|
9
tests/build_secrets/Dockerfile
Normal file
9
tests/build_secrets/Dockerfile
Normal 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' ]
|
22
tests/build_secrets/docker-compose.yaml
Normal file
22
tests/build_secrets/docker-compose.yaml
Normal 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
|
18
tests/build_secrets/docker-compose.yaml.invalid
Normal file
18
tests/build_secrets/docker-compose.yaml.invalid
Normal 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
|
1
tests/build_secrets/my_secret
Normal file
1
tests/build_secrets/my_secret
Normal file
@ -0,0 +1 @@
|
|||||||
|
important-secret-is-important
|
@ -1,26 +0,0 @@
|
|||||||
"""conftest.py
|
|
||||||
|
|
||||||
Defines global pytest fixtures available to all tests.
|
|
||||||
"""
|
|
||||||
# pylint: disable=redefined-outer-name
|
|
||||||
from pathlib import Path
|
|
||||||
import os
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
|
|
||||||
@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")
|
|
@ -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'
|
||||||
```
|
```
|
||||||
|
@ -1,21 +1,22 @@
|
|||||||
version: "3.7"
|
version: "3.7"
|
||||||
services:
|
services:
|
||||||
web:
|
web:
|
||||||
image: busybox
|
image: nopush/podman-compose-test
|
||||||
command: ["/bin/busybox", "httpd", "-f", "-h", "/etc/", "-p", "8000"]
|
command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", "/etc/", "-p", "8000"]
|
||||||
tmpfs:
|
tmpfs:
|
||||||
- /run
|
- /run
|
||||||
- /tmp
|
- /tmp
|
||||||
sleep:
|
sleep:
|
||||||
image: busybox
|
image: nopush/podman-compose-test
|
||||||
command: ["/bin/busybox", "sh", "-c", "sleep 3600"]
|
command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 3600"]
|
||||||
depends_on: "web"
|
depends_on:
|
||||||
|
- "web"
|
||||||
tmpfs:
|
tmpfs:
|
||||||
- /run
|
- /run
|
||||||
- /tmp
|
- /tmp
|
||||||
sleep2:
|
sleep2:
|
||||||
image: busybox
|
image: nopush/podman-compose-test
|
||||||
command: ["/bin/busybox", "sh", "-c", "sleep 3600"]
|
command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 3600"]
|
||||||
depends_on:
|
depends_on:
|
||||||
- sleep
|
- sleep
|
||||||
tmpfs:
|
tmpfs:
|
||||||
|
2
tests/env-file-tests/.env
Normal file
2
tests/env-file-tests/.env
Normal 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
4
tests/env-file-tests/.gitignore
vendored
Normal 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
|
@ -7,3 +7,31 @@ podman-compose -f project/container-compose.yaml --env-file env-files/project-1.
|
|||||||
```
|
```
|
||||||
podman-compose -f $(pwd)/project/container-compose.yaml --env-file $(pwd)/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
|
||||||
|
```
|
||||||
|
@ -1 +1,3 @@
|
|||||||
ZZVAR1=podman-rocks-123
|
ZZVAR1=podman-rocks-123
|
||||||
|
ZZVAR2=podman-rocks-124
|
||||||
|
ZZVAR3=podman-rocks-125
|
||||||
|
2
tests/env-file-tests/env-files/project-2.env
Normal file
2
tests/env-file-tests/env-files/project-2.env
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
ZZVAR1=podman-rocks-223
|
||||||
|
ZZVAR2=podman-rocks-224
|
2
tests/env-file-tests/project/.env
Normal file
2
tests/env-file-tests/project/.env
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
ZZVAR1='This value is loaded but should be overwritten'
|
||||||
|
ZZVAR2='This value is loaded from .env in project/ directory'
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -1,20 +1,20 @@
|
|||||||
version: "3"
|
version: "3"
|
||||||
services:
|
services:
|
||||||
too_long:
|
too_long:
|
||||||
image: busybox
|
image: nopush/podman-compose-test
|
||||||
command: ["/bin/busybox", "sh", "-c", "sleep 3600; exit 0"]
|
command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 3600; exit 0"]
|
||||||
tmpfs:
|
tmpfs:
|
||||||
- /run
|
- /run
|
||||||
- /tmp
|
- /tmp
|
||||||
sh1:
|
sh1:
|
||||||
image: busybox
|
image: nopush/podman-compose-test
|
||||||
command: ["/bin/busybox", "sh", "-c", "sleep 5; exit 1"]
|
command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 1; exit 1"]
|
||||||
tmpfs:
|
tmpfs:
|
||||||
- /run
|
- /run
|
||||||
- /tmp
|
- /tmp
|
||||||
sh2:
|
sh2:
|
||||||
image: busybox
|
image: nopush/podman-compose-test
|
||||||
command: ["/bin/busybox", "sh", "-c", "sleep 5; exit 2"]
|
command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 1; exit 2"]
|
||||||
tmpfs:
|
tmpfs:
|
||||||
- /run
|
- /run
|
||||||
- /tmp
|
- /tmp
|
||||||
|
@ -2,6 +2,6 @@ services:
|
|||||||
webapp_default:
|
webapp_default:
|
||||||
|
|
||||||
webapp_special:
|
webapp_special:
|
||||||
image: busybox
|
image: nopush/podman-compose-test
|
||||||
volumes:
|
volumes:
|
||||||
- "/data"
|
- "/data"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
version: "3"
|
version: "3"
|
||||||
services:
|
services:
|
||||||
web:
|
web:
|
||||||
image: busybox
|
image: nopush/podman-compose-test
|
||||||
extends:
|
extends:
|
||||||
file: common-services.yml
|
file: common-services.yml
|
||||||
service: webapp_default
|
service: webapp_default
|
||||||
|
@ -1 +1 @@
|
|||||||
FROM busybox as base
|
FROM nopush/podman-compose-test as base
|
||||||
|
9
tests/in_pod/custom_x-podman_false/docker-compose.yml
Normal file
9
tests/in_pod/custom_x-podman_false/docker-compose.yml
Normal 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
|
@ -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"]
|
9
tests/in_pod/custom_x-podman_true/docker-compose.yml
Normal file
9
tests/in_pod/custom_x-podman_true/docker-compose.yml
Normal 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
|
@ -2,6 +2,6 @@ version: '3.6'
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
web:
|
web:
|
||||||
image: busybox
|
image: nopush/podman-compose-test
|
||||||
command: ["/bin/busybox", "httpd", "-f", "-h", ".", "-p", "8003"]
|
command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", ".", "-p", "8003"]
|
||||||
|
|
||||||
|
6
tests/include/docker-compose.extend.yaml
Normal file
6
tests/include/docker-compose.extend.yaml
Normal 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"]
|
@ -2,3 +2,4 @@ version: '3.6'
|
|||||||
|
|
||||||
include:
|
include:
|
||||||
- docker-compose.base.yaml
|
- docker-compose.base.yaml
|
||||||
|
- docker-compose.extend.yaml
|
||||||
|
15
tests/ipam_default/docker-compose.yaml
Normal file
15
tests/ipam_default/docker-compose.yaml
Normal 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"]
|
||||||
|
|
61
tests/nets_test_ip/docker-compose.yml
Normal file
61
tests/nets_test_ip/docker-compose.yml
Normal 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
|
1
tests/nets_test_ip/test1.txt
Normal file
1
tests/nets_test_ip/test1.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
test1
|
1
tests/nets_test_ip/test2.txt
Normal file
1
tests/nets_test_ip/test2.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
test2
|
1
tests/nets_test_ip/test3.txt
Normal file
1
tests/nets_test_ip/test3.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
test3
|
1
tests/nets_test_ip/test4.txt
Normal file
1
tests/nets_test_ip/test4.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
test4
|
6
tests/pid/docker-compose.yml
Normal file
6
tests/pid/docker-compose.yml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
serv:
|
||||||
|
image: busybox
|
||||||
|
pid: host
|
||||||
|
command: sh -c "ps all"
|
@ -1,18 +1,18 @@
|
|||||||
version: "3"
|
version: "3"
|
||||||
services:
|
services:
|
||||||
web1:
|
web1:
|
||||||
image: busybox
|
image: nopush/podman-compose-test
|
||||||
hostname: web1
|
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
|
working_dir: /var/www/html
|
||||||
ports:
|
ports:
|
||||||
- 8001:8001
|
- 8001:8001
|
||||||
volumes:
|
volumes:
|
||||||
- ./test1.txt:/var/www/html/index.txt:ro,z
|
- ./test1.txt:/var/www/html/index.txt:ro,z
|
||||||
web2:
|
web2:
|
||||||
image: busybox
|
image: nopush/podman-compose-test
|
||||||
hostname: web2
|
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
|
working_dir: /var/www/html
|
||||||
ports:
|
ports:
|
||||||
- 8002:8002
|
- 8002:8002
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
version: "3"
|
version: "3"
|
||||||
services:
|
services:
|
||||||
default-service:
|
default-service:
|
||||||
image: busybox
|
image: nopush/podman-compose-test
|
||||||
command: ["/bin/busybox", "httpd", "-f", "-h", "/etc/", "-p", "8000"]
|
command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", "/etc/", "-p", "8000"]
|
||||||
tmpfs:
|
tmpfs:
|
||||||
- /run
|
- /run
|
||||||
- /tmp
|
- /tmp
|
||||||
service-1:
|
service-1:
|
||||||
image: busybox
|
image: nopush/podman-compose-test
|
||||||
command: ["/bin/busybox", "httpd", "-f", "-h", "/etc/", "-p", "8000"]
|
command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", "/etc/", "-p", "8000"]
|
||||||
tmpfs:
|
tmpfs:
|
||||||
- /run
|
- /run
|
||||||
- /tmp
|
- /tmp
|
||||||
profiles:
|
profiles:
|
||||||
- profile-1
|
- profile-1
|
||||||
service-2:
|
service-2:
|
||||||
image: busybox
|
image: nopush/podman-compose-test
|
||||||
command: ["/bin/busybox", "httpd", "-f", "-h", "/etc/", "-p", "8000"]
|
command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", "/etc/", "-p", "8000"]
|
||||||
tmpfs:
|
tmpfs:
|
||||||
- /run
|
- /run
|
||||||
- /tmp
|
- /tmp
|
||||||
|
@ -31,6 +31,9 @@ services:
|
|||||||
uid: '103'
|
uid: '103'
|
||||||
gid: '103'
|
gid: '103'
|
||||||
mode: 400
|
mode: 400
|
||||||
|
- source: my_secret
|
||||||
|
target: ENV_SECRET
|
||||||
|
type: env
|
||||||
|
|
||||||
secrets:
|
secrets:
|
||||||
my_secret:
|
my_secret:
|
||||||
@ -43,4 +46,3 @@ secrets:
|
|||||||
name: my_secret_3
|
name: my_secret_3
|
||||||
file_secret:
|
file_secret:
|
||||||
file: ./my_secret
|
file: ./my_secret
|
||||||
|
|
||||||
|
@ -4,3 +4,4 @@ ls -la /run/secrets/*
|
|||||||
ls -la /etc/custom_location
|
ls -la /etc/custom_location
|
||||||
cat /run/secrets/*
|
cat /run/secrets/*
|
||||||
cat /etc/custom_location
|
cat /etc/custom_location
|
||||||
|
env | grep SECRET
|
||||||
|
14
tests/selinux/docker-compose.yml
Normal file
14
tests/selinux/docker-compose.yml
Normal 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"
|
||||||
|
|
@ -1,18 +1,29 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import subprocess
|
|
||||||
|
from .test_utils import RunSubprocessMixin
|
||||||
|
|
||||||
|
|
||||||
def capture(command):
|
def base_path():
|
||||||
proc = subprocess.Popen(
|
"""Returns the base path for the project"""
|
||||||
command,
|
return Path(__file__).parent.parent
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
)
|
|
||||||
out, err = proc.communicate()
|
|
||||||
return out, err, proc.returncode
|
|
||||||
|
|
||||||
|
|
||||||
def test_podman_compose_extends_w_file_subdir():
|
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")
|
||||||
|
|
||||||
|
|
||||||
|
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
|
Test that podman-compose can execute podman-compose -f <file> up with extended File which
|
||||||
includes a build context
|
includes a build context
|
||||||
@ -21,7 +32,8 @@ def test_podman_compose_extends_w_file_subdir():
|
|||||||
main_path = Path(__file__).parent.parent
|
main_path = Path(__file__).parent.parent
|
||||||
|
|
||||||
command_up = [
|
command_up = [
|
||||||
"python3",
|
"coverage",
|
||||||
|
"run",
|
||||||
str(main_path.joinpath("podman_compose.py")),
|
str(main_path.joinpath("podman_compose.py")),
|
||||||
"-f",
|
"-f",
|
||||||
str(main_path.joinpath("tests", "extends_w_file_subdir", "docker-compose.yml")),
|
str(main_path.joinpath("tests", "extends_w_file_subdir", "docker-compose.yml")),
|
||||||
@ -30,42 +42,44 @@ def test_podman_compose_extends_w_file_subdir():
|
|||||||
]
|
]
|
||||||
|
|
||||||
command_check_container = [
|
command_check_container = [
|
||||||
"podman",
|
"coverage",
|
||||||
"container",
|
"run",
|
||||||
|
str(main_path.joinpath("podman_compose.py")),
|
||||||
|
"-f",
|
||||||
|
str(main_path.joinpath("tests", "extends_w_file_subdir", "docker-compose.yml")),
|
||||||
"ps",
|
"ps",
|
||||||
"--all",
|
|
||||||
"--format",
|
"--format",
|
||||||
'"{{.Image}}"',
|
'{{.Image}}',
|
||||||
]
|
]
|
||||||
|
|
||||||
command_down = [
|
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",
|
"podman",
|
||||||
"rmi",
|
"rmi",
|
||||||
"--force",
|
"--force",
|
||||||
"localhost/subdir_test:me",
|
"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
|
# check container did not exists anymore
|
||||||
out, _, returncode = capture(command_check_container)
|
out, _ = self.run_subprocess_assert_returncode(command_check_container)
|
||||||
assert 0 == returncode
|
self.assertEqual(out, b'')
|
||||||
assert out == b""
|
|
||||||
|
|
||||||
|
def test_extends_w_empty_service(self):
|
||||||
def test_podman_compose_extends_w_empty_service():
|
|
||||||
"""
|
"""
|
||||||
Test that podman-compose can execute podman-compose -f <file> up with extended File which
|
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.)
|
includes an empty service. (e.g. if the file is used as placeholder for more complex
|
||||||
:return:
|
configurations.)
|
||||||
"""
|
"""
|
||||||
main_path = Path(__file__).parent.parent
|
main_path = Path(__file__).parent.parent
|
||||||
|
|
||||||
@ -73,12 +87,9 @@ def test_podman_compose_extends_w_empty_service():
|
|||||||
"python3",
|
"python3",
|
||||||
str(main_path.joinpath("podman_compose.py")),
|
str(main_path.joinpath("podman_compose.py")),
|
||||||
"-f",
|
"-f",
|
||||||
str(
|
str(main_path.joinpath("tests", "extends_w_empty_service", "docker-compose.yml")),
|
||||||
main_path.joinpath("tests", "extends_w_empty_service", "docker-compose.yml")
|
|
||||||
),
|
|
||||||
"up",
|
"up",
|
||||||
"-d",
|
"-d",
|
||||||
]
|
]
|
||||||
|
|
||||||
_, _, returncode = capture(command_up)
|
self.run_subprocess_assert_returncode(command_up)
|
||||||
assert 0 == returncode
|
|
||||||
|
44
tests/test_podman_compose_additional_contexts.py
Normal file
44
tests/test_podman_compose_additional_contexts.py
Normal 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)
|
90
tests/test_podman_compose_build_secrets.py
Normal file
90
tests/test_podman_compose_build_secrets.py
Normal 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,
|
||||||
|
)
|
93
tests/test_podman_compose_build_ulimits.py
Normal file
93
tests/test_podman_compose_build_ulimits.py
Normal 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)
|
@ -1,40 +1,49 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
"""
|
"""
|
||||||
test_podman_compose_config.py
|
test_podman_compose_config.py
|
||||||
|
|
||||||
Tests the podman-compose config command which is used to return defined compose services.
|
Tests the podman-compose config command which is used to return defined compose services.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# pylint: disable=redefined-outer-name
|
# pylint: disable=redefined-outer-name
|
||||||
import os
|
import os
|
||||||
from test_podman_compose import capture
|
import unittest
|
||||||
import pytest
|
|
||||||
|
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():
|
||||||
def profile_compose_file(test_path):
|
|
||||||
""" "Returns the path to the `profile` compose file used for this test module"""
|
""" "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):
|
class TestComposeConfig(unittest.TestCase, RunSubprocessMixin):
|
||||||
|
def test_config_no_profiles(self):
|
||||||
"""
|
"""
|
||||||
Tests podman-compose config command without profile enablement.
|
Tests podman-compose config command without profile enablement.
|
||||||
|
|
||||||
: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"]
|
config_cmd = [
|
||||||
|
"coverage",
|
||||||
|
"run",
|
||||||
|
podman_compose_path(),
|
||||||
|
"-f",
|
||||||
|
profile_compose_file(),
|
||||||
|
"config",
|
||||||
|
]
|
||||||
|
|
||||||
out, _, return_code = capture(config_cmd)
|
out, _ = self.run_subprocess_assert_returncode(config_cmd)
|
||||||
assert return_code == 0
|
|
||||||
|
|
||||||
string_output = out.decode("utf-8")
|
string_output = out.decode("utf-8")
|
||||||
assert "default-service" in string_output
|
self.assertIn("default-service", string_output)
|
||||||
assert "service-1" not in string_output
|
self.assertNotIn("service-1", string_output)
|
||||||
assert "service-2" not in string_output
|
self.assertNotIn("service-2", string_output)
|
||||||
|
|
||||||
|
@parameterized.expand(
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"profiles, expected_services",
|
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
["--profile", "profile-1", "config"],
|
["--profile", "profile-1", "config"],
|
||||||
@ -49,30 +58,25 @@ def test_config_no_profiles(podman_compose_path, profile_compose_file):
|
|||||||
{"default-service": True, "service-1": True, "service-2": True},
|
{"default-service": True, "service-1": True, "service-2": True},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_config_profiles(
|
def test_config_profiles(self, profiles, expected_services):
|
||||||
podman_compose_path, profile_compose_file, profiles, expected_services
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Tests podman-compose
|
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 profiles: The enabled profiles for the parameterized test.
|
||||||
:param expected_services: Dictionary used to model the expected "enabled" services in the profile.
|
:param expected_services: Dictionary used to model the expected "enabled" services in the
|
||||||
Key = service name, Value = True if the service is enabled, otherwise False.
|
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 = ["coverage", "run", podman_compose_path(), "-f", profile_compose_file()]
|
||||||
config_cmd.extend(profiles)
|
config_cmd.extend(profiles)
|
||||||
|
|
||||||
out, _, return_code = capture(config_cmd)
|
out, _ = self.run_subprocess_assert_returncode(config_cmd)
|
||||||
assert return_code == 0
|
|
||||||
|
|
||||||
actual_output = out.decode("utf-8")
|
actual_output = out.decode("utf-8")
|
||||||
|
|
||||||
assert len(expected_services) == 3
|
self.assertEqual(len(expected_services), 3)
|
||||||
|
|
||||||
actual_services = {}
|
actual_services = {}
|
||||||
for service, _ in expected_services.items():
|
for service, _ in expected_services.items():
|
||||||
actual_services[service] = service in actual_output
|
actual_services[service] = service in actual_output
|
||||||
|
|
||||||
assert expected_services == actual_services
|
self.assertEqual(expected_services, actual_services)
|
||||||
|
442
tests/test_podman_compose_in_pod.py
Normal file
442
tests/test_podman_compose_in_pod.py
Normal 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)
|
@ -1,18 +1,13 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
|
import unittest
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import subprocess
|
|
||||||
|
from .test_utils import RunSubprocessMixin
|
||||||
|
|
||||||
|
|
||||||
def capture(command):
|
class TestPodmanComposeInclude(unittest.TestCase, RunSubprocessMixin):
|
||||||
proc = subprocess.Popen(
|
def test_podman_compose_include(self):
|
||||||
command,
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
)
|
|
||||||
out, err = proc.communicate()
|
|
||||||
return out, err, proc.returncode
|
|
||||||
|
|
||||||
|
|
||||||
def test_podman_compose_include():
|
|
||||||
"""
|
"""
|
||||||
Test that podman-compose can execute podman-compose -f <file> up with include
|
Test that podman-compose can execute podman-compose -f <file> up with include
|
||||||
:return:
|
:return:
|
||||||
@ -20,7 +15,8 @@ def test_podman_compose_include():
|
|||||||
main_path = Path(__file__).parent.parent
|
main_path = Path(__file__).parent.parent
|
||||||
|
|
||||||
command_up = [
|
command_up = [
|
||||||
"python3",
|
"coverage",
|
||||||
|
"run",
|
||||||
str(main_path.joinpath("podman_compose.py")),
|
str(main_path.joinpath("podman_compose.py")),
|
||||||
"-f",
|
"-f",
|
||||||
str(main_path.joinpath("tests", "include", "docker-compose.yaml")),
|
str(main_path.joinpath("tests", "include", "docker-compose.yaml")),
|
||||||
@ -48,24 +44,21 @@ def test_podman_compose_include():
|
|||||||
'"{{.ID}}"',
|
'"{{.ID}}"',
|
||||||
]
|
]
|
||||||
|
|
||||||
command_down = ["podman", "rm", "--force", "CONTAINER_ID"]
|
command_down = ["podman", "rm", "--force"]
|
||||||
|
|
||||||
out, _, returncode = capture(command_up)
|
self.run_subprocess_assert_returncode(command_up)
|
||||||
assert 0 == returncode
|
out, _ = self.run_subprocess_assert_returncode(command_check_container)
|
||||||
out, _, returncode = capture(command_check_container)
|
expected_output = b'"localhost/nopush/podman-compose-test:latest"\n' * 2
|
||||||
assert 0 == returncode
|
self.assertEqual(out, expected_output)
|
||||||
assert out == b'"docker.io/library/busybox:latest"\n'
|
|
||||||
# Get container ID to remove it
|
# Get container ID to remove it
|
||||||
out, _, returncode = capture(command_container_id)
|
out, _ = self.run_subprocess_assert_returncode(command_container_id)
|
||||||
assert 0 == returncode
|
self.assertNotEqual(out, b"")
|
||||||
assert out != b""
|
container_ids = out.decode().strip().split("\n")
|
||||||
container_id = out.decode().strip().replace('"', "")
|
container_ids = [container_id.replace('"', "") for container_id in container_ids]
|
||||||
command_down[3] = container_id
|
command_down.extend(container_ids)
|
||||||
out, _, returncode = capture(command_down)
|
out, _ = self.run_subprocess_assert_returncode(command_down)
|
||||||
# cleanup test image(tags)
|
# cleanup test image(tags)
|
||||||
assert 0 == returncode
|
self.assertNotEqual(out, b"")
|
||||||
assert out != b""
|
|
||||||
# check container did not exists anymore
|
# check container did not exists anymore
|
||||||
out, _, returncode = capture(command_check_container)
|
out, _ = self.run_subprocess_assert_returncode(command_check_container)
|
||||||
assert 0 == returncode
|
self.assertEqual(out, b"")
|
||||||
assert out == b""
|
|
||||||
|
116
tests/test_podman_compose_networks.py
Normal file
116
tests/test_podman_compose_networks.py
Normal 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'))
|
189
tests/test_podman_compose_tests.py
Normal file
189
tests/test_podman_compose_tests.py
Normal 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,
|
||||||
|
)
|
@ -1,47 +1,50 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
"""
|
"""
|
||||||
test_podman_compose_up_down.py
|
test_podman_compose_up_down.py
|
||||||
|
|
||||||
Tests the podman compose up and down commands used to create and remove services.
|
Tests the podman compose up and down commands used to create and remove services.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# pylint: disable=redefined-outer-name
|
# pylint: disable=redefined-outer-name
|
||||||
import os
|
import os
|
||||||
from test_podman_compose import capture
|
import unittest
|
||||||
import pytest
|
|
||||||
|
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():
|
||||||
def profile_compose_file(test_path):
|
|
||||||
""" "Returns the path to the `profile` compose file used for this test module"""
|
""" "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)
|
class TestUpDown(unittest.TestCase, RunSubprocessMixin):
|
||||||
def teardown(podman_compose_path, profile_compose_file):
|
def tearDown(self):
|
||||||
"""
|
"""
|
||||||
Ensures that the services within the "profile compose file" are removed between each test case.
|
Ensures that the services within the "profile compose file" are removed between each 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
|
# run the test case
|
||||||
yield
|
|
||||||
|
|
||||||
down_cmd = [
|
down_cmd = [
|
||||||
"python3",
|
"coverage",
|
||||||
podman_compose_path,
|
"run",
|
||||||
|
podman_compose_path(),
|
||||||
"--profile",
|
"--profile",
|
||||||
"profile-1",
|
"profile-1",
|
||||||
"--profile",
|
"--profile",
|
||||||
"profile-2",
|
"profile-2",
|
||||||
"-f",
|
"-f",
|
||||||
profile_compose_file,
|
profile_compose_file(),
|
||||||
"down",
|
"down",
|
||||||
]
|
]
|
||||||
capture(down_cmd)
|
self.run_subprocess(down_cmd)
|
||||||
|
|
||||||
|
@parameterized.expand(
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"profiles, expected_services",
|
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
["--profile", "profile-1", "up", "-d"],
|
["--profile", "profile-1", "up", "-d"],
|
||||||
@ -56,18 +59,18 @@ def teardown(podman_compose_path, profile_compose_file):
|
|||||||
{"default-service": True, "service-1": True, "service-2": True},
|
{"default-service": True, "service-1": True, "service-2": True},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_up(podman_compose_path, profile_compose_file, profiles, expected_services):
|
def test_up(self, profiles, expected_services):
|
||||||
up_cmd = [
|
up_cmd = [
|
||||||
"python3",
|
"coverage",
|
||||||
podman_compose_path,
|
"run",
|
||||||
|
podman_compose_path(),
|
||||||
"-f",
|
"-f",
|
||||||
profile_compose_file,
|
profile_compose_file(),
|
||||||
]
|
]
|
||||||
up_cmd.extend(profiles)
|
up_cmd.extend(profiles)
|
||||||
|
|
||||||
out, _, return_code = capture(up_cmd)
|
self.run_subprocess_assert_returncode(up_cmd)
|
||||||
assert return_code == 0
|
|
||||||
|
|
||||||
check_cmd = [
|
check_cmd = [
|
||||||
"podman",
|
"podman",
|
||||||
@ -76,14 +79,13 @@ def test_up(podman_compose_path, profile_compose_file, profiles, expected_servic
|
|||||||
"--format",
|
"--format",
|
||||||
'"{{.Names}}"',
|
'"{{.Names}}"',
|
||||||
]
|
]
|
||||||
out, _, return_code = capture(check_cmd)
|
out, _ = self.run_subprocess_assert_returncode(check_cmd)
|
||||||
assert return_code == 0
|
|
||||||
|
|
||||||
assert len(expected_services) == 3
|
self.assertEqual(len(expected_services), 3)
|
||||||
actual_output = out.decode("utf-8")
|
actual_output = out.decode("utf-8")
|
||||||
|
|
||||||
actual_services = {}
|
actual_services = {}
|
||||||
for service, _ in expected_services.items():
|
for service, _ in expected_services.items():
|
||||||
actual_services[service] = service in actual_output
|
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
38
tests/test_utils.py
Normal 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
|
5
tests/ulimit_build/Dockerfile
Normal file
5
tests/ulimit_build/Dockerfile
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
FROM busybox
|
||||||
|
|
||||||
|
COPY ./ulimit.sh /bin/ulimit.sh
|
||||||
|
|
||||||
|
RUN /bin/ulimit.sh
|
26
tests/ulimit_build/docker-compose.yaml
Normal file
26
tests/ulimit_build/docker-compose.yaml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
ulimit1:
|
||||||
|
image: ulimit_build_test
|
||||||
|
build:
|
||||||
|
context: ./
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
ulimits: nofile=1001
|
||||||
|
ulimit2:
|
||||||
|
image: ulimit_build_test
|
||||||
|
build:
|
||||||
|
context: ./
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
ulimits:
|
||||||
|
- nproc=1002:2002
|
||||||
|
- nofile=1002
|
||||||
|
ulimit3:
|
||||||
|
image: ulimit_build_test
|
||||||
|
build:
|
||||||
|
context: ./
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
ulimits:
|
||||||
|
nofile: 1003
|
||||||
|
nproc:
|
||||||
|
soft: 1003
|
||||||
|
hard: 2003
|
6
tests/ulimit_build/ulimit.sh
Executable file
6
tests/ulimit_build/ulimit.sh
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
echo "soft process limit:" $(ulimit -S -u)
|
||||||
|
echo "hard process limit:" $(ulimit -H -u)
|
||||||
|
echo "soft nofile limit:" $(ulimit -S -n)
|
||||||
|
echo "hard nofile limit:" $(ulimit -H -n)
|
@ -1,8 +1,8 @@
|
|||||||
version: "3"
|
version: "3"
|
||||||
services:
|
services:
|
||||||
web:
|
web:
|
||||||
image: busybox
|
image: nopush/podman-compose-test
|
||||||
command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8000"]
|
command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8000"]
|
||||||
working_dir: /var/www/html
|
working_dir: /var/www/html
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
@ -11,21 +11,21 @@ services:
|
|||||||
- /run
|
- /run
|
||||||
- /tmp
|
- /tmp
|
||||||
web1:
|
web1:
|
||||||
image: busybox
|
image: nopush/podman-compose-test
|
||||||
command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"]
|
command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"]
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
working_dir: /var/www/html
|
working_dir: /var/www/html
|
||||||
volumes:
|
volumes:
|
||||||
- myvol1:/var/www/html:ro,z
|
- myvol1:/var/www/html:ro,z
|
||||||
web2:
|
web2:
|
||||||
image: busybox
|
image: nopush/podman-compose-test
|
||||||
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
|
working_dir: /var/www/html
|
||||||
volumes:
|
volumes:
|
||||||
- myvol2:/var/www/html:ro
|
- myvol2:/var/www/html:ro
|
||||||
web3:
|
web3:
|
||||||
image: busybox
|
image: nopush/podman-compose-test
|
||||||
command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8003"]
|
command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8003"]
|
||||||
working_dir: /var/www/html
|
working_dir: /var/www/html
|
||||||
volumes:
|
volumes:
|
||||||
- myvol2:/var/www/html
|
- myvol2:/var/www/html
|
||||||
|
Reference in New Issue
Block a user