├── .codespellignore ├── .codespellrc ├── .coveragerc ├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── codespell.yml │ ├── release.yml │ ├── static-checks.yml │ └── test.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .pylintrc ├── CODE-OF-CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── RELEASING.md ├── SECURITY.md ├── completion └── bash │ └── podman-compose ├── docs ├── Changelog-1.1.0.md ├── Changelog-1.2.0.md ├── Changelog-1.3.0.md ├── Changelog-1.4.0.md ├── Extensions.md └── Mappings.md ├── examples ├── awx17 │ ├── README.md │ └── roles │ │ └── local_docker │ │ ├── defaults │ │ └── main.yml │ │ ├── tasks │ │ ├── compose.yml │ │ ├── main.yml │ │ ├── set_image.yml │ │ └── upgrade_postgres.yml │ │ └── templates │ │ ├── credentials.py.j2 │ │ ├── docker-compose.yml.j2 │ │ ├── environment.sh.j2 │ │ ├── nginx.conf.j2 │ │ └── redis.conf.j2 ├── awx3 │ └── docker-compose.yml ├── azure-vote │ ├── README.md │ └── docker-compose.yaml ├── busybox │ └── docker-compose.yaml ├── docker-inline │ └── docker-compose.yml ├── echo │ ├── README.md │ └── docker-compose.yaml ├── hello-app-redis │ ├── README.md │ └── docker-compose.yaml ├── hello-app │ ├── README.md │ └── docker-compose.yaml ├── hello-python │ ├── Dockerfile │ ├── README.md │ ├── app │ │ ├── __init__.py │ │ └── web.py │ ├── docker-compose.yaml │ └── requirements.txt ├── nodeproj │ ├── .eslintrc.json │ ├── .gitignore │ ├── .home │ │ └── .gitignore │ ├── README.md │ ├── containers │ │ └── node16-runtime │ │ │ └── Dockerfile │ ├── docker-compose.yml │ ├── example.env │ ├── example.local.env │ ├── index.js │ ├── jsconfig.json │ ├── lib │ │ └── commands │ │ │ ├── task.js │ │ │ └── web.js │ ├── package.json │ └── public │ │ └── index.html ├── nvidia-smi │ └── docker-compose.yaml └── wordpress │ └── docker-compose.yaml ├── newsfragments ├── README.txt ├── compose-systemd-unregister.feature ├── custom-pod-name-argument.change ├── fix-build-ssh-path-to-be-relative.bugfix ├── fix-cmd-healtchecks.bugfix └── io-podman-compose-service-label.feature ├── podman_compose.py ├── pyproject.toml ├── requirements.txt ├── scripts ├── Changelog-template.jinja ├── clean_up.sh ├── download_and_build_podman-compose.sh ├── generate_binary_using_dockerfile.sh ├── make_release.sh ├── make_release_notes.sh ├── make_release_upload.sh └── uninstall.sh ├── setup.cfg ├── setup.py ├── test-requirements.txt └── tests ├── integration ├── __init__.py ├── abort │ ├── __init__.py │ ├── docker-compose-fail-first.yaml │ ├── docker-compose-fail-none.yaml │ ├── docker-compose-fail-second.yaml │ ├── docker-compose-fail-simultaneous.yaml │ └── test_podman_compose_abort.py ├── additional_contexts │ ├── README.md │ ├── __init__.py │ ├── data_for_dict │ │ └── data.txt │ ├── data_for_list │ │ └── data.txt │ ├── project │ │ ├── Dockerfile │ │ └── docker-compose.yml │ └── test_podman_compose_additional_contexts.py ├── base_image │ └── Dockerfile ├── build │ ├── __init__.py │ ├── context │ │ ├── Dockerfile │ │ └── Dockerfile-alt │ ├── docker-compose.yml │ ├── git_url_context │ │ ├── __init__.py │ │ ├── docker-compose.yml │ │ └── test_podman_compose_build_git_url_context.py │ └── test_podman_compose_build.py ├── build_fail │ ├── __init__.py │ ├── context │ │ └── Dockerfile │ ├── context_no_file │ │ └── NotDockerfile │ ├── docker-compose.yml │ └── test_podman_compose_build_fail.py ├── build_fail_multi │ ├── __init__.py │ ├── bad │ │ └── Dockerfile │ ├── docker-compose.yml │ ├── good │ │ └── Dockerfile │ └── test_podman_compose_build_fail_multi.py ├── build_labels │ ├── __init__.py │ ├── context │ │ └── Dockerfile │ ├── docker-compose.yml │ └── test_build_labels.py ├── build_secrets │ ├── Dockerfile │ ├── __init__.py │ ├── docker-compose.yaml │ ├── docker-compose.yaml.invalid │ ├── my_secret │ └── test_podman_compose_build_secrets.py ├── build_ssh │ ├── __init__.py │ ├── context │ │ └── Dockerfile │ ├── docker-compose.yml │ ├── id_ed25519_dummy │ └── test_build_ssh.py ├── default_net_behavior │ ├── __init__.py │ ├── docker-compose_no_nets.yaml │ ├── docker-compose_no_nets_compat.yaml │ ├── docker-compose_one_net.yaml │ ├── docker-compose_one_net_compat.yaml │ ├── docker-compose_two_nets.yaml │ ├── docker-compose_two_nets_compat.yaml │ ├── docker-compose_with_default.yaml │ ├── docker-compose_with_default_compat.yaml │ └── test_podman_compose_default_net_behavior.py ├── deps │ ├── __init__.py │ ├── docker-compose-conditional-fails.yaml │ ├── docker-compose-conditional-healthy.yaml │ ├── docker-compose-conditional-succeeds.yaml │ ├── docker-compose.yaml │ └── test_podman_compose_deps.py ├── env_file_tests │ ├── .env │ ├── .gitignore │ ├── __init__.py │ ├── env-files │ │ ├── project-1.env │ │ └── project-2.env │ ├── project │ │ ├── .env │ │ ├── container-compose.env-file-flat.yaml │ │ ├── container-compose.env-file-obj-optional-exists.yaml │ │ ├── container-compose.env-file-obj-optional-missing.yaml │ │ ├── container-compose.env-file-obj.yaml │ │ ├── container-compose.load-.env-in-project.yaml │ │ └── container-compose.yaml │ └── test_podman_compose_env_file.py ├── env_tests │ ├── __init__.py │ ├── container-compose.yml │ └── test_podman_compose_env.py ├── exit_from │ ├── __init__.py │ ├── docker-compose.yaml │ └── test_podman_compose_exit_from.py ├── extends │ ├── __init__.py │ ├── docker-compose.yaml │ └── test_podman_compose_extends.py ├── extends_w_empty_service │ ├── __init__.py │ ├── common-services.yml │ ├── docker-compose.yml │ └── test_podman_compose_extends_w_empty_service.py ├── extends_w_file │ ├── Dockerfile │ ├── __init__.py │ ├── common-services.yml │ ├── docker-compose.yml │ └── test_podman_compose_extends_w_file.py ├── extends_w_file_subdir │ ├── __init__.py │ ├── docker-compose.yml │ ├── sub │ │ ├── docker-compose.yml │ │ └── docker │ │ │ └── example │ │ │ └── Dockerfile │ └── test_podman_compose_extends_w_file_subdir.py ├── filesystem │ ├── __init__.py │ ├── compose_symlink │ │ ├── docker-compose.yml │ │ └── file │ ├── compose_symlink_dest │ │ ├── docker-compose.yml │ │ └── file │ └── test_podman_compose_filesystem.py ├── in_pod │ ├── __init__.py │ ├── custom_x-podman_custom_name │ │ └── docker-compose.yml │ ├── custom_x-podman_false │ │ └── docker-compose.yml │ ├── custom_x-podman_not_exists │ │ └── docker-compose.yml │ ├── custom_x-podman_true │ │ └── docker-compose.yml │ └── test_podman_compose_in_pod.py ├── include │ ├── __init__.py │ ├── docker-compose.base.yaml │ ├── docker-compose.extend.yaml │ ├── docker-compose.yaml │ └── test_podman_compose_include.py ├── interpolation │ ├── .env │ ├── __init__.py │ ├── docker-compose-colon-question-error.yml │ ├── docker-compose-question-error.yml │ ├── docker-compose.yml │ └── test_podman_compose_interpolation.py ├── ipam_default │ ├── __init__.py │ ├── docker-compose.yaml │ └── test_podman_compose_ipam_default.py ├── lifetime │ ├── __init__.py │ ├── test_lifetime.py │ ├── up_single_container │ │ └── docker-compose.yml │ ├── up_single_container_many_times │ │ └── docker-compose.yml │ └── up_single_container_many_times_with_ports │ │ └── docker-compose.yml ├── merge │ ├── __init__.py │ ├── reset_and_override_tags │ │ ├── __init__.py │ │ ├── override_tag_attribute │ │ │ ├── __init__.py │ │ │ ├── docker-compose.override_attribute.yaml │ │ │ ├── docker-compose.yaml │ │ │ └── test_podman_compose_override_tag_attribute.py │ │ ├── override_tag_service │ │ │ ├── __init__.py │ │ │ ├── docker-compose.override_service.yaml │ │ │ ├── docker-compose.yaml │ │ │ └── test_podman_compose_override_tag_service.py │ │ ├── reset_tag_attribute │ │ │ ├── __init__.py │ │ │ ├── docker-compose.reset_attribute.yaml │ │ │ ├── docker-compose.yaml │ │ │ └── test_podman_compose_reset_tag_attribute.py │ │ └── reset_tag_service │ │ │ ├── __init__.py │ │ │ ├── docker-compose.reset_service.yaml │ │ │ ├── docker-compose.yaml │ │ │ └── test_podman_compose_reset_tag_service.py │ └── volumes_merge │ │ ├── __init__.py │ │ ├── docker-compose.override.yaml │ │ ├── docker-compose.yaml │ │ ├── index.txt │ │ ├── override.txt │ │ └── test_podman_compose_volumes_merge.py ├── multicompose │ ├── __init__.py │ ├── d1 │ │ ├── 1.env │ │ ├── 12.env │ │ ├── 2.env │ │ └── docker-compose.yml │ ├── d2 │ │ ├── 12.env │ │ ├── 2.env │ │ └── docker-compose.yml │ └── test_podman_compose_multicompose.py ├── nethost │ ├── __init__.py │ ├── docker-compose.yaml │ └── test_podman_compose_nethost.py ├── nets_test1 │ ├── __init__.py │ ├── docker-compose.yml │ ├── test1.txt │ ├── test2.txt │ └── test_podman_compose_nets_test1.py ├── nets_test2 │ ├── __init__.py │ ├── docker-compose.yml │ ├── test1.txt │ ├── test2.txt │ └── test_podman_compose_nets_test2.py ├── nets_test3 │ ├── __init__.py │ ├── docker-compose.yml │ ├── test1.txt │ ├── test2.txt │ ├── test3.txt │ └── test_podman_compose_nets_test3.py ├── nets_test_ip │ ├── __init__.py │ ├── docker-compose.yml │ ├── test1.txt │ ├── test2.txt │ ├── test3.txt │ ├── test4.txt │ └── test_podman_compose_nets_test_ip.py ├── network │ ├── __init__.py │ ├── docker-compose.yml │ ├── test1.txt │ ├── test2.txt │ └── test_podman_compose_network.py ├── network_interface_name │ ├── __init__.py │ ├── docker-compose.yml │ └── test_podman_compose_network_interface_name.py ├── network_scoped_aliases │ ├── __init__.py │ ├── docker-compose.yaml │ └── test_podman_compose_network_scoped_aliases.py ├── no_services │ ├── __init__.py │ ├── docker-compose.yaml │ └── test_podman_compose_no_services.py ├── pid │ ├── README.txt │ └── docker-compose.yml ├── pod_args │ ├── __init__.py │ ├── custom_pod_args_empty │ │ └── docker-compose.yml │ ├── custom_pod_args_set │ │ └── docker-compose.yml │ ├── custom_pod_args_unset │ │ └── docker-compose.yml │ └── test_podman_compose_pod_args.py ├── ports │ ├── __init__.py │ ├── docker-compose.yml │ └── test_podman_compose_ports.py ├── profile │ ├── __init__.py │ ├── docker-compose.yml │ ├── test_podman_compose_config.py │ └── test_podman_compose_up_down.py ├── seccomp │ ├── __init__.py │ ├── default.json │ ├── docker-compose.yml │ └── test_podman_compose_seccomp.py ├── secrets │ ├── __init__.py │ ├── docker-compose.yaml │ ├── my_secret │ ├── print_secrets.sh │ └── test_podman_compose_secrets.py ├── selinux │ ├── __init__.py │ ├── docker-compose.yml │ ├── host_test_text.txt │ └── test_podman_compose_selinux.py ├── service_scale │ ├── __init__.py │ ├── scaleup_cli │ │ └── docker-compose.yml │ ├── scaleup_deploy_replicas_parameter │ │ └── docker-compose.yml │ ├── scaleup_scale_parameter │ │ └── docker-compose.yml │ └── test_podman_compose_scale.py ├── short │ ├── data │ │ ├── redis │ │ │ └── .keep │ │ └── web │ │ │ └── .keep │ └── docker-compose.yaml ├── test_utils.py ├── testlogs │ └── docker-compose.yml ├── uidmaps │ ├── __init__.py │ ├── docker-compose.yml │ └── test_podman_compose_uidmaps.py ├── ulimit │ ├── Dockerfile │ ├── __init__.py │ ├── docker-compose.yaml │ ├── test_podman_compose_ulimit.py │ └── ulimit.sh ├── ulimit_build │ ├── Dockerfile │ ├── __init__.py │ ├── docker-compose.yaml │ ├── test_podman_compose_build_ulimits.py │ └── ulimit.sh ├── up_down │ ├── Dockerfile │ ├── __init__.py │ ├── docker-compose-orphans.yml │ ├── docker-compose.yml │ └── test_podman_compose_up_down.py ├── vol │ ├── README.md │ ├── __init__.py │ ├── docker-compose.yaml │ └── test_podman_compose_vol.py └── yamlmagic │ └── docker-compose.yml └── unit ├── __init__.py ├── test_can_merge_build.py ├── test_compose_exec_args.py ├── test_compose_run_log_format.py ├── test_compose_run_update_container_from_args.py ├── test_container_to_args.py ├── test_container_to_args_secrets.py ├── test_container_to_build_args.py ├── test_get_net_args.py ├── test_get_network_create_args.py ├── test_is_path_git_url.py ├── test_normalize_depends_on.py ├── test_normalize_final_build.py ├── test_normalize_service.py ├── test_rec_subs.py └── test_volumes.py /.codespellignore: -------------------------------------------------------------------------------- 1 | assertIn 2 | -------------------------------------------------------------------------------- /.codespellrc: -------------------------------------------------------------------------------- 1 | [codespell] 2 | skip = .git,*.pdf,*.svg,requirements.txt,test-requirements.txt 3 | # poped - loved variable name 4 | ignore-words-list = poped 5 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | parallel=True 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = tab 6 | tab_width = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | max_line_length = 100 12 | 13 | [*.{yml,yaml}] 14 | indent_style = space 15 | indent_size = 2 16 | 17 | [*.py] 18 | indent_style = space 19 | 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | Please make sure it's not a bug in podman (in that case report it to podman) 14 | or your understanding of docker-compose or how rootless containers work (for example, it's normal for rootless container not to be able to listen for port less than 1024 like 80) 15 | 16 | **To Reproduce** 17 | Steps to reproduce the behavior: 18 | 1. what is the content of the current working directory (ex. `docker-compose.yml`, `.env`, `Dockerfile`, ...etc.) 19 | 2. what is the sequence of commands you typed 20 | 21 | please use [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) for example give me a small busybox-based compose yaml 22 | 23 | 24 | **Expected behavior** 25 | A clear and concise description of what you expected to happen. 26 | 27 | **Actual behavior** 28 | What is the behavior you actually got and that should not happen. 29 | 30 | 31 | **Output** 32 | 33 | ``` 34 | $ podman-compose version 35 | using podman version: 3.4.0 36 | podman-compose version 0.1.7dev 37 | podman --version 38 | podman version 3.4.0 39 | 40 | $ podman-compose up 41 | ... 42 | 43 | ``` 44 | 45 | **Environment:** 46 | - OS: Linux / WSL / Mac 47 | - podman version: 48 | - podman compose version: (git hex) 49 | 50 | **Additional context** 51 | 52 | Add any other context about the problem here. 53 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | ## Contributor Checklist: 3 | 4 | If this PR adds a new feature that improves compatibility with docker-compose, please add a link 5 | to the exact part of compose spec that the PR touches. 6 | 7 | For any user-visible change please add a release note to newsfragments directory, e.g. 8 | newsfragments/my_feature.feature. See newsfragments/README.md for more details. 9 | 10 | All changes require additional unit tests. 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/codespell.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Codespell 3 | 4 | on: 5 | push: 6 | pull_request: 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | codespell: 13 | name: Check for spelling errors 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | - name: Codespell 20 | uses: codespell-project/actions-codespell@v2 21 | with: 22 | ignore_words_file: .codespellignore 23 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*.*.*' 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v4 15 | 16 | - name: Build binary 17 | run: | 18 | mkdir -p release/ 19 | docker build -t podman-compose-bin -v "$PWD/release:/result" . 20 | mv "$PWD/release/podman-compose" "$PWD/release/podman-compose-linux-x86" 21 | 22 | - name: Upload release asset 23 | uses: softprops/action-gh-release@v2 24 | with: 25 | files: ./release/podman-compose-linux-x86 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | -------------------------------------------------------------------------------- /.github/workflows/static-checks.yml: -------------------------------------------------------------------------------- 1 | name: Static checks 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | static-checks: 9 | runs-on: ubuntu-latest 10 | container: 11 | image: docker.io/library/python:3.11-bookworm 12 | # cgroupns needed to address the following error: 13 | # write /sys/fs/cgroup/cgroup.subtree_control: operation not supported 14 | options: --privileged --cgroupns=host 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Install dependencies 18 | run: | 19 | set -e 20 | pip install -r test-requirements.txt 21 | - name: Analysing the code using ruff 22 | run: | 23 | set -e 24 | ruff format --check 25 | ruff check 26 | - name: Analysing the code using mypy 27 | run: | 28 | set -e 29 | mypy . 30 | - name: Analysing the code with pylint 31 | run: | 32 | pylint podman_compose.py 33 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | test: 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | python-version: [ '3.9', '3.10', '3.11', '3.12', '3.13' ] 13 | 14 | runs-on: ubuntu-latest 15 | container: 16 | image: "docker.io/library/python:${{ matrix.python-version }}-bookworm" 17 | # cgroupns needed to address the following error: 18 | # write /sys/fs/cgroup/cgroup.subtree_control: operation not supported 19 | options: --privileged --cgroupns=host 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: Install dependencies 23 | run: | 24 | set -e 25 | apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y podman 26 | python -m pip install --upgrade pip 27 | pip install -r requirements.txt 28 | pip install -r test-requirements.txt 29 | - name: Run integration tests 30 | run: | 31 | python -m unittest discover -v tests/integration 32 | env: 33 | TESTS_DEBUG: 1 34 | - name: Run unit tests 35 | run: | 36 | coverage run --source podman_compose -m unittest discover tests/unit 37 | - name: Report coverage 38 | run: | 39 | coverage combine 40 | coverage report --format=markdown | tee -a $GITHUB_STEP_SUMMARY 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | .idea/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | .hypothesis/ 49 | .pytest_cache/ 50 | test-compose.yaml 51 | test-compose-?.yaml 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # Jupyter Notebook 76 | .ipynb_checkpoints 77 | 78 | # pyenv 79 | .python-version 80 | 81 | # celery beat schedule file 82 | celerybeat-schedule 83 | 84 | # SageMath parsed files 85 | *.sage.py 86 | 87 | # Environments 88 | .env 89 | .venv 90 | env/ 91 | venv/ 92 | ENV/ 93 | env.bak/ 94 | venv.bak/ 95 | 96 | # Spyder project settings 97 | .spyderproject 98 | .spyproject 99 | 100 | # Rope project settings 101 | .ropeproject 102 | 103 | # mkdocs documentation 104 | /site 105 | 106 | # mypy 107 | .mypy_cache/ 108 | 109 | 110 | .vscode 111 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | default_install_hook_types: [pre-commit, commit-msg] 2 | repos: 3 | - repo: https://github.com/astral-sh/ruff-pre-commit 4 | rev: v0.9.6 5 | hooks: 6 | - id: ruff 7 | types: [python] 8 | - repo: https://github.com/pycqa/flake8 9 | rev: 6.0.0 10 | hooks: 11 | - id: flake8 12 | types: [python] 13 | - repo: local 14 | hooks: 15 | - id: pylint 16 | name: pylint 17 | entry: pylint 18 | language: system 19 | types: [python] 20 | args: 21 | [ 22 | "-rn", # Only display messages 23 | "-sn", # Don't display the score 24 | "--rcfile=.pylintrc", # Link to your config file 25 | ] 26 | - repo: https://github.com/codespell-project/codespell 27 | rev: v2.2.5 28 | hooks: 29 | - id: codespell 30 | 31 | - repo: https://github.com/gklein/check_signoff 32 | rev: v1.0.5 33 | hooks: 34 | - id: check-signoff 35 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MESSAGES CONTROL] 2 | # C0111 missing-docstring: missing-class-docstring, missing-function-docstring, missing-method-docstring, missing-module-docstrin 3 | # consider-using-with: we need it for color formatter pipe 4 | disable=too-many-lines,too-many-branches,too-many-locals,too-many-statements,too-many-arguments,too-many-instance-attributes,fixme,multiple-statements,missing-docstring,line-too-long,consider-using-f-string,consider-using-with,unnecessary-lambda-assignment,broad-exception-caught 5 | # allow _ for ignored variables 6 | # allow generic names like a,b,c and i,j,k,l,m,n and x,y,z 7 | # allow k,v for key/value 8 | # allow e for exceptions, it for iterator, ix for index 9 | # allow ip for ip address 10 | # allow w,h for width, height 11 | # allow op for operation/operator/opcode 12 | # allow t, t0, t1, t2, and t3 for time 13 | # allow dt for delta time 14 | # allow db for database 15 | # allow ls for list 16 | # allow p for pipe 17 | # allow ex for examples, exists ..etc 18 | good-names=_,a,b,c,dt,db,e,f,fn,fd,i,j,k,v,kv,kw,l,m,n,ls,t,t0,t1,t2,t3,w,h,x,y,z,it,ix,ip,op,p,ex 19 | -------------------------------------------------------------------------------- /CODE-OF-CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## The Podman Compose Project Community Code of Conduct 2 | 3 | The Podman Compose project follows the [Containers Community Code of Conduct](https://github.com/containers/common/blob/master/CODE-OF-CONDUCT.md). 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use a base image with necessary build tools 2 | FROM python:3.11-slim AS builder 3 | 4 | # Install required packages for building 5 | RUN apt-get update && apt-get install -y \ 6 | gcc \ 7 | musl-dev \ 8 | build-essential \ 9 | python3-dev \ 10 | && apt-get clean \ 11 | && rm -rf /var/lib/apt/lists/* 12 | 13 | # Set the working directory 14 | WORKDIR /app 15 | 16 | # Copy the application code 17 | COPY . . 18 | 19 | # Install PyInstaller 20 | RUN pip install pyinstaller 21 | RUN pip install -r requirements.txt 22 | 23 | # Create a binary with PyInstaller 24 | RUN pyinstaller --onefile --clean podman_compose.py 25 | 26 | # Create /result dir in case it is not mounted 27 | RUN mkdir -p /result 28 | 29 | # Export binary 30 | RUN cp /app/dist/podman_compose /result/podman-compose 31 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | Creating a release 2 | ================== 3 | 4 | This file contains instructions for maintainers on how to release new versions of podman-compose. 5 | 6 | Step 1: Initialize variables for subsequent steps 7 | ------------------------------------------------- 8 | 9 | ``` 10 | export VERSION=1.2.3 11 | ``` 12 | 13 | Step 2: Release notes PR 14 | ------------------------ 15 | 16 | Open a new branch (e.g. `release`) and run the following: 17 | 18 | ``` 19 | ./scripts/make_release_notes.sh $VERSION 20 | ``` 21 | 22 | This collects the release notes using the `towncrier` tool and then commits the result. 23 | This step is done as a PR so that CI can check for spelling errors and similar issues. 24 | 25 | Certain file names are not properly supported by the `towncrier` tool and it ignores them. 26 | Check `newsfragments` directory for any forgotten release notes 27 | 28 | Step 3: Merge the release notes PR 29 | ---------------------------------- 30 | 31 | Step 4: Perform actual release 32 | ------------------------------ 33 | 34 | Pull the merge commit created on the `main` branch during the step 2. 35 | Then run: 36 | 37 | ``` 38 | ./scripts/make_release.sh $VERSION 39 | ``` 40 | 41 | This will create release commit, tag and push everything. 42 | 43 | Step 5: Create a release on Github 44 | ---------------------------------- 45 | 46 | The release notes must be added manually by drafting a release on the GitHub UI at 47 | https://github.com/containers/podman-compose/releases. 48 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Security and Disclosure Information Policy for the Podman Compose Project 2 | 3 | The Podman Compose Project follows the [Security and Disclosure Information Policy](https://github.com/containers/common/blob/master/SECURITY.md) for the Containers Projects. 4 | -------------------------------------------------------------------------------- /docs/Changelog-1.1.0.md: -------------------------------------------------------------------------------- 1 | Version v1.1.0 (2024-04-17) 2 | =========================== 3 | 4 | Bug fixes 5 | --------- 6 | 7 | - Fixed support for values with equals sign in `-e` argument of `run` and `exec` commands. 8 | - Fixed duplicate arguments being emitted in `stop` and `restart` commands. 9 | - Removed extraneous debug output. `--verbose` flag has been added to preserve verbose output. 10 | - Links aliases are now added to service aliases. 11 | - Fixed image build process to use defined environmental variables. 12 | - Empty list is now allowed to be `COMMAND` and `ENTRYPOINT`. 13 | - Environment files are now resolved relative to current working directory. 14 | - Exit code of container build is now preserved as return code of `build` command. 15 | 16 | New features 17 | ------------ 18 | 19 | - Added support for `uidmap`, `gidmap`, `http_proxy` and `runtime` service configuration keys. 20 | - Added support for `enable_ipv6` network configuration key. 21 | - Added `--parallel` option to support parallel pulling and building of images. 22 | - Implemented support for maps in `sysctls` container configuration key. 23 | - Implemented `stats` command. 24 | - Added `--no-normalize` flag to `config` command. 25 | - Added support for `include` global configuration key. 26 | - Added support for `build` command. 27 | - Added support to start containers with multiple networks. 28 | - Added support for `profile` argument. 29 | - Added support for starting podman in existing network namespace. 30 | - Added IPAM driver support. 31 | - Added support for file secrets being passed to `podman build` via `--secret` argument. 32 | - Added support for multiple networks with separately specified IP and MAC address. 33 | - Added support for `service.build.ulimits` when building image. 34 | -------------------------------------------------------------------------------- /docs/Changelog-1.2.0.md: -------------------------------------------------------------------------------- 1 | Version v1.2.0 (2024-06-26) 2 | =========================== 3 | 4 | Bug fixes 5 | --------- 6 | 7 | - Fixed handling of `--in-pod` argument. Previously it was hard to provide false value to it. 8 | - podman-compose no longer creates pods when registering systemd unit. 9 | - Fixed warning `RuntimeWarning: coroutine 'create_pods' was never awaited` 10 | - Fixed error when setting up IPAM network with default driver. 11 | - Fixed support for having list and dictionary `depends_on` sections in related compose files. 12 | - Fixed logging of failed build message. 13 | - Fixed support for multiple entries in `include` section. 14 | - Fixed environment variable precedence order. 15 | 16 | Changes 17 | ------- 18 | 19 | - `x-podman` dictionary in container root has been migrated to `x-podman.*` fields in container root. 20 | 21 | New features 22 | ------------ 23 | 24 | - Added support for `--publish` in `podman-compose run`. 25 | - Added support for Podman external root filesystem management (`--rootfs` option). 26 | - Added support for `podman-compose images` command. 27 | - Added support for `env_file` being configured via dictionaries. 28 | - Added support for enabling GPU access. 29 | - Added support for selinux in verbose mount specification. 30 | - Added support for `additional_contexts` section. 31 | - Added support for multi-line environment files. 32 | - Added support for passing contents of `podman-compose.yml` via stdin. 33 | - Added support for specifying the value for `--in-pod` setting in `podman-compose.yml` file. 34 | - Added support for environmental secrets. 35 | 36 | Documentation 37 | ------------- 38 | 39 | - Added instructions on how to install podman-compose on Homebrew. 40 | - Added explanation that netavark is an alternative to dnsname plugin 41 | -------------------------------------------------------------------------------- /docs/Changelog-1.3.0.md: -------------------------------------------------------------------------------- 1 | Version 1.3.0 (2025-01-07) 2 | ========================== 3 | 4 | Bug fixes 5 | --------- 6 | 7 | - Fixed support for de-facto alternative `Dockerfile` names (e.g. `Containerfile`) 8 | - Fixed a bug that caused attempts to create already existing pods multiple times. 9 | - Fixed compatibility with docker-compose in how symlinks to docker-compose.yml are handled. 10 | - Fixed freeze caused by too long log lines without a newline. 11 | - Fixed support for `network_mode: none`. 12 | - Improved error detection by rejecting service definitions that contain both `network_mode` and 13 | `networks` keys, which is not allowed. 14 | 15 | 16 | Features 17 | -------- 18 | 19 | - Added support for build labels. 20 | - Added support for "platform" property in the build command. 21 | - Added support for "ssh" property in the build command. 22 | - Added support for cache_from and cache_to fields in build section. 23 | - Added support for honoring the condition in the depends_on section of the service, if stated. 24 | - Added `x-podman.no_hosts` setting to pass `--no-hosts` to podman run 25 | - Added support for compatibility with docker compose for default network behavior when no network 26 | defined in service. This is controlled via `default_net_behavior_compat` feature flag. 27 | - Added a way to get compatibility of default network names with docker compose. 28 | This is selected by setting `default_net_name_compat: true` on `x-podman` global dictionary. 29 | - Added support for the `device_cgroup_rules` property in services. 30 | - Added support for removing networks in `podman-compose down`. 31 | - Added support for network scoped service aliases. 32 | - Added support for network level `mac_address` attribute. 33 | - Added ability to substitute variables with the environment of the service. 34 | 35 | Misc 36 | ---- 37 | 38 | - Declared compatibility with Python 3.13. 39 | -------------------------------------------------------------------------------- /docs/Changelog-1.4.0.md: -------------------------------------------------------------------------------- 1 | Version 1.4.0 (2025-05-10) 2 | ========================== 3 | 4 | Bug fixes 5 | --------- 6 | 7 | - Fixed handling of relative includes and extends in compose files 8 | - Fixed error when merging arguments in list and dictionary syntax 9 | - Fixed issue where short-lived containers could execute twice when using `up` in detached mode 10 | - Fixed `up` command hanging on Podman versions earlier than 4.6.0 11 | - Fixed issue where `service_healthy` conditions weren't enforced during `up` command 12 | - Fixed support for the `--scale` flag 13 | - Fixed bug causing dependent containers to start despite `--no-deps` flag 14 | - Fixed port command behavior for dynamic host ports 15 | - Fixed interpolation of `COMPOSE_PROJECT_NAME` when set from top-level `name` in compose file 16 | - Fixed project name evaluation order to match compose spec 17 | - Fixed build context when using git URLs 18 | - Fixed `KeyError` when `down` is called with non-existent service 19 | - Skip `down` during `up` when no active containers exist 20 | - Fixed non-zero exit code on failure when using `up -d` 21 | - Fixed SIGINT handling during `up` command for graceful shutdown 22 | - Fixed `NotImplementedError` when interrupted on Windows 23 | 24 | Features 25 | -------- 26 | 27 | - Added `--quiet` flag to `config` command to suppress output 28 | - Added support for `pids_limit` and `deploy.resources.limits.pids` 29 | - Added `--abort-on-container-failure` option 30 | - Added `--rmi` argument to `down` command for image removal 31 | - Added support for `x-podman.disable-dns` to disable DNS plugin on defined networks 32 | - Added support for `x-podman.dns` to set DNS nameservers for defined networks 33 | - Improved file descriptor handling - no longer closes externally created descriptors. 34 | This allows descriptors created e.g. via systemd socket activation to be passed to 35 | containers. 36 | - Added support for `cpuset` configuration 37 | - Added support for `reset` and `override` tags when merging compose files 38 | - Added support for `x-podman.interface_name` to set network interface names 39 | - Added support for `x-podman.pod_args` to override default `--pod-args` 40 | -------------------------------------------------------------------------------- /docs/Mappings.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | * `1podfw` - create all containers in one pod (inter-container communication is done via `localhost`), doing port mapping in that pod 4 | * `1pod` - create all containers in one pod, doing port mapping in each container (does not work) 5 | * `identity` - no mapping 6 | * `hostnet` - use host network, and inter-container communication is done via host gateway and published ports 7 | * `cntnet` - create a container and use it via `--network container:name` (inter-container communication via `localhost`) 8 | * `publishall` - publish all ports to host (using `-P`) and communicate via gateway 9 | 10 | -------------------------------------------------------------------------------- /examples/awx17/README.md: -------------------------------------------------------------------------------- 1 | # AWX Compose 2 | 3 | the directory roles is taken from [here](https://github.com/ansible/awx/tree/17.1.0/installer/roles/local_docker) 4 | 5 | also look at https://github.com/ansible/awx/tree/17.1.0/tools/docker-compose 6 | 7 | ``` 8 | mkdir deploy awx17 9 | ansible localhost \ 10 | -e host_port=8080 \ 11 | -e awx_secret_key='awx,secret.123' \ 12 | -e secret_key='awx,secret.123' \ 13 | -e admin_user='admin' \ 14 | -e admin_password='admin' \ 15 | -e pg_password='awx,123.' \ 16 | -e pg_username='awx' \ 17 | -e pg_database='awx' \ 18 | -e pg_port='5432' \ 19 | -e redis_image="docker.io/library/redis:6-alpine" \ 20 | -e postgres_data_dir="./data/pg" \ 21 | -e compose_start_containers=false \ 22 | -e dockerhub_base='docker.io/ansible' \ 23 | -e awx_image='docker.io/ansible/awx' \ 24 | -e awx_version='17.1.0' \ 25 | -e dockerhub_version='17.1.0' \ 26 | -e docker_deploy_base_path=$PWD/deploy \ 27 | -e docker_compose_dir=$PWD/awx17 \ 28 | -e awx_task_hostname=awx \ 29 | -e awx_web_hostname=awxweb \ 30 | -m include_role -a name=local_docker 31 | cp awx17/docker-compose.yml awx17/docker-compose.yml.orig 32 | sed -i -re "s#- \"$PWD/awx17/(.*):/#- \"./\1:/#" awx17/docker-compose.yml 33 | cd awx17 34 | podman-compose run --rm --service-ports task awx-manage migrate --no-input 35 | podman-compose up -d 36 | ``` 37 | 38 | -------------------------------------------------------------------------------- /examples/awx17/roles/local_docker/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dockerhub_version: "{{ lookup('file', playbook_dir + '/../VERSION') }}" 3 | 4 | awx_image: "awx" 5 | redis_image: "redis" 6 | 7 | postgresql_version: "12" 8 | postgresql_image: "postgres:{{postgresql_version}}" 9 | 10 | compose_start_containers: true 11 | upgrade_postgres: false 12 | -------------------------------------------------------------------------------- /examples/awx17/roles/local_docker/tasks/compose.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create {{ docker_compose_dir }} directory 3 | file: 4 | path: "{{ docker_compose_dir }}" 5 | state: directory 6 | 7 | - name: Create Redis socket directory 8 | file: 9 | path: "{{ docker_compose_dir }}/redis_socket" 10 | state: directory 11 | mode: 0777 12 | 13 | - name: Create Docker Compose Configuration 14 | template: 15 | src: "{{ item.file }}.j2" 16 | dest: "{{ docker_compose_dir }}/{{ item.file }}" 17 | mode: "{{ item.mode }}" 18 | loop: 19 | - file: environment.sh 20 | mode: "0600" 21 | - file: credentials.py 22 | mode: "0600" 23 | - file: docker-compose.yml 24 | mode: "0600" 25 | - file: nginx.conf 26 | mode: "0600" 27 | - file: redis.conf 28 | mode: "0664" 29 | register: awx_compose_config 30 | 31 | - name: Render SECRET_KEY file 32 | copy: 33 | content: "{{ secret_key }}" 34 | dest: "{{ docker_compose_dir }}/SECRET_KEY" 35 | mode: 0600 36 | register: awx_secret_key 37 | 38 | - block: 39 | - name: Remove AWX containers before migrating postgres so that the old postgres container does not get used 40 | docker_compose: 41 | project_src: "{{ docker_compose_dir }}" 42 | state: absent 43 | ignore_errors: true 44 | 45 | - name: Run migrations in task container 46 | shell: docker-compose run --rm --service-ports task awx-manage migrate --no-input 47 | args: 48 | chdir: "{{ docker_compose_dir }}" 49 | 50 | - name: Start the containers 51 | docker_compose: 52 | project_src: "{{ docker_compose_dir }}" 53 | restarted: "{{ awx_compose_config is changed or awx_secret_key is changed }}" 54 | register: awx_compose_start 55 | 56 | - name: Update CA trust in awx_web container 57 | command: docker exec awx_web '/usr/bin/update-ca-trust' 58 | when: awx_compose_config.changed or awx_compose_start.changed 59 | 60 | - name: Update CA trust in awx_task container 61 | command: docker exec awx_task '/usr/bin/update-ca-trust' 62 | when: awx_compose_config.changed or awx_compose_start.changed 63 | 64 | - name: Wait for launch script to create user 65 | wait_for: 66 | timeout: 10 67 | delegate_to: localhost 68 | 69 | - name: Create Preload data 70 | command: docker exec awx_task bash -c "/usr/bin/awx-manage create_preload_data" 71 | when: create_preload_data|bool 72 | register: cdo 73 | changed_when: "'added' in cdo.stdout" 74 | when: compose_start_containers|bool 75 | -------------------------------------------------------------------------------- /examples/awx17/roles/local_docker/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Generate broadcast websocket secret 3 | set_fact: 4 | broadcast_websocket_secret: "{{ lookup('password', '/dev/null length=128') }}" 5 | run_once: true 6 | no_log: true 7 | when: broadcast_websocket_secret is not defined 8 | 9 | - import_tasks: upgrade_postgres.yml 10 | when: 11 | - postgres_data_dir is defined 12 | - pg_hostname is not defined 13 | 14 | - import_tasks: set_image.yml 15 | - import_tasks: compose.yml 16 | -------------------------------------------------------------------------------- /examples/awx17/roles/local_docker/tasks/set_image.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Manage AWX Container Images 3 | block: 4 | - name: Export Docker awx image if it isn't local and there isn't a registry defined 5 | docker_image: 6 | name: "{{ awx_image }}" 7 | tag: "{{ awx_version }}" 8 | archive_path: "{{ awx_local_base_config_path|default('/tmp') }}/{{ awx_image }}_{{ awx_version }}.tar" 9 | when: inventory_hostname != "localhost" and docker_registry is not defined 10 | delegate_to: localhost 11 | 12 | - name: Set docker base path 13 | set_fact: 14 | docker_deploy_base_path: "{{ awx_base_path|default('/tmp') }}/docker_deploy" 15 | when: ansible_connection != "local" and docker_registry is not defined 16 | 17 | - name: Ensure directory exists 18 | file: 19 | path: "{{ docker_deploy_base_path }}" 20 | state: directory 21 | when: ansible_connection != "local" and docker_registry is not defined 22 | 23 | - name: Copy awx image to docker execution 24 | copy: 25 | src: "{{ awx_local_base_config_path|default('/tmp') }}/{{ awx_image }}_{{ awx_version }}.tar" 26 | dest: "{{ docker_deploy_base_path }}/{{ awx_image }}_{{ awx_version }}.tar" 27 | when: ansible_connection != "local" and docker_registry is not defined 28 | 29 | - name: Load awx image 30 | docker_image: 31 | name: "{{ awx_image }}" 32 | tag: "{{ awx_version }}" 33 | load_path: "{{ docker_deploy_base_path }}/{{ awx_image }}_{{ awx_version }}.tar" 34 | timeout: 300 35 | when: ansible_connection != "local" and docker_registry is not defined 36 | 37 | - name: Set full image path for local install 38 | set_fact: 39 | awx_docker_actual_image: "{{ awx_image }}:{{ awx_version }}" 40 | when: docker_registry is not defined 41 | when: dockerhub_base is not defined 42 | 43 | - name: Set DockerHub Image Paths 44 | set_fact: 45 | awx_docker_actual_image: "{{ dockerhub_base }}/awx:{{ dockerhub_version }}" 46 | when: dockerhub_base is defined 47 | -------------------------------------------------------------------------------- /examples/awx17/roles/local_docker/tasks/upgrade_postgres.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Create {{ postgres_data_dir }} directory 4 | file: 5 | path: "{{ postgres_data_dir }}" 6 | state: directory 7 | 8 | - name: Get full path of postgres data dir 9 | shell: "echo {{ postgres_data_dir }}" 10 | register: fq_postgres_data_dir 11 | 12 | - name: Register temporary docker container 13 | set_fact: 14 | container_command: "docker run --rm -v '{{ fq_postgres_data_dir.stdout }}:/var/lib/postgresql' centos:8 bash -c " 15 | 16 | - name: Check for existing Postgres data (run from inside the container for access to file) 17 | shell: 18 | cmd: | 19 | {{ container_command }} "[[ -f /var/lib/postgresql/10/data/PG_VERSION ]] && echo 'exists'" 20 | register: pg_version_file 21 | ignore_errors: true 22 | 23 | - name: Record Postgres version 24 | shell: | 25 | {{ container_command }} "cat /var/lib/postgresql/10/data/PG_VERSION" 26 | register: old_pg_version 27 | when: pg_version_file is defined and pg_version_file.stdout == 'exists' 28 | 29 | - name: Determine whether to upgrade postgres 30 | set_fact: 31 | upgrade_postgres: "{{ old_pg_version.stdout == '10' }}" 32 | when: old_pg_version.changed 33 | 34 | - name: Set up new postgres paths pre-upgrade 35 | shell: | 36 | {{ container_command }} "mkdir -p /var/lib/postgresql/12/data/" 37 | when: upgrade_postgres | bool 38 | 39 | - name: Stop AWX before upgrading postgres 40 | docker_compose: 41 | project_src: "{{ docker_compose_dir }}" 42 | stopped: true 43 | when: upgrade_postgres | bool 44 | 45 | - name: Upgrade Postgres 46 | shell: | 47 | docker run --rm \ 48 | -v {{ postgres_data_dir }}/10/data:/var/lib/postgresql/10/data \ 49 | -v {{ postgres_data_dir }}/12/data:/var/lib/postgresql/12/data \ 50 | -e PGUSER={{ pg_username }} -e POSTGRES_INITDB_ARGS="-U {{ pg_username }}" \ 51 | tianon/postgres-upgrade:10-to-12 --username={{ pg_username }} 52 | when: upgrade_postgres | bool 53 | 54 | - name: Copy old pg_hba.conf 55 | shell: | 56 | {{ container_command }} "cp /var/lib/postgresql/10/data/pg_hba.conf /var/lib/postgresql/12/data/pg_hba.conf" 57 | when: upgrade_postgres | bool 58 | 59 | - name: Remove old data directory 60 | shell: | 61 | {{ container_command }} "rm -rf /var/lib/postgresql/10/data" 62 | when: 63 | - upgrade_postgres | bool 64 | - compose_start_containers|bool 65 | -------------------------------------------------------------------------------- /examples/awx17/roles/local_docker/templates/credentials.py.j2: -------------------------------------------------------------------------------- 1 | DATABASES = { 2 | 'default': { 3 | 'ATOMIC_REQUESTS': True, 4 | 'ENGINE': 'django.db.backends.postgresql', 5 | 'NAME': "{{ pg_database }}", 6 | 'USER': "{{ pg_username }}", 7 | 'PASSWORD': "{{ pg_password }}", 8 | 'HOST': "{{ pg_hostname | default('postgres') }}", 9 | 'PORT': "{{ pg_port }}", 10 | } 11 | } 12 | 13 | BROADCAST_WEBSOCKET_SECRET = "{{ broadcast_websocket_secret | b64encode }}" 14 | -------------------------------------------------------------------------------- /examples/awx17/roles/local_docker/templates/environment.sh.j2: -------------------------------------------------------------------------------- 1 | DATABASE_USER={{ pg_username|quote }} 2 | DATABASE_NAME={{ pg_database|quote }} 3 | DATABASE_HOST={{ pg_hostname|default('postgres')|quote }} 4 | DATABASE_PORT={{ pg_port|default('5432')|quote }} 5 | DATABASE_PASSWORD={{ pg_password|default('awxpass')|quote }} 6 | {% if pg_admin_password is defined %} 7 | DATABASE_ADMIN_PASSWORD={{ pg_admin_password|quote }} 8 | {% endif %} 9 | AWX_ADMIN_USER={{ admin_user|quote }} 10 | AWX_ADMIN_PASSWORD={{ admin_password|quote }} 11 | -------------------------------------------------------------------------------- /examples/awx17/roles/local_docker/templates/redis.conf.j2: -------------------------------------------------------------------------------- 1 | unixsocket /var/run/redis/redis.sock 2 | unixsocketperm 660 3 | port 0 4 | bind 127.0.0.1 5 | -------------------------------------------------------------------------------- /examples/awx3/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | postgres: 4 | image: "postgres:9.6" 5 | environment: 6 | POSTGRES_USER: awx 7 | POSTGRES_PASSWORD: awxpass 8 | POSTGRES_DB: awx 9 | 10 | rabbitmq: 11 | image: "rabbitmq:3" 12 | environment: 13 | RABBITMQ_DEFAULT_VHOST: awx 14 | 15 | memcached: 16 | image: "memcached:alpine" 17 | 18 | awx_web: 19 | # image: "geerlingguy/awx_web:latest" 20 | image: "ansible/awx_web:3.0.1" 21 | links: 22 | - rabbitmq 23 | - memcached 24 | - postgres 25 | ports: 26 | - "8080:8052" 27 | hostname: awxweb 28 | user: root 29 | environment: 30 | SECRET_KEY: aabbcc 31 | DATABASE_USER: awx 32 | DATABASE_PASSWORD: awxpass 33 | DATABASE_NAME: awx 34 | DATABASE_PORT: 5432 35 | DATABASE_HOST: postgres 36 | RABBITMQ_USER: guest 37 | RABBITMQ_PASSWORD: guest 38 | RABBITMQ_HOST: rabbitmq 39 | RABBITMQ_PORT: 5672 40 | RABBITMQ_VHOST: awx 41 | MEMCACHED_HOST: memcached 42 | MEMCACHED_PORT: 11211 43 | 44 | awx_task: 45 | # image: "geerlingguy/awx_task:latest" 46 | image: "ansible/awx_task:3.0.1" 47 | links: 48 | - rabbitmq 49 | - memcached 50 | - awx_web:awxweb 51 | - postgres 52 | hostname: awx 53 | user: root 54 | environment: 55 | SECRET_KEY: aabbcc 56 | DATABASE_USER: awx 57 | DATABASE_PASSWORD: awxpass 58 | DATABASE_NAME: awx 59 | DATABASE_PORT: 5432 60 | DATABASE_HOST: postgres 61 | RABBITMQ_USER: guest 62 | RABBITMQ_PASSWORD: guest 63 | RABBITMQ_HOST: rabbitmq 64 | RABBITMQ_PORT: 5672 65 | RABBITMQ_VHOST: awx 66 | MEMCACHED_HOST: memcached 67 | MEMCACHED_PORT: 11211 68 | -------------------------------------------------------------------------------- /examples/azure-vote/README.md: -------------------------------------------------------------------------------- 1 | # Azure Vote Example 2 | 3 | This example have two containers: 4 | 5 | * backend: `redis` used as storage 6 | * frontend: having supervisord, nginx, uwsgi/python 7 | 8 | 9 | ``` 10 | echo "HOST_PORT=8080" > .env 11 | podman-compose up 12 | ``` 13 | 14 | after typing the commands above open your browser on the host port you picked above like 15 | [http://localhost:8080/](http://localhost:8080/) 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/azure-vote/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # from https://github.com/Azure-Samples/azure-voting-app-redis/blob/master/docker-compose.yaml 3 | version: '3' 4 | services: 5 | azure-vote-back: 6 | image: mcr.microsoft.com/oss/bitnami/redis:6.0.8 7 | container_name: azure-vote-back 8 | environment: 9 | ALLOW_EMPTY_PASSWORD: "yes" 10 | azure-vote-front: 11 | image: mcr.microsoft.com/azuredocs/azure-vote-front:v1 12 | environment: 13 | REDIS: azure-vote-back 14 | ports: 15 | - "${HOST_PORT:-8080}:80" 16 | 17 | -------------------------------------------------------------------------------- /examples/busybox/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | services: 3 | 4 | redis: 5 | image: redis:alpine 6 | ports: 7 | - "6379" 8 | environment: 9 | - SECRET_KEY=aabbcc 10 | - ENV_IS_SET 11 | 12 | frontend: 13 | image: busybox 14 | #entrypoint: [] 15 | command: ["/bin/busybox", "httpd", "-f", "-p", "8080"] 16 | working_dir: / 17 | environment: 18 | SECRET_KEY2: aabbcc 19 | ENV_IS_SET2: 20 | ports: 21 | - "8080" 22 | links: 23 | - redis:myredis 24 | labels: 25 | my.label: my_value 26 | 27 | #tmpfs: /run 28 | #tmpfs: 29 | # - /run 30 | # - /tmp 31 | #user: postgresql 32 | #working_dir: /code 33 | #domainname: foo.com 34 | #hostname: foo 35 | #ipc: host 36 | #mac_address: 02:42:ac:11:65:43 37 | #privileged: true 38 | #read_only: true 39 | #shm_size: 64M 40 | #stdin_open: true 41 | #tty: true 42 | -------------------------------------------------------------------------------- /examples/docker-inline/docker-compose.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: '3' 3 | services: 4 | dummy: 5 | build: 6 | context: . 7 | dockerfile_inline: | 8 | FROM alpine 9 | RUN echo "hello world" 10 | -------------------------------------------------------------------------------- /examples/echo/README.md: -------------------------------------------------------------------------------- 1 | # Echo Service example 2 | 3 | ``` 4 | podman-compose up 5 | ``` 6 | 7 | Test the service with `curl like this` 8 | 9 | ``` 10 | $ curl -X POST -d "foobar" http://localhost:8080/; echo 11 | 12 | CLIENT VALUES: 13 | client_address=10.89.31.2 14 | command=POST 15 | real path=/ 16 | query=nil 17 | request_version=1.1 18 | request_uri=http://localhost:8080/ 19 | 20 | SERVER VALUES: 21 | server_version=nginx: 1.10.0 - lua: 10001 22 | 23 | HEADERS RECEIVED: 24 | accept=*/* 25 | content-length=6 26 | content-type=application/x-www-form-urlencoded 27 | host=localhost:8080 28 | user-agent=curl/7.76.1 29 | BODY: 30 | foobar 31 | ``` 32 | -------------------------------------------------------------------------------- /examples/echo/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: '3' 3 | services: 4 | web: 5 | image: k8s.gcr.io/echoserver:1.4 6 | ports: 7 | - "${HOST_PORT:-8080}:8080" 8 | 9 | -------------------------------------------------------------------------------- /examples/hello-app-redis/README.md: -------------------------------------------------------------------------------- 1 | # GCR Hello App Redis 2 | 3 | A 6-node redis cluster using [Bitnami](https://github.com/bitnami/bitnami-docker-redis-cluster) 4 | with a [simple hit counter](https://github.com/GoogleCloudPlatform/kubernetes-engine-samples/tree/main/hello-app-redis) that persists on that redis cluster 5 | 6 | ``` 7 | podman-compose up 8 | ``` 9 | 10 | then open your browser on [http://localhost:8080/](http://localhost:8080/) 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/hello-app-redis/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: '3' 3 | volumes: 4 | redis-node1-data: 5 | redis-node2-data: 6 | redis-node3-data: 7 | redis-node4-data: 8 | redis-node5-data: 9 | redis-data: 10 | services: 11 | web: 12 | image: gcr.io/google-samples/hello-app-redis:1.0 13 | depends_on: 14 | - redis-cluster 15 | ports: 16 | - "${HOST_PORT:-8080}:8080" 17 | redis-node1: 18 | image: docker.io/bitnami/redis-cluster:6.2 19 | volumes: 20 | - redis-node1-data:/bitnami/redis/data 21 | environment: 22 | - ALLOW_EMPTY_PASSWORD=yes 23 | - REDIS_NODES=redis-node1 redis-node2 redis-node3 redis-node4 redis-node5 redis-cluster 24 | redis-node2: 25 | image: docker.io/bitnami/redis-cluster:6.2 26 | volumes: 27 | - redis-node2-data:/bitnami/redis/data 28 | environment: 29 | - ALLOW_EMPTY_PASSWORD=yes 30 | - REDIS_NODES=redis-node1 redis-node2 redis-node3 redis-node4 redis-node5 redis-cluster 31 | redis-node3: 32 | image: docker.io/bitnami/redis-cluster:6.2 33 | volumes: 34 | - redis-node3-data:/bitnami/redis/data 35 | environment: 36 | - ALLOW_EMPTY_PASSWORD=yes 37 | - REDIS_NODES=redis-node1 redis-node2 redis-node3 redis-node4 redis-node5 redis-cluster 38 | redis-node4: 39 | image: docker.io/bitnami/redis-cluster:6.2 40 | volumes: 41 | - redis-node4-data:/bitnami/redis/data 42 | environment: 43 | - ALLOW_EMPTY_PASSWORD=yes 44 | - REDIS_NODES=redis-node1 redis-node2 redis-node3 redis-node4 redis-node5 redis-cluster 45 | redis-node5: 46 | image: docker.io/bitnami/redis-cluster:6.2 47 | volumes: 48 | - redis-node5-data:/bitnami/redis/data 49 | environment: 50 | - ALLOW_EMPTY_PASSWORD=yes 51 | - REDIS_NODES=redis-node1 redis-node2 redis-node3 redis-node4 redis-node5 redis-cluster 52 | 53 | redis-cluster: 54 | image: docker.io/bitnami/redis-cluster:6.2 55 | volumes: 56 | - redis-data:/bitnami/redis/data 57 | depends_on: 58 | - redis-node1 59 | - redis-node2 60 | - redis-node3 61 | - redis-node4 62 | - redis-node5 63 | environment: 64 | - ALLOW_EMPTY_PASSWORD=yes 65 | - REDIS_NODES=redis-node1 redis-node2 redis-node3 redis-node4 redis-node5 redis-cluster 66 | - REDIS_CLUSTER_CREATOR=yes 67 | 68 | -------------------------------------------------------------------------------- /examples/hello-app/README.md: -------------------------------------------------------------------------------- 1 | # GCR Hello App 2 | 3 | A small ~2MB image, type 4 | 5 | ``` 6 | podman-compose up 7 | ``` 8 | 9 | then open your browser on [http://localhost:8080/](http://localhost:8080/) 10 | 11 | -------------------------------------------------------------------------------- /examples/hello-app/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: '3' 3 | services: 4 | web: 5 | image: gcr.io/google-samples/hello-app:1.0 6 | ports: 7 | - "${HOST_PORT:-8080}:8080" 8 | 9 | -------------------------------------------------------------------------------- /examples/hello-python/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9-alpine 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY requirements.txt ./ 6 | RUN pip install --no-cache-dir -r requirements.txt 7 | 8 | COPY . . 9 | 10 | CMD [ "python", "-m", "app.web" ] 11 | EXPOSE 8080 12 | 13 | -------------------------------------------------------------------------------- /examples/hello-python/README.md: -------------------------------------------------------------------------------- 1 | # Simple Python Demo 2 | ## A Redis counter 3 | 4 | ``` 5 | podman-compose up -d 6 | curl localhost:8080/ 7 | curl localhost:8080/hello.json 8 | ``` 9 | -------------------------------------------------------------------------------- /examples/hello-python/app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/containers/podman-compose/4c6df85efac6dea83cea68b406d3d1abfb7cacef/examples/hello-python/app/__init__.py -------------------------------------------------------------------------------- /examples/hello-python/app/web.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=import-error 2 | # pylint: disable=unused-import 3 | import asyncio # noqa: F401 4 | import os 5 | 6 | import aioredis # type: ignore[import-not-found] 7 | from aiohttp import web # type: ignore[import-not-found] 8 | 9 | REDIS_HOST = os.environ.get("REDIS_HOST", "localhost") 10 | REDIS_PORT = int(os.environ.get("REDIS_PORT", "6379")) 11 | REDIS_DB = int(os.environ.get("REDIS_DB", "0")) 12 | 13 | redis = aioredis.from_url(f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB}") 14 | app = web.Application() 15 | routes = web.RouteTableDef() 16 | 17 | 18 | @routes.get("/") 19 | async def hello(request: web.Request) -> web.Response: # pylint: disable=unused-argument 20 | counter = await redis.incr("mycounter") 21 | return web.Response(text=f"counter={counter}") 22 | 23 | 24 | @routes.get("/hello.json") 25 | async def hello_json(request: web.Request) -> web.Response: # pylint: disable=unused-argument 26 | counter = await redis.incr("mycounter") 27 | data = {"counter": counter} 28 | return web.json_response(data) 29 | 30 | 31 | app.add_routes(routes) 32 | 33 | 34 | def main() -> None: 35 | web.run_app(app, port=8080) 36 | 37 | 38 | if __name__ == "__main__": 39 | main() 40 | -------------------------------------------------------------------------------- /examples/hello-python/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: '3' 3 | volumes: 4 | redis: 5 | services: 6 | redis: 7 | read_only: true 8 | image: docker.io/redis:alpine 9 | command: ["redis-server", "--appendonly", "yes", "--notify-keyspace-events", "Ex"] 10 | volumes: 11 | - redis:/data 12 | web: 13 | read_only: true 14 | build: 15 | context: . 16 | image: hello-py-aioweb 17 | ports: 18 | - 8080:8080 19 | environment: 20 | REDIS_HOST: redis 21 | 22 | -------------------------------------------------------------------------------- /examples/hello-python/requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp 2 | aioredis 3 | # aioredis[hiredis] 4 | -------------------------------------------------------------------------------- /examples/nodeproj/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "es6": true 5 | }, 6 | "settings": { 7 | "import/resolver": { 8 | "node": { 9 | "extensions": [".js", ".mjs", ".ts", ".cjs"] 10 | } 11 | } 12 | }, 13 | "parser": "@typescript-eslint/parser", 14 | "parserOptions": { 15 | "ecmaVersion": 2020, 16 | "sourceType": "module", 17 | "allowImportExportEverywhere": true 18 | }, 19 | "extends": [ 20 | "eslint:recommended", 21 | "plugin:import/errors", 22 | "plugin:import/warnings", 23 | "plugin:import/typescript", 24 | "plugin:promise/recommended", 25 | "google", 26 | "plugin:security/recommended" 27 | ], 28 | "plugins": ["promise", "security", "import"], 29 | "overrides": [ 30 | { 31 | "files": "public/**/*.min.js", 32 | "env": { 33 | "browser": true, 34 | "node": false, 35 | "es6": false 36 | }, 37 | "parserOptions": { 38 | "sourceType": "script" 39 | }, 40 | "extends": ["plugin:compat/recommended"], 41 | "plugins": [], 42 | "rules": { 43 | "no-var": ["off"] 44 | } 45 | } 46 | ], 47 | "rules": { 48 | "security/detect-non-literal-fs-filename":["off"], 49 | "security/detect-object-injection":["off"], 50 | "camelcase": ["off"], 51 | "no-console": ["off"], 52 | "require-jsdoc": ["off"], 53 | "one-var": ["off"], 54 | "guard-for-in": ["off"], 55 | "max-len": [ 56 | "warn", 57 | { 58 | "ignoreComments": true, 59 | "ignoreTrailingComments": true, 60 | "ignoreUrls": true, 61 | "code": 200 62 | } 63 | ], 64 | "indent": ["warn", 4], 65 | "no-unused-vars": ["warn"], 66 | "no-extra-semi": ["warn"], 67 | "linebreak-style": ["error", "unix"], 68 | "quotes": ["warn", "double"], 69 | "semi": ["error", "always"] 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /examples/nodeproj/.gitignore: -------------------------------------------------------------------------------- 1 | local.env 2 | .env 3 | *.pid 4 | node_modules 5 | 6 | -------------------------------------------------------------------------------- /examples/nodeproj/.home/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | -------------------------------------------------------------------------------- /examples/nodeproj/README.md: -------------------------------------------------------------------------------- 1 | # How to run example 2 | 3 | 4 | 5 | ``` 6 | cp example.local.env local.env 7 | cp example.env .env 8 | cat local.env 9 | cat .env 10 | echo "UID=$UID" >> .env 11 | cat .env 12 | podman-compose build 13 | podman-compose run --rm --no-deps init 14 | podman-compose up 15 | ``` 16 | 17 | -------------------------------------------------------------------------------- /examples/nodeproj/containers/node16-runtime/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM registry.fedoraproject.org/fedora-minimal:35 2 | ARG NODE_VER=16 3 | # microdnf -y module enable nodejs:${NODE_VER} 4 | RUN \ 5 | echo -e "[nodejs]\nname=nodejs\nstream=${NODE_VER}\nprofiles=\nstate=enabled\n" > /etc/dnf/modules.d/nodejs.module && \ 6 | microdnf -y install shadow-utils nodejs zopfli findutils busybox && \ 7 | microdnf clean all 8 | RUN adduser -d /app app && mkdir -p /app/code/.home && chown app:app -R /app/code && chmod 711 /app /app/code/.home && usermod -d /app/code/.home app 9 | ENV XDG_CONFIG_HOME=/app/code/.home 10 | ENV HOME=/app/code/.home 11 | WORKDIR /app/code 12 | 13 | -------------------------------------------------------------------------------- /examples/nodeproj/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | volumes: 3 | redis: 4 | services: 5 | redis: 6 | read_only: true 7 | image: docker.io/redis:alpine 8 | command: ["redis-server", "--appendonly", "yes", "--notify-keyspace-events", "Ex"] 9 | volumes: 10 | - redis:/data 11 | tmpfs: 12 | - /tmp 13 | - /var/run 14 | - /run 15 | init: 16 | read_only: true 17 | #userns_mode: keep-id 18 | user: ${UID:-1000} 19 | build: 20 | context: ./containers/${NODE_IMG:-node16-runtime} 21 | image: ${NODE_IMG:-node16-runtime} 22 | env_file: 23 | - local.env 24 | volumes: 25 | - .:/app/code 26 | command: ["/bin/sh", "-c", "mkdir -p ~/; [ -d ./node_modules ] && echo '** node_modules exists' || npm install"] 27 | tmpfs: 28 | - /tmp 29 | - /run 30 | task: 31 | extends: 32 | service: init 33 | command: ["npm", "run", "cli", "--", "task"] 34 | links: 35 | - redis 36 | depends_on: 37 | - redis 38 | web: 39 | extends: 40 | service: init 41 | command: ["npm", "run", "cli", "--", "web"] 42 | ports: 43 | - ${WEB_LISTEN_PORT:-3000}:3000 44 | depends_on: 45 | - redis 46 | links: 47 | - mongo 48 | 49 | -------------------------------------------------------------------------------- /examples/nodeproj/example.env: -------------------------------------------------------------------------------- 1 | WEB_LISTEN_PORT=3000 2 | # pass UID= your IDE user 3 | 4 | -------------------------------------------------------------------------------- /examples/nodeproj/example.local.env: -------------------------------------------------------------------------------- 1 | REDIS_HOST=redis 2 | 3 | -------------------------------------------------------------------------------- /examples/nodeproj/index.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | "use strict"; 3 | import {start} from "./lib"; 4 | 5 | start(); 6 | 7 | -------------------------------------------------------------------------------- /examples/nodeproj/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "es2020", 5 | "moduleResolution": "node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "files": [ 9 | "index.js" 10 | ], 11 | "include": [ 12 | "lib/**/*.js" 13 | ] 14 | } -------------------------------------------------------------------------------- /examples/nodeproj/lib/commands/task.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import {proj} from "../proj"; 3 | 4 | async function loop() { 5 | const poped = await proj.predis.blpop("queue", 5); 6 | const task_desc_s = poped[1]; 7 | let task_desc; 8 | try { 9 | task_desc = JSON.parse(task_desc_s); 10 | } catch (e) { 11 | console.exception(e); 12 | } 13 | console.info("got task "+task_desc.func); 14 | const func = task_desc.func; 15 | const args = task_desc.args; 16 | if (typeof(proj.tasks[func])!="function") { 17 | console.log(`task ${func} not found`); 18 | process.exit(-1) 19 | } 20 | try { 21 | await ((this.tasks[func])(...args)); 22 | } catch (e) { 23 | console.exception(e); 24 | } 25 | } 26 | 27 | export async function start() { 28 | while(true) { 29 | loop(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/nodeproj/lib/commands/web.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import {proj} from "../proj"; 3 | 4 | import http from "http"; 5 | import express from "express"; 6 | 7 | 8 | export async function start() { 9 | const app = express(); 10 | const server = http.createServer(app); 11 | 12 | // Routing 13 | app.use(express.static(proj.config.basedir + "/public")); 14 | app.get("/healthz", function(req, res) { 15 | res.send("ok@"+Date.now()); 16 | }); 17 | 18 | server.listen(proj.config.LISTEN_PORT, proj.config.LISTEN_HOST, function() { 19 | console.warn(`listening at port ${proj.config.LISTEN_PORT}`); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /examples/nodeproj/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodeproj", 3 | "version": "0.0.1", 4 | "description": "nodejs example project", 5 | "exports": { 6 | ".": "./index.js", 7 | "./lib": "./lib" 8 | }, 9 | "main": "index.js", 10 | "type": "module", 11 | "scripts": { 12 | "cli": "nodemon -w lib -w index.js --es-module-specifier-resolution=node ./index.js" 13 | }, 14 | "dependencies": { 15 | "express": "~4.16.4", 16 | "redis": "^3.1.2" 17 | }, 18 | "private": true, 19 | "author": "", 20 | "license": "proprietary", 21 | "devDependencies": { 22 | "nodemon": "^2.0.14" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/nodeproj/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Vote 5 | 6 | 7 | 8 | 9 |

This is a Heading

10 |

This is a paragraph.

11 | 12 | 13 | 18 | 19 | -------------------------------------------------------------------------------- /examples/nvidia-smi/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | test: 3 | image: nvidia/cuda:12.3.1-base-ubuntu20.04 4 | command: nvidia-smi 5 | deploy: 6 | resources: 7 | reservations: 8 | devices: 9 | - driver: nvidia 10 | count: 1 11 | capabilities: [gpu] 12 | -------------------------------------------------------------------------------- /examples/wordpress/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | volumes: 3 | db_data: 4 | services: 5 | wordpress: 6 | image: docker.io/library/wordpress:latest 7 | ports: 8 | - 8080:80 9 | environment: 10 | - WORDPRESS_DB_HOST=db 11 | - WORDPRESS_DB_USER=wordpress 12 | - WORDPRESS_DB_PASSWORD=password 13 | - WORDPRESS_DB_NAME=wordpress 14 | db: 15 | image: docker.io/library/mariadb:10.6.4-focal 16 | command: '--default-authentication-plugin=mysql_native_password' 17 | volumes: 18 | - db_data:/var/lib/mysql 19 | environment: 20 | - MYSQL_ROOT_PASSWORD=somewordpress 21 | - MYSQL_DATABASE=wordpress 22 | - MYSQL_USER=wordpress 23 | - MYSQL_PASSWORD=password 24 | 25 | -------------------------------------------------------------------------------- /newsfragments/README.txt: -------------------------------------------------------------------------------- 1 | This is the directory for news fragments used by towncrier: https://github.com/hawkowl/towncrier 2 | 3 | You create a news fragment in this directory when you make a change, and the file gets removed from 4 | this directory when the news is published. 5 | 6 | towncrier has a few standard types of news fragments, signified by the file extension. These are: 7 | 8 | .feature: Signifying a new feature. 9 | .bugfix: Signifying a bug fix. 10 | .doc: Signifying a documentation improvement. 11 | .removal: Signifying a deprecation or removal of public API. 12 | .change: Signifying a change of behavior 13 | .misc: Miscellaneous change 14 | -------------------------------------------------------------------------------- /newsfragments/compose-systemd-unregister.feature: -------------------------------------------------------------------------------- 1 | - Add unregister command to remove systemd service registration (`podman-compose systemd -a unregister`) -------------------------------------------------------------------------------- /newsfragments/custom-pod-name-argument.change: -------------------------------------------------------------------------------- 1 | - Change behaviour of `--in-pod` to handle custom pod names instead of only disabling pod feature 2 | -------------------------------------------------------------------------------- /newsfragments/fix-build-ssh-path-to-be-relative.bugfix: -------------------------------------------------------------------------------- 1 | Fixed build ssh path to a local SSH key, to be relative to the directory of compose file. 2 | -------------------------------------------------------------------------------- /newsfragments/fix-cmd-healtchecks.bugfix: -------------------------------------------------------------------------------- 1 | Fixed support for CMD healthchecks to run using the given command directly and not using `/bin/sh -c`. 2 | -------------------------------------------------------------------------------- /newsfragments/io-podman-compose-service-label.feature: -------------------------------------------------------------------------------- 1 | Added `io.podman.compose.service` label to created containers. It contains the same value as com.docker.compose.service. 2 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.ruff] 2 | line-length = 100 3 | target-version = "py38" 4 | 5 | [tool.ruff.lint] 6 | select = ["W", "E", "F", "I"] 7 | ignore = [ 8 | ] 9 | 10 | [tool.ruff.lint.isort] 11 | force-single-line = true 12 | 13 | [tool.ruff.format] 14 | preview = true # needed for quote-style 15 | quote-style = "preserve" 16 | 17 | [tool.towncrier] 18 | package = "podman_compose" 19 | package_dir = "master" 20 | directory = "newsfragments" 21 | filename = "docs/Changelog-new.md" 22 | template = "scripts/Changelog-template.jinja" 23 | title_format = "Version {version} ({project_date})" 24 | [[tool.towncrier.section]] 25 | path = "" 26 | 27 | [[tool.towncrier.type]] 28 | directory = "feature" 29 | name = "Features" 30 | showcontent = true 31 | 32 | [[tool.towncrier.type]] 33 | directory = "change" 34 | name = "Changes" 35 | showcontent = true 36 | 37 | [[tool.towncrier.type]] 38 | directory = "bugfix" 39 | name = "Bug fixes" 40 | showcontent = true 41 | 42 | [[tool.towncrier.type]] 43 | directory = "doc" 44 | name = "Improved Documentation" 45 | showcontent = true 46 | 47 | [[tool.towncrier.type]] 48 | directory = "removal" 49 | name = "Deprecations and Removals" 50 | showcontent = true 51 | 52 | [[tool.towncrier.type]] 53 | directory = "misc" 54 | name = "Misc" 55 | showcontent = true 56 | 57 | [tool.mypy] 58 | python_version = "3.9" 59 | namespace_packages = true 60 | explicit_package_bases = true 61 | pretty = true 62 | warn_redundant_casts = true 63 | disallow_untyped_calls = false 64 | disallow_untyped_defs = true 65 | no_implicit_optional = true 66 | mypy_path = "$MYPY_CONFIG_FILE_DIR" 67 | exclude = "build" 68 | 69 | [[tool.mypy.overrides]] 70 | module = [ 71 | "parameterized.*", 72 | ] 73 | ignore_missing_imports = true 74 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # The order of packages is significant, because pip processes them in the order 2 | # of appearance. Changing the order has an impact on the overall integration 3 | # process, which may cause wedges in the gate later. 4 | 5 | pyyaml 6 | python-dotenv 7 | 8 | -------------------------------------------------------------------------------- /scripts/Changelog-template.jinja: -------------------------------------------------------------------------------- 1 | {% for section, _ in sections|dictsort(by='key') %} 2 | {% set underline = "-" %} 3 | {% if section %} 4 | {{section}} 5 | {{ underline * section|length }}{% set underline = "~" %} 6 | 7 | {% endif %} 8 | {% if sections[section] %} 9 | {% for category, val in definitions|dictsort if category in sections[section]%} 10 | 11 | {{ definitions[category]['name'] }} 12 | {{ underline * definitions[category]['name']|length }} 13 | 14 | {% for text, values in sections[section][category]|dictsort(by='value') %} 15 | - {{ text }} 16 | {% endfor %} 17 | 18 | {% if sections[section][category]|length == 0 %} 19 | 20 | No significant changes. 21 | 22 | 23 | {% else %} 24 | {% endif %} 25 | {% endfor %} 26 | {% else %} 27 | 28 | No significant changes. 29 | 30 | 31 | {% endif %} 32 | {% endfor %} 33 | (venv) p12@exec-desktop:~/cod 34 | -------------------------------------------------------------------------------- /scripts/clean_up.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | find . -name "*.pyc" -delete 4 | find . -name "__pycache__" -delete 5 | find . -name "*.orig" -delete 6 | rm -rf .cache/ 7 | rm -rf build/ 8 | rm -rf builddocs/ 9 | rm -rf dist/ 10 | rm -rf deb_dist/ 11 | rm src/podman_compose.egg-info -rf 12 | rm builddocs.zip 13 | -------------------------------------------------------------------------------- /scripts/download_and_build_podman-compose.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Delete repository dir 4 | rm -rf podman-compose-src 5 | 6 | # Clone repository 7 | git clone https://github.com/containers/podman-compose podman-compose-src 8 | 9 | # Generate binary 10 | sh podman-compose-src/scripts/generate_binary_using_dockerfile.sh 11 | 12 | # Move binary outside repo's dir 13 | mv podman-compose-src/podman-compose . 14 | 15 | # Delete repository dir 16 | rm -rf podman-compose-src 17 | -------------------------------------------------------------------------------- /scripts/generate_binary_using_dockerfile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Find an available container tool (docker or podman) 4 | find_container_tool() { 5 | if command -v docker > /dev/null 2>&1; then 6 | echo "sudo docker" 7 | elif command -v podman > /dev/null 2>&1; then 8 | echo "podman" 9 | else 10 | echo "Error: Neither docker nor podman is available." >&2 11 | exit 1 12 | fi 13 | } 14 | 15 | # Determine which container tool to use 16 | CONTAINER_TOOL=$(find_container_tool) 17 | 18 | # Locate the directory containing dockerfile (root) 19 | PROJECT_ROOT_DIR="$(cd "$(dirname "$0")" && pwd)/.." 20 | 21 | # Check SELinux status and set appropriate mount option 22 | check_selinux() { 23 | if command -v getenforce > /dev/null 2>&1; then 24 | SELINUX_STATUS=$(getenforce) 25 | if [ "$SELINUX_STATUS" = "Enforcing" ] || [ "$SELINUX_STATUS" = "Permissive" ]; then 26 | echo ":z" 27 | else 28 | echo "" 29 | fi 30 | elif [ -f /sys/fs/selinux/enforce ]; then 31 | if [ "$(cat /sys/fs/selinux/enforce)" = "1" ]; then 32 | echo ":z" 33 | else 34 | echo "" 35 | fi 36 | else 37 | echo "" 38 | fi 39 | } 40 | 41 | # Get the SELinux option for volume mounts if SELinux is enforcing or permissive 42 | SELINUX=$(check_selinux) 43 | 44 | # Build binary 45 | $CONTAINER_TOOL image rm build-podman-compose 46 | 47 | if expr "$CONTAINER_TOOL" : '.*docker.*' >/dev/null; then 48 | $CONTAINER_TOOL build -t build-podman-compose "$PROJECT_ROOT_DIR" 49 | $CONTAINER_TOOL run --name build-podman-compose build-podman-compose 50 | $CONTAINER_TOOL cp build-podman-compose:/result/podman-compose "$PROJECT_ROOT_DIR/podman-compose" 51 | $CONTAINER_TOOL container stop build-podman-compose 52 | $CONTAINER_TOOL container rm -f build-podman-compose 53 | else 54 | $CONTAINER_TOOL build -v "$PROJECT_ROOT_DIR:/result$SELINUX" -t build-podman-compose "$PROJECT_ROOT_DIR" 55 | fi 56 | $CONTAINER_TOOL image rm python:3.11-slim 57 | $CONTAINER_TOOL image rm build-podman-compose 58 | -------------------------------------------------------------------------------- /scripts/make_release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [ $# -ne 1 ]; then 6 | echo "Usage: make_release.sh VERSION" 7 | exit 1 8 | fi 9 | 10 | VERSION=$1 11 | 12 | sed "s/__version__ = .*/__version__ = \"$VERSION\"/g" -i podman_compose.py 13 | git add podman_compose.py 14 | git commit -m "Release $VERSION" 15 | 16 | git tag "v$VERSION" -m "v$VERSION" -s 17 | 18 | git push ssh://github.com/containers/podman-compose main "v$VERSION" 19 | -------------------------------------------------------------------------------- /scripts/make_release_notes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [ $# -ne 1 ]; then 6 | echo "Usage: make_release_notes.sh VERSION" 7 | exit 1 8 | fi 9 | 10 | VERSION=$1 11 | towncrier build --version "$VERSION" --yes 12 | git mv "docs/Changelog-new.md" "docs/Changelog-$VERSION.md" 13 | git add "newsfragments/" 14 | git commit -m "Release notes for $VERSION" 15 | -------------------------------------------------------------------------------- /scripts/make_release_upload.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ./scripts/uninstall.sh 3 | ./scripts/clean_up.sh 4 | python3 setup.py register 5 | python3 setup.py sdist bdist_wheel 6 | twine upload dist/* 7 | -------------------------------------------------------------------------------- /scripts/uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | pip3 uninstall podman-compose -y 3 | ./scripts/clean_up.sh 4 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | 4 | [metadata] 5 | version = attr: podman_compose.__version__ 6 | 7 | [flake8] 8 | # The GitHub editor is 127 chars wide 9 | max-line-length=127 10 | # These are not being followed yet 11 | ignore=E222,E231,E272,E713,W503 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | import os 4 | 5 | from setuptools import setup 6 | 7 | try: 8 | README = open(os.path.join(os.path.dirname(__file__), "README.md"), encoding="utf-8").read() 9 | except: # noqa: E722 # pylint: disable=bare-except 10 | README = "" 11 | 12 | setup( 13 | name="podman-compose", 14 | description="A script to run docker-compose.yml using podman", 15 | long_description=README, 16 | long_description_content_type="text/markdown", 17 | classifiers=[ 18 | "Programming Language :: Python", 19 | "Programming Language :: Python :: 3", 20 | "Programming Language :: Python :: 3.9", 21 | "Programming Language :: Python :: 3.10", 22 | "Programming Language :: Python :: 3.11", 23 | "Programming Language :: Python :: 3.12", 24 | "Programming Language :: Python :: 3.13", 25 | "Intended Audience :: Developers", 26 | "Operating System :: OS Independent", 27 | "Development Status :: 3 - Alpha", 28 | "Topic :: Software Development :: Build Tools", 29 | "License :: OSI Approved :: GNU General Public License v2 (GPLv2)", 30 | ], 31 | keywords="podman, podman-compose", 32 | author="Muayyad Alsadi", 33 | author_email="alsadi@gmail.com", 34 | url="https://github.com/containers/podman-compose", 35 | py_modules=["podman_compose"], 36 | entry_points={"console_scripts": ["podman-compose = podman_compose:main"]}, 37 | include_package_data=True, 38 | license="GPL-2.0-only", 39 | install_requires=[ 40 | "pyyaml", 41 | "python-dotenv", 42 | ], 43 | extras_require={"devel": ["ruff", "pre-commit", "coverage", "parameterized"]}, 44 | # test_suite='tests', 45 | # tests_require=[ 46 | # 'coverage', 47 | # 'tox', 48 | # ] 49 | ) 50 | -------------------------------------------------------------------------------- /test-requirements.txt: -------------------------------------------------------------------------------- 1 | -e . 2 | coverage==7.4.3 3 | cryptography==44.0.3 4 | parameterized==0.9.0 5 | pytest==8.0.2 6 | tox==4.13.0 7 | mypy==1.15.0 8 | ruff==0.11.11 9 | pylint==3.1.0 10 | types-PyYAML==6.0.12.20250402 11 | types-requests==2.32.0.20250328 12 | types-setuptools==80.7.0.20250516 13 | 14 | # The packages below are transitive dependencies of the packages above and are included here 15 | # to make testing reproducible. 16 | # To refresh, create a new virtualenv and do: 17 | # pip install -r requirements.txt -r test-requirements.txt 18 | # pip freeze > test-requirements.txt 19 | # and edit test-requirements.txt to add this comment 20 | 21 | astroid==3.1.0 22 | cachetools==5.3.3 23 | chardet==5.2.0 24 | colorama==0.4.6 25 | dill==0.3.8 26 | distlib==0.3.8 27 | filelock==3.13.1 28 | iniconfig==2.0.0 29 | isort==5.13.2 30 | mccabe==0.7.0 31 | mypy_extensions==1.1.0 32 | packaging==23.2 33 | platformdirs==4.2.0 34 | pluggy==1.4.0 35 | pyproject-api==1.6.1 36 | python-dotenv==1.0.1 37 | PyYAML==6.0.1 38 | requests 39 | tomlkit==0.12.4 40 | typing_extensions==4.13.2 41 | virtualenv==20.26.6 42 | -------------------------------------------------------------------------------- /tests/integration/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | 4 | 5 | def create_base_test_image() -> None: 6 | subprocess.check_call( 7 | ['podman', 'build', '-t', 'nopush/podman-compose-test', '.'], 8 | cwd=os.path.join(os.path.dirname(__file__), "base_image"), 9 | ) 10 | 11 | 12 | create_base_test_image() 13 | -------------------------------------------------------------------------------- /tests/integration/abort/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/containers/podman-compose/4c6df85efac6dea83cea68b406d3d1abfb7cacef/tests/integration/abort/__init__.py -------------------------------------------------------------------------------- /tests/integration/abort/docker-compose-fail-first.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | sh1: 4 | image: nopush/podman-compose-test 5 | command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 1; exit 1"] 6 | sh2: 7 | image: nopush/podman-compose-test 8 | command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 2; exit 0"] 9 | sh3: 10 | image: nopush/podman-compose-test 11 | command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 3; exit 0"] 12 | -------------------------------------------------------------------------------- /tests/integration/abort/docker-compose-fail-none.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | sh1: 4 | image: nopush/podman-compose-test 5 | command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 1; exit 0"] 6 | sh2: 7 | image: nopush/podman-compose-test 8 | command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 2; exit 0"] 9 | sh3: 10 | image: nopush/podman-compose-test 11 | command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 3; exit 0"] 12 | -------------------------------------------------------------------------------- /tests/integration/abort/docker-compose-fail-second.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | sh1: 4 | image: nopush/podman-compose-test 5 | command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 1; exit 0"] 6 | sh2: 7 | image: nopush/podman-compose-test 8 | command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 2; exit 1"] 9 | sh3: 10 | image: nopush/podman-compose-test 11 | command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 3; exit 0"] 12 | -------------------------------------------------------------------------------- /tests/integration/abort/docker-compose-fail-simultaneous.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | sh1: 4 | image: nopush/podman-compose-test 5 | command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 1; exit 1"] 6 | sh2: 7 | image: nopush/podman-compose-test 8 | command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 1; exit 0"] 9 | sh3: 10 | image: nopush/podman-compose-test 11 | command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 2; exit 0"] 12 | -------------------------------------------------------------------------------- /tests/integration/abort/test_podman_compose_abort.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | import os 4 | import unittest 5 | 6 | from parameterized import parameterized 7 | 8 | from tests.integration.test_utils import RunSubprocessMixin 9 | from tests.integration.test_utils import podman_compose_path 10 | from tests.integration.test_utils import test_path 11 | 12 | 13 | def compose_yaml_path(failure_order: str) -> str: 14 | return os.path.join(test_path(), "abort", f"docker-compose-fail-{failure_order}.yaml") 15 | 16 | 17 | class TestComposeAbort(unittest.TestCase, RunSubprocessMixin): 18 | @parameterized.expand([ 19 | ("exit", "first", 0), 20 | ("failure", "first", 1), 21 | ("exit", "second", 0), 22 | ("failure", "second", 1), 23 | ("exit", "simultaneous", 0), 24 | ("failure", "simultaneous", 1), 25 | ("exit", "none", 0), 26 | ("failure", "none", 0), 27 | ]) 28 | def test_abort(self, abort_type: str, failure_order: str, expected_exit_code: int) -> None: 29 | try: 30 | self.run_subprocess_assert_returncode( 31 | [ 32 | podman_compose_path(), 33 | "-f", 34 | compose_yaml_path(failure_order), 35 | "up", 36 | f"--abort-on-container-{abort_type}", 37 | ], 38 | expected_exit_code, 39 | ) 40 | finally: 41 | self.run_subprocess_assert_returncode([ 42 | podman_compose_path(), 43 | "-f", 44 | compose_yaml_path(failure_order), 45 | "down", 46 | ]) 47 | -------------------------------------------------------------------------------- /tests/integration/additional_contexts/README.md: -------------------------------------------------------------------------------- 1 | # Test podman-compose with build.additional_contexts 2 | 3 | ``` 4 | podman-compose build 5 | podman-compose up 6 | podman-compose down 7 | ``` 8 | 9 | expected output would be 10 | 11 | ``` 12 | [dict] | Data for dict 13 | [list] | Data for list 14 | ``` 15 | -------------------------------------------------------------------------------- /tests/integration/additional_contexts/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/additional_contexts/data_for_dict/data.txt: -------------------------------------------------------------------------------- 1 | Data for dict 2 | -------------------------------------------------------------------------------- /tests/integration/additional_contexts/data_for_list/data.txt: -------------------------------------------------------------------------------- 1 | Data for list 2 | -------------------------------------------------------------------------------- /tests/integration/additional_contexts/project/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM busybox 2 | COPY --from=data data.txt /data/data.txt 3 | CMD ["busybox", "cat", "/data/data.txt"] 4 | -------------------------------------------------------------------------------- /tests/integration/additional_contexts/project/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | services: 3 | dict: 4 | build: 5 | context: . 6 | additional_contexts: 7 | data: ../data_for_dict 8 | list: 9 | build: 10 | context: . 11 | additional_contexts: 12 | - data=../data_for_list 13 | -------------------------------------------------------------------------------- /tests/integration/additional_contexts/test_podman_compose_additional_contexts.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | 4 | """Test how additional contexts are passed to podman.""" 5 | 6 | import os 7 | import subprocess 8 | import unittest 9 | 10 | from tests.integration.test_utils import podman_compose_path 11 | from tests.integration.test_utils import test_path 12 | 13 | 14 | def compose_yaml_path() -> str: 15 | """ "Returns the path to the compose file used for this test module""" 16 | return os.path.join(test_path(), "additional_contexts", "project") 17 | 18 | 19 | class TestComposeBuildAdditionalContexts(unittest.TestCase): 20 | def test_build_additional_context(self) -> None: 21 | """podman build should receive additional contexts as --build-context 22 | 23 | See additional_context/project/docker-compose.yaml for context paths 24 | """ 25 | cmd = ( 26 | "coverage", 27 | "run", 28 | podman_compose_path(), 29 | "--dry-run", 30 | "--verbose", 31 | "-f", 32 | os.path.join(compose_yaml_path(), "docker-compose.yml"), 33 | "build", 34 | ) 35 | p = subprocess.run( 36 | cmd, 37 | stdout=subprocess.PIPE, 38 | check=False, 39 | stderr=subprocess.STDOUT, 40 | text=True, 41 | ) 42 | self.assertEqual(p.returncode, 0) 43 | self.assertIn("--build-context=data=../data_for_dict", p.stdout) 44 | self.assertIn("--build-context=data=../data_for_list", p.stdout) 45 | -------------------------------------------------------------------------------- /tests/integration/base_image/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/library/debian:bookworm-slim 2 | RUN apt-get update \ 3 | && apt-get install -y \ 4 | dumb-init \ 5 | busybox \ 6 | wget 7 | -------------------------------------------------------------------------------- /tests/integration/build/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/build/context/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM busybox 2 | RUN mkdir -p /var/www/html/ && date -Iseconds > /var/www/html/index.txt 3 | CMD ["busybox", "httpd", "-f", "-p", "80", "-h", "/var/www/html"] 4 | -------------------------------------------------------------------------------- /tests/integration/build/context/Dockerfile-alt: -------------------------------------------------------------------------------- 1 | FROM busybox 2 | ARG buildno=1 3 | ARG httpd_port=80 4 | ARG other_variable=not_set 5 | ENV httpd_port ${httpd_port} 6 | ENV other_variable ${other_variable} 7 | RUN mkdir -p /var/www/html/ && \ 8 | echo "ALT buildno=$buildno port=$httpd_port `date -Iseconds`" > /var/www/html/index.txt 9 | CMD httpd -f -p "$httpd_port" -h /var/www/html 10 | -------------------------------------------------------------------------------- /tests/integration/build/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | web1: 4 | build: ./context 5 | image: my-busybox-httpd 6 | ports: 7 | - 8080:80 8 | web2: 9 | build: 10 | context: ./context 11 | dockerfile: Dockerfile-alt 12 | labels: 13 | mykey: myval 14 | args: 15 | buildno: 2 16 | httpd_port: 8000 17 | image: my-busybox-httpd2 18 | ports: 19 | - 8000:8000 20 | test_build_arg_argument: 21 | build: 22 | context: ./context 23 | dockerfile: Dockerfile-alt 24 | image: my-busybox-httpd2 25 | command: env 26 | -------------------------------------------------------------------------------- /tests/integration/build/git_url_context/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/build/git_url_context/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | test_context: 4 | build: 5 | context: https://github.com/mokibit/test-git-url-as-context.git 6 | image: test-git-url-as-context 7 | test_context_inline: 8 | build: https://github.com/mokibit/test-git-url-as-context.git 9 | image: test-git-url-as-context-inline 10 | -------------------------------------------------------------------------------- /tests/integration/build/git_url_context/test_podman_compose_build_git_url_context.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | import os 4 | from parameterized import parameterized 5 | import unittest 6 | 7 | from tests.integration.test_utils import podman_compose_path 8 | from tests.integration.test_utils import test_path 9 | from tests.integration.test_utils import RunSubprocessMixin 10 | 11 | 12 | def compose_yaml_path(): 13 | """ "Returns the path to the compose file used for this test module""" 14 | base_path = os.path.join(test_path(), "build/git_url_context") 15 | return os.path.join(base_path, "docker-compose.yml") 16 | 17 | 18 | class TestComposeBuildGitUrlAsContext(unittest.TestCase, RunSubprocessMixin): 19 | @parameterized.expand([ 20 | ("git_url_context_test_context_1", "data_1.txt", b'test1\r\n'), 21 | ("git_url_context_test_context_1", "data_2.txt", b'test2\r\n'), 22 | ("git_url_context_test_context_inline_1", "data_1.txt", b'test1\r\n'), 23 | ("git_url_context_test_context_inline_1", "data_2.txt", b'test2\r\n'), 24 | ]) 25 | def test_build_git_url_as_context(self, container_name, file_name, output): 26 | # test if container can access specific files from git repository when git url is used as 27 | # a build context 28 | try: 29 | out, _ = self.run_subprocess_assert_returncode([ 30 | podman_compose_path(), 31 | "-f", 32 | compose_yaml_path(), 33 | "up", 34 | "-d", 35 | ]) 36 | 37 | out, _ = self.run_subprocess_assert_returncode([ 38 | "podman", 39 | "exec", 40 | "-ti", 41 | f"{container_name}", 42 | "sh", 43 | "-c", 44 | f"cat {file_name}", 45 | ]) 46 | self.assertEqual(out, output) 47 | finally: 48 | out, _ = self.run_subprocess_assert_returncode([ 49 | podman_compose_path(), 50 | "-f", 51 | compose_yaml_path(), 52 | "down", 53 | "-t", 54 | "0", 55 | ]) 56 | -------------------------------------------------------------------------------- /tests/integration/build/test_podman_compose_build.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | import os 4 | import unittest 5 | 6 | import requests 7 | 8 | from tests.integration.test_utils import podman_compose_path 9 | from tests.integration.test_utils import test_path 10 | from tests.integration.test_utils import RunSubprocessMixin 11 | 12 | 13 | def compose_yaml_path(): 14 | """ "Returns the path to the compose file used for this test module""" 15 | base_path = os.path.join(test_path(), "build") 16 | return os.path.join(base_path, "docker-compose.yml") 17 | 18 | 19 | class TestComposeBuild(unittest.TestCase, RunSubprocessMixin): 20 | def test_build(self): 21 | try: 22 | self.run_subprocess_assert_returncode([ 23 | podman_compose_path(), 24 | "-f", 25 | compose_yaml_path(), 26 | "build", 27 | "--no-cache", 28 | ]) 29 | 30 | self.run_subprocess_assert_returncode([ 31 | podman_compose_path(), 32 | "-f", 33 | compose_yaml_path(), 34 | "up", 35 | "-d", 36 | ]) 37 | 38 | request = requests.get('http://localhost:8080/index.txt') 39 | self.assertEqual(request.status_code, 200) 40 | 41 | alt_request_success = False 42 | try: 43 | # FIXME: suspicious behaviour, too often ends up in error 44 | alt_request = requests.get('http://localhost:8000/index.txt') 45 | self.assertEqual(alt_request.status_code, 200) 46 | self.assertIn("ALT buildno=2 port=8000 ", alt_request.text) 47 | alt_request_success = True 48 | except requests.exceptions.ConnectionError: 49 | pass 50 | 51 | if alt_request_success: 52 | output, _ = self.run_subprocess_assert_returncode([ 53 | "podman", 54 | "inspect", 55 | "my-busybox-httpd2", 56 | ]) 57 | self.assertIn("httpd_port=8000", str(output)) 58 | self.assertIn("buildno=2", str(output)) 59 | finally: 60 | self.run_subprocess_assert_returncode([ 61 | podman_compose_path(), 62 | "-f", 63 | compose_yaml_path(), 64 | "down", 65 | ]) 66 | -------------------------------------------------------------------------------- /tests/integration/build_fail/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/build_fail/context/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM busybox 2 | RUN this_command_does_not_exist 3 | CMD ["sh"] 4 | -------------------------------------------------------------------------------- /tests/integration/build_fail/context_no_file/NotDockerfile: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /tests/integration/build_fail/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | test: 4 | build: ./context 5 | image: build-fail-img 6 | test_no_dockerfile: 7 | build: 8 | context: ./context_no_file 9 | image: busybox 10 | test_no_custom_dockerfile: 11 | build: 12 | context: ./context_no_file 13 | dockerfile: Dockerfile-alt 14 | image: busybox 15 | -------------------------------------------------------------------------------- /tests/integration/build_fail/test_podman_compose_build_fail.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | import os 4 | import unittest 5 | 6 | from tests.integration.test_utils import RunSubprocessMixin 7 | from tests.integration.test_utils import podman_compose_path 8 | from tests.integration.test_utils import test_path 9 | 10 | 11 | def compose_yaml_path(): 12 | """ "Returns the path to the compose file used for this test module""" 13 | base_path = os.path.join(test_path(), "build_fail") 14 | return os.path.join(base_path, "docker-compose.yml") 15 | 16 | 17 | class TestComposeBuildFail(unittest.TestCase, RunSubprocessMixin): 18 | def test_build_fail(self): 19 | output, error = self.run_subprocess_assert_returncode( 20 | [ 21 | podman_compose_path(), 22 | "-f", 23 | compose_yaml_path(), 24 | "build", 25 | "test", 26 | ], 27 | expected_returncode=127, 28 | ) 29 | self.assertIn("RUN this_command_does_not_exist", str(output)) 30 | self.assertIn("this_command_does_not_exist: not found", str(error)) 31 | self.assertIn("while running runtime: exit status 127", str(error)) 32 | 33 | def test_dockerfile_does_not_exist(self): 34 | out, error = self.run_subprocess_assert_returncode( 35 | [ 36 | podman_compose_path(), 37 | "-f", 38 | compose_yaml_path(), 39 | "build", 40 | "test_no_dockerfile", 41 | ], 42 | expected_returncode=1, 43 | ) 44 | error = error.decode('utf-8') 45 | result = '\n'.join(error.splitlines()[-1:]) 46 | 47 | expected_path = os.path.join(os.path.dirname(__file__), "context_no_file") 48 | expected = f'OSError: Dockerfile not found in {expected_path}' 49 | 50 | self.assertEqual(expected, result) 51 | 52 | def test_custom_dockerfile_does_not_exist(self): 53 | out, error = self.run_subprocess_assert_returncode( 54 | [ 55 | podman_compose_path(), 56 | "-f", 57 | compose_yaml_path(), 58 | "build", 59 | "test_no_custom_dockerfile", 60 | ], 61 | expected_returncode=1, 62 | ) 63 | error = error.decode('utf-8') 64 | result = '\n'.join(error.splitlines()[-1:]) 65 | 66 | expected_path = os.path.join(os.path.dirname(__file__), "context_no_file/Dockerfile-alt") 67 | expected = f'OSError: Dockerfile not found in {expected_path}' 68 | 69 | self.assertEqual(expected, result) 70 | -------------------------------------------------------------------------------- /tests/integration/build_fail_multi/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/containers/podman-compose/4c6df85efac6dea83cea68b406d3d1abfb7cacef/tests/integration/build_fail_multi/__init__.py -------------------------------------------------------------------------------- /tests/integration/build_fail_multi/bad/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM busybox 2 | 3 | RUN false 4 | -------------------------------------------------------------------------------- /tests/integration/build_fail_multi/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | bad: 4 | build: 5 | context: bad 6 | good: 7 | build: 8 | context: good 9 | -------------------------------------------------------------------------------- /tests/integration/build_fail_multi/good/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM busybox 2 | #ensure that this build finishes second so that it has a chance to overwrite the return code 3 | RUN sleep 0.5 4 | -------------------------------------------------------------------------------- /tests/integration/build_fail_multi/test_podman_compose_build_fail_multi.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | import os 4 | import unittest 5 | 6 | from tests.integration.test_utils import RunSubprocessMixin 7 | from tests.integration.test_utils import podman_compose_path 8 | from tests.integration.test_utils import test_path 9 | 10 | 11 | def compose_yaml_path(): 12 | """ "Returns the path to the compose file used for this test module""" 13 | base_path = os.path.join(test_path(), "build_fail_multi") 14 | return os.path.join(base_path, "docker-compose.yml") 15 | 16 | 17 | class TestComposeBuildFailMulti(unittest.TestCase, RunSubprocessMixin): 18 | def test_build_fail_multi(self): 19 | output, error = self.run_subprocess_assert_returncode( 20 | [ 21 | podman_compose_path(), 22 | "-f", 23 | compose_yaml_path(), 24 | "build", 25 | # prevent the successful build from being cached to ensure it runs long enough 26 | "--no-cache", 27 | ], 28 | expected_returncode=1, 29 | ) 30 | self.assertIn("RUN false", str(output)) 31 | self.assertIn("while running runtime: exit status 1", str(error)) 32 | -------------------------------------------------------------------------------- /tests/integration/build_labels/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/containers/podman-compose/4c6df85efac6dea83cea68b406d3d1abfb7cacef/tests/integration/build_labels/__init__.py -------------------------------------------------------------------------------- /tests/integration/build_labels/context/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM busybox 2 | -------------------------------------------------------------------------------- /tests/integration/build_labels/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | test_build_labels_map: 4 | build: 5 | context: ./context 6 | dockerfile: Dockerfile 7 | labels: 8 | com.example.description: "Accounting webapp" 9 | com.example.department: "Finance" 10 | com.example.label-with-empty-value: "" 11 | image: my-busybox-build-labels-map 12 | command: env 13 | test_build_labels_array: 14 | build: 15 | context: ./context 16 | dockerfile: Dockerfile 17 | labels: 18 | - "com.example.description=Accounting webapp" 19 | - "com.example.department=Finance" 20 | - "com.example.label-with-empty-value" 21 | image: my-busybox-build-labels-array 22 | command: env 23 | -------------------------------------------------------------------------------- /tests/integration/build_labels/test_build_labels.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | 4 | import json 5 | import os 6 | import unittest 7 | 8 | from tests.integration.test_utils import RunSubprocessMixin 9 | from tests.integration.test_utils import podman_compose_path 10 | from tests.integration.test_utils import test_path 11 | 12 | 13 | class TestBuildLabels(unittest.TestCase, RunSubprocessMixin): 14 | def test_build_labels(self): 15 | """The build context can contain labels which should be added to the resulting image. They 16 | can be either an array or a map. 17 | """ 18 | 19 | compose_path = os.path.join(test_path(), "build_labels/docker-compose.yml") 20 | 21 | try: 22 | self.run_subprocess_assert_returncode([ 23 | podman_compose_path(), 24 | "-f", 25 | compose_path, 26 | "build", 27 | "test_build_labels_map", 28 | "test_build_labels_array", 29 | ]) 30 | 31 | expected_labels = { 32 | "com.example.department": "Finance", 33 | "com.example.description": "Accounting webapp", 34 | "com.example.label-with-empty-value": "", 35 | } 36 | 37 | out, _ = self.run_subprocess_assert_returncode([ 38 | "podman", 39 | "inspect", 40 | "my-busybox-build-labels-map", 41 | "my-busybox-build-labels-array", 42 | ]) 43 | 44 | images = json.loads(out) 45 | self.assertEqual(len(images), 2) 46 | labels_map = images[0].get("Config", {}).get("Labels", {}) 47 | labels_array = images[1].get("Config", {}).get("Labels", {}) 48 | for k, v in expected_labels.items(): 49 | self.assertIn(k, labels_map) 50 | self.assertEqual(labels_map[k], v) 51 | self.assertIn(k, labels_array) 52 | self.assertEqual(labels_array[k], v) 53 | 54 | finally: 55 | self.run_subprocess_assert_returncode([ 56 | "podman", 57 | "rmi", 58 | "my-busybox-build-labels-map", 59 | "my-busybox-build-labels-array", 60 | ]) 61 | -------------------------------------------------------------------------------- /tests/integration/build_secrets/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM busybox 2 | 3 | RUN --mount=type=secret,required=true,id=build_secret \ 4 | ls -l /run/secrets/ && cat /run/secrets/build_secret 5 | 6 | RUN --mount=type=secret,required=true,id=build_secret,target=/tmp/secret \ 7 | ls -l /run/secrets/ /tmp/ && cat /tmp/secret 8 | 9 | CMD [ 'echo', 'nothing here' ] 10 | -------------------------------------------------------------------------------- /tests/integration/build_secrets/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/build_secrets/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | test: 5 | image: test 6 | secrets: 7 | - run_secret # implicitly mount to /run/secrets/run_secret 8 | - source: run_secret 9 | target: /tmp/run_secret2 # explicit mount point 10 | 11 | build: 12 | context: . 13 | secrets: 14 | - build_secret # can be mounted in Dockerfile with "RUN --mount=type=secret,id=build_secret" 15 | - source: build_secret 16 | target: build_secret2 # rename to build_secret2 17 | 18 | secrets: 19 | build_secret: 20 | file: ./my_secret 21 | run_secret: 22 | file: ./my_secret 23 | -------------------------------------------------------------------------------- /tests/integration/build_secrets/docker-compose.yaml.invalid: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | test: 5 | image: test 6 | build: 7 | context: . 8 | secrets: 9 | # invalid target argument 10 | # 11 | # According to https://github.com/compose-spec/compose-spec/blob/master/build.md, target is 12 | # supposed to be the "name of a *file* to be mounted in /run/secrets/". Not a path. 13 | - source: build_secret 14 | target: /build_secret 15 | 16 | secrets: 17 | build_secret: 18 | file: ./my_secret 19 | -------------------------------------------------------------------------------- /tests/integration/build_secrets/my_secret: -------------------------------------------------------------------------------- 1 | important-secret-is-important 2 | -------------------------------------------------------------------------------- /tests/integration/build_ssh/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/build_ssh/context/Dockerfile: -------------------------------------------------------------------------------- 1 | # Base image 2 | FROM alpine:latest 3 | 4 | # Install OpenSSH client 5 | RUN apk add openssh 6 | 7 | # Test the SSH agents during the build 8 | 9 | RUN echo -n "default: " >> /result.log 10 | RUN --mount=type=ssh ssh-add -L >> /result.log 11 | 12 | RUN echo -n "id1: " >> /result.log 13 | RUN --mount=type=ssh,id=id1 ssh-add -L >> /result.log 14 | 15 | RUN echo -n "id2: " >> /result.log 16 | RUN --mount=type=ssh,id=id2 ssh-add -L >> /result.log 17 | -------------------------------------------------------------------------------- /tests/integration/build_ssh/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | test_build_ssh_map: 4 | build: 5 | context: ./context 6 | dockerfile: Dockerfile 7 | ssh: 8 | default: 9 | id1: "./id_ed25519_dummy" 10 | id2: "./agent_dummy.sock" 11 | image: my-alpine-build-ssh-map 12 | command: 13 | - cat 14 | - /result.log 15 | test_build_ssh_array: 16 | build: 17 | context: ./context 18 | dockerfile: Dockerfile 19 | ssh: 20 | - default 21 | - "id1=./id_ed25519_dummy" 22 | - "id2=./agent_dummy.sock" 23 | image: my-alpine-build-ssh-array 24 | command: 25 | - cat 26 | - /result.log 27 | -------------------------------------------------------------------------------- /tests/integration/build_ssh/id_ed25519_dummy: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW 3 | QyNTUxOQAAACBWELzfWvraCAeo0rOM2OxTGqWZx7fNBCglK/1oS8FLpgAAAJhzHuERcx7h 4 | EQAAAAtzc2gtZWQyNTUxOQAAACBWELzfWvraCAeo0rOM2OxTGqWZx7fNBCglK/1oS8FLpg 5 | AAAEAEIrYvY3jJ2IvAnUa5jIrVe8UG+7G7PzWzZqqBQykZllYQvN9a+toIB6jSs4zY7FMa 6 | pZnHt80EKCUr/WhLwUumAAAADnJpbmdvQGJuZHRib3gyAQIDBAUGBw== 7 | -----END OPENSSH PRIVATE KEY----- 8 | -------------------------------------------------------------------------------- /tests/integration/default_net_behavior/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/default_net_behavior/docker-compose_no_nets.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | web: 3 | image: busybox 4 | command: httpd -f -p 8123 -h /tmp/ 5 | -------------------------------------------------------------------------------- /tests/integration/default_net_behavior/docker-compose_no_nets_compat.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | web: 3 | image: busybox 4 | command: httpd -f -p 8123 -h /tmp/ 5 | 6 | x-podman: 7 | default_net_behavior_compat: true 8 | -------------------------------------------------------------------------------- /tests/integration/default_net_behavior/docker-compose_one_net.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | web: 3 | image: busybox 4 | command: httpd -f -p 8123 -h /tmp/ 5 | 6 | networks: 7 | net0: {} 8 | -------------------------------------------------------------------------------- /tests/integration/default_net_behavior/docker-compose_one_net_compat.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | web: 3 | image: busybox 4 | command: httpd -f -p 8123 -h /tmp/ 5 | 6 | networks: 7 | net0: {} 8 | 9 | x-podman: 10 | default_net_behavior_compat: true 11 | -------------------------------------------------------------------------------- /tests/integration/default_net_behavior/docker-compose_two_nets.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | web: 3 | image: busybox 4 | command: httpd -f -p 8123 -h /tmp/ 5 | 6 | networks: 7 | net0: {} 8 | net1: {} 9 | -------------------------------------------------------------------------------- /tests/integration/default_net_behavior/docker-compose_two_nets_compat.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | web: 3 | image: busybox 4 | command: httpd -f -p 8123 -h /tmp/ 5 | 6 | networks: 7 | net0: {} 8 | net1: {} 9 | 10 | x-podman: 11 | default_net_behavior_compat: true 12 | -------------------------------------------------------------------------------- /tests/integration/default_net_behavior/docker-compose_with_default.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | web: 3 | image: busybox 4 | command: httpd -f -p 8123 -h /tmp/ 5 | 6 | networks: 7 | net0: {} 8 | net1: {} 9 | default: {} 10 | -------------------------------------------------------------------------------- /tests/integration/default_net_behavior/docker-compose_with_default_compat.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | web: 3 | image: busybox 4 | command: httpd -f -p 8123 -h /tmp/ 5 | 6 | networks: 7 | net0: {} 8 | net1: {} 9 | default: {} 10 | 11 | x-podman: 12 | default_net_behavior_compat: true 13 | -------------------------------------------------------------------------------- /tests/integration/default_net_behavior/test_podman_compose_default_net_behavior.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | import os 4 | import unittest 5 | 6 | from parameterized import parameterized 7 | 8 | from tests.integration.test_utils import RunSubprocessMixin 9 | from tests.integration.test_utils import podman_compose_path 10 | from tests.integration.test_utils import test_path 11 | 12 | 13 | def compose_yaml_path(scenario: str) -> str: 14 | return os.path.join( 15 | os.path.join(test_path(), "default_net_behavior"), f"docker-compose_{scenario}.yaml" 16 | ) 17 | 18 | 19 | class TestComposeDefaultNetBehavior(unittest.TestCase, RunSubprocessMixin): 20 | @parameterized.expand([ 21 | ('no_nets', 'default_net_behavior_default'), 22 | ('one_net', 'default_net_behavior_net0'), 23 | ('two_nets', 'podman'), 24 | ('with_default', 'default_net_behavior_default'), 25 | ('no_nets_compat', 'default_net_behavior_default'), 26 | ('one_net_compat', 'default_net_behavior_default'), 27 | ('two_nets_compat', 'default_net_behavior_default'), 28 | ('with_default_compat', 'default_net_behavior_default'), 29 | ]) 30 | def test_nethost(self, scenario: str, default_net: str) -> None: 31 | try: 32 | self.run_subprocess_assert_returncode( 33 | [podman_compose_path(), "-f", compose_yaml_path(scenario), "up", "-d"], 34 | ) 35 | 36 | container_id_out, _ = self.run_subprocess_assert_returncode( 37 | [ 38 | podman_compose_path(), 39 | "-f", 40 | compose_yaml_path(scenario), 41 | "ps", 42 | "--format", 43 | '{{.ID}}', 44 | ], 45 | ) 46 | container_id = container_id_out.decode('utf-8').split('\n')[0] 47 | output, _ = self.run_subprocess_assert_returncode( 48 | [ 49 | "podman", 50 | "inspect", 51 | container_id, 52 | "--format", 53 | "{{range $key, $value := .NetworkSettings.Networks }}{{ $key }}\n{{ end }}", 54 | ], 55 | ) 56 | self.assertEqual(output.decode('utf-8').strip(), default_net) 57 | finally: 58 | self.run_subprocess_assert_returncode([ 59 | podman_compose_path(), 60 | "-f", 61 | compose_yaml_path(scenario), 62 | "down", 63 | "-t", 64 | "0", 65 | ]) 66 | -------------------------------------------------------------------------------- /tests/integration/deps/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/deps/docker-compose-conditional-fails.yaml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | services: 3 | web: 4 | image: nopush/podman-compose-test 5 | command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", "/etc/", "-p", "8000"] 6 | tmpfs: 7 | - /run 8 | - /tmp 9 | healthcheck: 10 | test: ["CMD", "/bin/false"] 11 | interval: 10s # Time between health checks 12 | timeout: 1s # Time to wait for a response 13 | retries: 1 # Number of consecutive failures before marking as unhealthy 14 | sleep: 15 | image: nopush/podman-compose-test 16 | command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 3600"] 17 | depends_on: 18 | web: 19 | condition: service_healthy 20 | tmpfs: 21 | - /run 22 | - /tmp 23 | -------------------------------------------------------------------------------- /tests/integration/deps/docker-compose-conditional-healthy.yaml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | services: 3 | web: 4 | image: nopush/podman-compose-test 5 | command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", "/etc/", "-p", "8000"] 6 | tmpfs: 7 | - /run 8 | - /tmp 9 | healthcheck: 10 | test: ["CMD", "wget", "-qO-", "http://localhost:8000/hosts"] 11 | start_period: 10s # initialization time for containers that need time to bootstrap 12 | interval: 10s # Time between health checks 13 | timeout: 5s # Time to wait for a response 14 | retries: 3 # Number of consecutive failures before marking as unhealthy 15 | sleep: 16 | image: nopush/podman-compose-test 17 | command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 3600"] 18 | depends_on: 19 | web: 20 | condition: service_healthy 21 | tmpfs: 22 | - /run 23 | - /tmp 24 | -------------------------------------------------------------------------------- /tests/integration/deps/docker-compose-conditional-succeeds.yaml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | services: 3 | web: 4 | image: nopush/podman-compose-test 5 | command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", "/etc/", "-p", "8000"] 6 | tmpfs: 7 | - /run 8 | - /tmp 9 | healthcheck: 10 | test: ["CMD", "wget", "-qO-", "http://localhost:8000/hosts"] 11 | interval: 30s # Time between health checks 12 | timeout: 5s # Time to wait for a response 13 | retries: 3 # Number of consecutive failures before marking as unhealthy 14 | sleep: 15 | image: nopush/podman-compose-test 16 | command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 3600"] 17 | depends_on: 18 | web: 19 | condition: service_healthy 20 | tmpfs: 21 | - /run 22 | - /tmp 23 | -------------------------------------------------------------------------------- /tests/integration/deps/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | services: 3 | web: 4 | image: nopush/podman-compose-test 5 | command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", "/etc/", "-p", "8000"] 6 | tmpfs: 7 | - /run 8 | - /tmp 9 | sleep: 10 | image: nopush/podman-compose-test 11 | command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 3600"] 12 | depends_on: 13 | - "web" 14 | tmpfs: 15 | - /run 16 | - /tmp 17 | sleep2: 18 | image: nopush/podman-compose-test 19 | command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 3600"] 20 | depends_on: 21 | - sleep 22 | tmpfs: 23 | - /run 24 | - /tmp 25 | 26 | -------------------------------------------------------------------------------- /tests/integration/env_file_tests/.env: -------------------------------------------------------------------------------- 1 | ZZVAR1='This value is overwritten by env_file_tests/.env' 2 | ZZVAR3='This value is loaded from env_file_tests/.env' 3 | -------------------------------------------------------------------------------- /tests/integration/env_file_tests/.gitignore: -------------------------------------------------------------------------------- 1 | # This overrides the repository root .gitignore (ignoring all .env). 2 | # The .env files in this directory are important for the test cases. 3 | !.env 4 | !project/.env 5 | -------------------------------------------------------------------------------- /tests/integration/env_file_tests/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/env_file_tests/env-files/project-1.env: -------------------------------------------------------------------------------- 1 | ZZVAR1=podman-rocks-123 2 | ZZVAR2=podman-rocks-124 3 | ZZVAR3=podman-rocks-125 4 | -------------------------------------------------------------------------------- /tests/integration/env_file_tests/env-files/project-2.env: -------------------------------------------------------------------------------- 1 | ZZVAR1=podman-rocks-223 2 | ZZVAR2=podman-rocks-224 3 | -------------------------------------------------------------------------------- /tests/integration/env_file_tests/project/.env: -------------------------------------------------------------------------------- 1 | ZZVAR1='This value is loaded but should be overwritten' 2 | ZZVAR2='This value is loaded from .env in project/ directory' 3 | -------------------------------------------------------------------------------- /tests/integration/env_file_tests/project/container-compose.env-file-flat.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | image: busybox 4 | command: ["/bin/busybox", "sh", "-c", "env | grep ZZ"] 5 | tmpfs: 6 | - /run 7 | - /tmp 8 | env_file: 9 | - ../env-files/project-1.env 10 | -------------------------------------------------------------------------------- /tests/integration/env_file_tests/project/container-compose.env-file-obj-optional-exists.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | image: busybox 4 | command: ["/bin/busybox", "sh", "-c", "env | grep ZZ"] 5 | tmpfs: 6 | - /run 7 | - /tmp 8 | env_file: 9 | - path: ../env-files/project-1.env 10 | - path: ../env-files/project-2.env # this file exists 11 | required: false 12 | -------------------------------------------------------------------------------- /tests/integration/env_file_tests/project/container-compose.env-file-obj-optional-missing.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | image: busybox 4 | command: ["/bin/busybox", "sh", "-c", "env | grep ZZ"] 5 | tmpfs: 6 | - /run 7 | - /tmp 8 | env_file: 9 | - path: ../env-files/project-1.env 10 | - path: ../env-files/project-3.env # this file is missing 11 | required: false 12 | -------------------------------------------------------------------------------- /tests/integration/env_file_tests/project/container-compose.env-file-obj.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | image: busybox 4 | command: ["/bin/busybox", "sh", "-c", "env | grep ZZ"] 5 | tmpfs: 6 | - /run 7 | - /tmp 8 | env_file: 9 | - path: ../env-files/project-1.env 10 | -------------------------------------------------------------------------------- /tests/integration/env_file_tests/project/container-compose.load-.env-in-project.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | image: busybox 4 | command: ["/bin/busybox", "sh", "-c", "env | grep ZZ"] 5 | tmpfs: 6 | - /run 7 | - /tmp 8 | environment: 9 | ZZVAR1: $ZZVAR1 10 | ZZVAR2: $ZZVAR2 11 | ZZVAR3: $ZZVAR3 12 | -------------------------------------------------------------------------------- /tests/integration/env_file_tests/project/container-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | image: busybox 4 | command: ["/bin/busybox", "sh", "-c", "env | grep ZZ"] 5 | tmpfs: 6 | - /run 7 | - /tmp 8 | environment: 9 | ZZVAR1: $ZZVAR1 10 | -------------------------------------------------------------------------------- /tests/integration/env_tests/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/env_tests/container-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | name: my-project-name 4 | 5 | services: 6 | env-test: 7 | image: busybox 8 | command: sh -c "export | grep ZZ" 9 | environment: 10 | ZZVAR1: myval1 11 | ZZVAR2: 2-$ZZVAR1 12 | ZZVAR3: 3-$ZZVAR2 13 | 14 | project-name-test: 15 | image: busybox 16 | command: sh -c "echo $$PNAME" 17 | environment: 18 | PNAME: ${COMPOSE_PROJECT_NAME} 19 | -------------------------------------------------------------------------------- /tests/integration/exit_from/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/exit_from/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | sh1: 4 | image: nopush/podman-compose-test 5 | command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 1; exit 1"] 6 | tmpfs: 7 | - /run 8 | - /tmp 9 | sh2: 10 | image: nopush/podman-compose-test 11 | command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 1; exit 2"] 12 | tmpfs: 13 | - /run 14 | - /tmp 15 | 16 | -------------------------------------------------------------------------------- /tests/integration/exit_from/test_podman_compose_exit_from.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | import os 4 | import unittest 5 | 6 | from tests.integration.test_utils import RunSubprocessMixin 7 | from tests.integration.test_utils import podman_compose_path 8 | from tests.integration.test_utils import test_path 9 | 10 | 11 | def compose_yaml_path() -> str: 12 | return os.path.join(os.path.join(test_path(), "exit_from"), "docker-compose.yaml") 13 | 14 | 15 | class TestComposeExitFrom(unittest.TestCase, RunSubprocessMixin): 16 | def test_exit_code_sh1(self) -> None: 17 | try: 18 | self.run_subprocess_assert_returncode( 19 | [ 20 | podman_compose_path(), 21 | "-f", 22 | compose_yaml_path(), 23 | "up", 24 | "--exit-code-from=sh1", 25 | ], 26 | 1, 27 | ) 28 | finally: 29 | self.run_subprocess_assert_returncode([ 30 | podman_compose_path(), 31 | "-f", 32 | compose_yaml_path(), 33 | "down", 34 | ]) 35 | 36 | def test_exit_code_sh2(self) -> None: 37 | try: 38 | self.run_subprocess_assert_returncode( 39 | [ 40 | podman_compose_path(), 41 | "-f", 42 | compose_yaml_path(), 43 | "up", 44 | "--exit-code-from=sh2", 45 | ], 46 | 2, 47 | ) 48 | finally: 49 | self.run_subprocess_assert_returncode([ 50 | podman_compose_path(), 51 | "-f", 52 | compose_yaml_path(), 53 | "down", 54 | ]) 55 | 56 | def test_podman_compose_exit_from(self) -> None: 57 | up_cmd = [ 58 | "coverage", 59 | "run", 60 | podman_compose_path(), 61 | "-f", 62 | compose_yaml_path(), 63 | "up", 64 | ] 65 | 66 | self.run_subprocess_assert_returncode(up_cmd + ["--exit-code-from", "sh1"], 1) 67 | self.run_subprocess_assert_returncode(up_cmd + ["--exit-code-from", "sh2"], 2) 68 | -------------------------------------------------------------------------------- /tests/integration/extends/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/extends/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | echo: 4 | image: busybox 5 | command: ["/bin/busybox", "echo", "Zero"] 6 | ports: 7 | - '1234:1234' 8 | environment: 9 | - FOO=original 10 | - BAR=original 11 | # volumes: 12 | # - ./original:/foo 13 | # - ./original:/bar 14 | echo1: 15 | extends: 16 | service: echo 17 | command: ["/bin/busybox", "echo", "One"] 18 | ports: 19 | - '12345:12345' 20 | # volumes: 21 | # - ./local:/bar 22 | # - ./local:/baz 23 | env1: 24 | extends: 25 | service: echo 26 | command: ["/bin/busybox", "env"] 27 | environment: 28 | - BAR=local 29 | - BAZ=local 30 | -------------------------------------------------------------------------------- /tests/integration/extends_w_empty_service/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/extends_w_empty_service/common-services.yml: -------------------------------------------------------------------------------- 1 | services: 2 | webapp_default: 3 | 4 | webapp_special: 5 | image: nopush/podman-compose-test 6 | volumes: 7 | - "/data" 8 | -------------------------------------------------------------------------------- /tests/integration/extends_w_empty_service/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | web: 4 | image: nopush/podman-compose-test 5 | extends: 6 | file: common-services.yml 7 | service: webapp_default 8 | environment: 9 | - DEBUG=1 10 | cpu_shares: 5 11 | -------------------------------------------------------------------------------- /tests/integration/extends_w_empty_service/test_podman_compose_extends_w_empty_service.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | import os 4 | import unittest 5 | from pathlib import Path 6 | 7 | from tests.integration.test_utils import RunSubprocessMixin 8 | from tests.integration.test_utils import podman_compose_path 9 | from tests.integration.test_utils import test_path 10 | 11 | 12 | def compose_yaml_path() -> str: 13 | return os.path.join(os.path.join(test_path(), "extends_w_empty_service"), "docker-compose.yml") 14 | 15 | 16 | class TestComposeExtendsWithEmptyService(unittest.TestCase, RunSubprocessMixin): 17 | def test_extends_w_empty_service(self) -> None: 18 | try: 19 | self.run_subprocess_assert_returncode( 20 | [ 21 | podman_compose_path(), 22 | "-f", 23 | compose_yaml_path(), 24 | "up", 25 | ], 26 | ) 27 | output, _ = self.run_subprocess_assert_returncode([ 28 | podman_compose_path(), 29 | "-f", 30 | compose_yaml_path(), 31 | "ps", 32 | ]) 33 | self.assertIn("extends_w_empty_service_web_1", str(output)) 34 | finally: 35 | self.run_subprocess_assert_returncode([ 36 | podman_compose_path(), 37 | "-f", 38 | compose_yaml_path(), 39 | "down", 40 | ]) 41 | 42 | def test_podman_compose_extends_w_empty_service(self) -> None: 43 | """ 44 | Test that podman-compose can execute podman-compose -f up with extended File which 45 | includes an empty service. (e.g. if the file is used as placeholder for more complex 46 | configurations.) 47 | """ 48 | main_path = Path(__file__).parent.parent.parent.parent 49 | 50 | command_up = [ 51 | "python3", 52 | str(main_path.joinpath("podman_compose.py")), 53 | "-f", 54 | str( 55 | main_path.joinpath( 56 | "tests", "integration", "extends_w_empty_service", "docker-compose.yml" 57 | ) 58 | ), 59 | "up", 60 | "-d", 61 | ] 62 | 63 | self.run_subprocess_assert_returncode(command_up) 64 | -------------------------------------------------------------------------------- /tests/integration/extends_w_file/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nopush/podman-compose-test as base 2 | -------------------------------------------------------------------------------- /tests/integration/extends_w_file/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/extends_w_file/common-services.yml: -------------------------------------------------------------------------------- 1 | webapp: 2 | build: . 3 | ports: 4 | - "8000:8000" 5 | volumes: 6 | - "/data" 7 | 8 | -------------------------------------------------------------------------------- /tests/integration/extends_w_file/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | web: 4 | extends: 5 | file: common-services.yml 6 | service: webapp 7 | environment: 8 | - DEBUG=1 9 | cpu_shares: 5 10 | 11 | important_web: 12 | extends: web 13 | cpu_shares: 10 14 | 15 | -------------------------------------------------------------------------------- /tests/integration/extends_w_file/test_podman_compose_extends_w_file.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | import os 4 | import unittest 5 | 6 | from tests.integration.test_utils import RunSubprocessMixin 7 | from tests.integration.test_utils import podman_compose_path 8 | from tests.integration.test_utils import test_path 9 | 10 | 11 | def compose_yaml_path() -> str: 12 | return os.path.join(os.path.join(test_path(), "extends_w_file"), "docker-compose.yml") 13 | 14 | 15 | class TestComposeExtendsWithFile(unittest.TestCase, RunSubprocessMixin): 16 | def test_extends_w_file(self) -> None: # when file is Dockerfile for building the image 17 | try: 18 | self.run_subprocess_assert_returncode( 19 | [ 20 | podman_compose_path(), 21 | "-f", 22 | compose_yaml_path(), 23 | "up", 24 | ], 25 | ) 26 | output, _ = self.run_subprocess_assert_returncode([ 27 | podman_compose_path(), 28 | "-f", 29 | compose_yaml_path(), 30 | "ps", 31 | ]) 32 | self.assertIn("extends_w_file_web_1", str(output)) 33 | self.assertIn("extends_w_file_important_web_1", str(output)) 34 | finally: 35 | self.run_subprocess_assert_returncode([ 36 | podman_compose_path(), 37 | "-f", 38 | compose_yaml_path(), 39 | "down", 40 | ]) 41 | -------------------------------------------------------------------------------- /tests/integration/extends_w_file_subdir/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/extends_w_file_subdir/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | web: 4 | extends: 5 | file: sub/docker-compose.yml 6 | service: webapp 7 | environment: 8 | - DEBUG=1 -------------------------------------------------------------------------------- /tests/integration/extends_w_file_subdir/sub/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | webapp: 4 | build: 5 | context: docker/example 6 | dockerfile: Dockerfile 7 | image: localhost/subdir_test:me 8 | ports: 9 | - "8000:8000" 10 | volumes: 11 | - "/data" 12 | 13 | -------------------------------------------------------------------------------- /tests/integration/extends_w_file_subdir/sub/docker/example/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nopush/podman-compose-test as base 2 | -------------------------------------------------------------------------------- /tests/integration/filesystem/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/filesystem/compose_symlink/docker-compose.yml: -------------------------------------------------------------------------------- 1 | ../compose_symlink_dest/docker-compose.yml -------------------------------------------------------------------------------- /tests/integration/filesystem/compose_symlink/file: -------------------------------------------------------------------------------- 1 | data_compose_symlink 2 | -------------------------------------------------------------------------------- /tests/integration/filesystem/compose_symlink_dest/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | container1: 4 | image: nopush/podman-compose-test 5 | command: ["/bin/busybox", "cat", "/file"] 6 | volumes: 7 | - "./file:/file" 8 | -------------------------------------------------------------------------------- /tests/integration/filesystem/compose_symlink_dest/file: -------------------------------------------------------------------------------- 1 | data_compose_symlink_dest 2 | -------------------------------------------------------------------------------- /tests/integration/filesystem/test_podman_compose_filesystem.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | 4 | import os 5 | import unittest 6 | 7 | from tests.integration.test_utils import RunSubprocessMixin 8 | from tests.integration.test_utils import podman_compose_path 9 | from tests.integration.test_utils import test_path 10 | 11 | 12 | class TestFilesystem(unittest.TestCase, RunSubprocessMixin): 13 | def test_compose_symlink(self) -> None: 14 | """The context of podman-compose.yml should come from the same directory as the file even 15 | if it is a symlink 16 | """ 17 | 18 | compose_path = os.path.join(test_path(), "filesystem/compose_symlink/docker-compose.yml") 19 | 20 | try: 21 | self.run_subprocess_assert_returncode([ 22 | podman_compose_path(), 23 | "-f", 24 | compose_path, 25 | "up", 26 | "-d", 27 | "container1", 28 | ]) 29 | 30 | out, _ = self.run_subprocess_assert_returncode([ 31 | podman_compose_path(), 32 | "-f", 33 | compose_path, 34 | "logs", 35 | "container1", 36 | ]) 37 | 38 | self.assertEqual(out, b'data_compose_symlink\n') 39 | 40 | finally: 41 | out, _ = self.run_subprocess_assert_returncode([ 42 | podman_compose_path(), 43 | "-f", 44 | compose_path, 45 | "down", 46 | ]) 47 | -------------------------------------------------------------------------------- /tests/integration/in_pod/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/in_pod/custom_x-podman_custom_name/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | cont: 4 | image: nopush/podman-compose-test 5 | command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-p", "8080"] 6 | 7 | x-podman: 8 | in_pod: custom_test_pod_name 9 | -------------------------------------------------------------------------------- /tests/integration/in_pod/custom_x-podman_false/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | cont: 4 | image: nopush/podman-compose-test 5 | userns_mode: keep-id:uid=1000 6 | command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-p", "8080"] 7 | 8 | x-podman: 9 | in_pod: false 10 | -------------------------------------------------------------------------------- /tests/integration/in_pod/custom_x-podman_not_exists/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | cont: 4 | image: nopush/podman-compose-test 5 | userns_mode: keep-id:uid=1000 6 | command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-p", "8080"] 7 | -------------------------------------------------------------------------------- /tests/integration/in_pod/custom_x-podman_true/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | cont: 4 | image: nopush/podman-compose-test 5 | userns_mode: keep-id:uid=1000 6 | command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-p", "8080"] 7 | 8 | x-podman: 9 | in_pod: true 10 | -------------------------------------------------------------------------------- /tests/integration/include/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/include/docker-compose.base.yaml: -------------------------------------------------------------------------------- 1 | version: '3.6' 2 | 3 | services: 4 | web: 5 | image: nopush/podman-compose-test 6 | command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", ".", "-p", "8003"] 7 | 8 | -------------------------------------------------------------------------------- /tests/integration/include/docker-compose.extend.yaml: -------------------------------------------------------------------------------- 1 | version: '3.6' 2 | 3 | services: 4 | web2: 5 | image: nopush/podman-compose-test 6 | command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", ".", "-p", "8004"] 7 | -------------------------------------------------------------------------------- /tests/integration/include/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.6' 2 | 3 | include: 4 | - docker-compose.base.yaml 5 | - docker-compose.extend.yaml 6 | -------------------------------------------------------------------------------- /tests/integration/include/test_podman_compose_include.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | import unittest 4 | from pathlib import Path 5 | 6 | from tests.integration.test_utils import RunSubprocessMixin 7 | 8 | 9 | class TestPodmanComposeInclude(unittest.TestCase, RunSubprocessMixin): 10 | def test_podman_compose_include(self) -> None: 11 | """ 12 | Test that podman-compose can execute podman-compose -f up with include 13 | :return: 14 | """ 15 | main_path = Path(__file__).parent.parent.parent.parent 16 | 17 | command_up = [ 18 | "coverage", 19 | "run", 20 | str(main_path.joinpath("podman_compose.py")), 21 | "-f", 22 | str(main_path.joinpath("tests", "integration", "include", "docker-compose.yaml")), 23 | "up", 24 | "-d", 25 | ] 26 | 27 | command_check_container = [ 28 | "podman", 29 | "ps", 30 | "-a", 31 | "--filter", 32 | "label=io.podman.compose.project=include", 33 | "--format", 34 | '"{{.Image}}"', 35 | ] 36 | 37 | command_container_id = [ 38 | "podman", 39 | "ps", 40 | "-a", 41 | "--filter", 42 | "label=io.podman.compose.project=include", 43 | "--format", 44 | '"{{.ID}}"', 45 | ] 46 | 47 | command_down = ["podman", "rm", "--force"] 48 | 49 | self.run_subprocess_assert_returncode(command_up) 50 | out, _ = self.run_subprocess_assert_returncode(command_check_container) 51 | expected_output = b'"localhost/nopush/podman-compose-test:latest"\n' * 2 52 | self.assertEqual(out, expected_output) 53 | # Get container ID to remove it 54 | out, _ = self.run_subprocess_assert_returncode(command_container_id) 55 | self.assertNotEqual(out, b"") 56 | container_ids = out.decode().strip().split("\n") 57 | container_ids = [container_id.replace('"', "") for container_id in container_ids] 58 | command_down.extend(container_ids) 59 | out, _ = self.run_subprocess_assert_returncode(command_down) 60 | # cleanup test image(tags) 61 | self.assertNotEqual(out, b"") 62 | # check container did not exists anymore 63 | out, _ = self.run_subprocess_assert_returncode(command_check_container) 64 | self.assertEqual(out, b"") 65 | -------------------------------------------------------------------------------- /tests/integration/interpolation/.env: -------------------------------------------------------------------------------- 1 | DOT_ENV_VARIABLE=This value is from the .env file 2 | -------------------------------------------------------------------------------- /tests/integration/interpolation/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/interpolation/docker-compose-colon-question-error.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | services: 3 | variables: 4 | image: busybox 5 | command: ["/bin/busybox", "sh", "-c", "export | grep EXAMPLE"] 6 | environment: 7 | EXAMPLE_COLON_QUESTION_ERROR: ${NOT_A_VARIABLE:?Missing variable} 8 | 9 | -------------------------------------------------------------------------------- /tests/integration/interpolation/docker-compose-question-error.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | services: 3 | variables: 4 | image: busybox 5 | command: ["/bin/busybox", "sh", "-c", "export | grep EXAMPLE"] 6 | environment: 7 | EXAMPLE_QUESTION_ERROR: ${NOT_A_VARIABLE?Missing variable} 8 | 9 | -------------------------------------------------------------------------------- /tests/integration/interpolation/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | services: 3 | variables: 4 | image: busybox 5 | command: ["/bin/busybox", "sh", "-c", "export | grep EXAMPLE"] 6 | environment: 7 | EXAMPLE_VARIABLE: "Host user: $EXAMPLE_VARIABLE_USER" 8 | EXAMPLE_BRACES: "Host user: ${EXAMPLE_VARIABLE_USER}" 9 | EXAMPLE_COLON_DASH_DEFAULT: ${NOT_A_VARIABLE:-My default} 10 | EXAMPLE_DASH_DEFAULT: ${NOT_A_VARIABLE-My other default} 11 | EXAMPLE_DOT_ENV: $DOT_ENV_VARIABLE 12 | EXAMPLE_LITERAL: This is a $$literal 13 | EXAMPLE_EMPTY: $NOT_A_VARIABLE 14 | 15 | -------------------------------------------------------------------------------- /tests/integration/interpolation/test_podman_compose_interpolation.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | import os 4 | import unittest 5 | 6 | from tests.integration.test_utils import RunSubprocessMixin 7 | from tests.integration.test_utils import podman_compose_path 8 | from tests.integration.test_utils import test_path 9 | 10 | 11 | def compose_yaml_path() -> str: 12 | return os.path.join(os.path.join(test_path(), "interpolation"), "docker-compose.yml") 13 | 14 | 15 | class TestComposeInterpolation(unittest.TestCase, RunSubprocessMixin): 16 | def test_interpolation(self) -> None: 17 | try: 18 | self.run_subprocess_assert_returncode([ 19 | "env", 20 | "EXAMPLE_VARIABLE_USER=test_user", 21 | podman_compose_path(), 22 | "-f", 23 | compose_yaml_path(), 24 | "up", 25 | ]) 26 | output, _ = self.run_subprocess_assert_returncode([ 27 | podman_compose_path(), 28 | "-f", 29 | compose_yaml_path(), 30 | "logs", 31 | ]) 32 | self.assertIn("EXAMPLE_VARIABLE='Host user: test_user'", str(output)) 33 | self.assertIn("EXAMPLE_BRACES='Host user: test_user'", str(output)) 34 | self.assertIn("EXAMPLE_COLON_DASH_DEFAULT='My default'", str(output)) 35 | self.assertIn("EXAMPLE_DASH_DEFAULT='My other default'", str(output)) 36 | self.assertIn("EXAMPLE_DOT_ENV='This value is from the .env file'", str(output)) 37 | self.assertIn("EXAMPLE_EMPTY=''", str(output)) 38 | self.assertIn("EXAMPLE_LITERAL='This is a $literal'", str(output)) 39 | finally: 40 | self.run_subprocess_assert_returncode([ 41 | podman_compose_path(), 42 | "-f", 43 | compose_yaml_path(), 44 | "down", 45 | ]) 46 | -------------------------------------------------------------------------------- /tests/integration/ipam_default/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/ipam_default/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | # --ipam-driver must not be passed when driver is "default" 4 | networks: 5 | ipam_test_default: 6 | ipam: 7 | driver: default 8 | config: 9 | - subnet: 172.19.0.0/24 10 | 11 | services: 12 | testipam: 13 | image: busybox 14 | command: ["echo", "ipamtest"] 15 | 16 | -------------------------------------------------------------------------------- /tests/integration/ipam_default/test_podman_compose_ipam_default.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | import json 4 | import os 5 | import unittest 6 | 7 | from tests.integration.test_utils import RunSubprocessMixin 8 | from tests.integration.test_utils import podman_compose_path 9 | from tests.integration.test_utils import test_path 10 | 11 | 12 | def compose_yaml_path() -> str: 13 | return os.path.join(os.path.join(test_path(), "ipam_default"), "docker-compose.yaml") 14 | 15 | 16 | class TestComposeIpamDefault(unittest.TestCase, RunSubprocessMixin): 17 | def test_ipam_default(self) -> None: 18 | try: 19 | self.run_subprocess_assert_returncode( 20 | [podman_compose_path(), "-f", compose_yaml_path(), "up", "-d"], 21 | ) 22 | 23 | output, _ = self.run_subprocess_assert_returncode([ 24 | podman_compose_path(), 25 | "-f", 26 | compose_yaml_path(), 27 | "logs", 28 | ]) 29 | # when container is created, its command echoes 'ipamtest' 30 | # BUG: figure out why echo is called twice 31 | self.assertIn("ipamtest", str(output)) 32 | 33 | output, _ = self.run_subprocess_assert_returncode( 34 | [ 35 | "podman", 36 | "inspect", 37 | "ipam_default_testipam_1", 38 | ], 39 | ) 40 | network_info = json.loads(output.decode('utf-8'))[0] 41 | network_name = next(iter(network_info["NetworkSettings"]["Networks"].keys())) 42 | 43 | output, _ = self.run_subprocess_assert_returncode([ 44 | "podman", 45 | "network", 46 | "inspect", 47 | "{}".format(network_name), 48 | ]) 49 | network_info = json.loads(output.decode('utf-8'))[0] 50 | # bridge is the default network driver 51 | self.assertEqual(network_info['driver'], "bridge") 52 | self.assertEqual(network_info['ipam_options'], {'driver': 'host-local'}) 53 | finally: 54 | self.run_subprocess_assert_returncode([ 55 | podman_compose_path(), 56 | "-f", 57 | compose_yaml_path(), 58 | "down", 59 | ]) 60 | -------------------------------------------------------------------------------- /tests/integration/lifetime/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/containers/podman-compose/4c6df85efac6dea83cea68b406d3d1abfb7cacef/tests/integration/lifetime/__init__.py -------------------------------------------------------------------------------- /tests/integration/lifetime/up_single_container/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | container1: 4 | image: nopush/podman-compose-test 5 | command: ["/bin/bash", "-c", "echo test1; sleep infinity"] 6 | container2: 7 | image: nopush/podman-compose-test 8 | command: ["/bin/bash", "-c", "echo test2; sleep infinity"] 9 | -------------------------------------------------------------------------------- /tests/integration/lifetime/up_single_container_many_times/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | container1: 4 | image: nopush/podman-compose-test 5 | command: ["/bin/bash", "-c", "echo test1; sleep infinity"] 6 | container2: 7 | image: nopush/podman-compose-test 8 | restart: always 9 | command: ["/bin/bash", "-c", "echo test2"] 10 | -------------------------------------------------------------------------------- /tests/integration/lifetime/up_single_container_many_times_with_ports/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | container1: 4 | image: nopush/podman-compose-test 5 | ports: "9001:9001" 6 | command: ["/bin/bash", "-c", "echo test1; sleep infinity"] 7 | container2: 8 | image: nopush/podman-compose-test 9 | restart: always 10 | ports: "9002:9002" 11 | command: ["/bin/bash", "-c", "echo test2"] 12 | -------------------------------------------------------------------------------- /tests/integration/merge/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/merge/reset_and_override_tags/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/merge/reset_and_override_tags/override_tag_attribute/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/merge/reset_and_override_tags/override_tag_attribute/docker-compose.override_attribute.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | app: 4 | image: busybox 5 | command: ["/bin/busybox", "echo", "One"] 6 | ports: !override 7 | - "8111:81" 8 | -------------------------------------------------------------------------------- /tests/integration/merge/reset_and_override_tags/override_tag_attribute/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | app: 4 | image: busybox 5 | command: ["/bin/busybox", "echo", "Zero"] 6 | ports: 7 | - "8080:80" 8 | -------------------------------------------------------------------------------- /tests/integration/merge/reset_and_override_tags/override_tag_attribute/test_podman_compose_override_tag_attribute.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | import json 4 | import os 5 | import unittest 6 | 7 | from tests.integration.test_utils import RunSubprocessMixin 8 | from tests.integration.test_utils import podman_compose_path 9 | from tests.integration.test_utils import test_path 10 | 11 | 12 | def compose_yaml_path() -> str: 13 | return os.path.join( 14 | test_path(), 15 | "merge/reset_and_override_tags/override_tag_attribute/docker-compose.yaml", 16 | ) 17 | 18 | 19 | class TestComposeOverrideTagAttribute(unittest.TestCase, RunSubprocessMixin): 20 | # test if a service attribute from docker-compose.yaml file is overridden 21 | def test_override_tag_attribute(self) -> None: 22 | override_file = os.path.join( 23 | test_path(), 24 | "merge/reset_and_override_tags/override_tag_attribute/docker-compose.override_attribute.yaml", 25 | ) 26 | try: 27 | self.run_subprocess_assert_returncode([ 28 | podman_compose_path(), 29 | "-f", 30 | compose_yaml_path(), 31 | "-f", 32 | override_file, 33 | "up", 34 | ]) 35 | # merge rules are still applied 36 | output, _ = self.run_subprocess_assert_returncode([ 37 | podman_compose_path(), 38 | "-f", 39 | compose_yaml_path(), 40 | "-f", 41 | override_file, 42 | "logs", 43 | ]) 44 | self.assertEqual(output, b"One\n") 45 | 46 | # only app service attribute "ports" was overridden 47 | output, _ = self.run_subprocess_assert_returncode([ 48 | "podman", 49 | "inspect", 50 | "override_tag_attribute_app_1", 51 | ]) 52 | container_info = json.loads(output.decode('utf-8'))[0] 53 | self.assertEqual( 54 | container_info['NetworkSettings']["Ports"], 55 | {"81/tcp": [{"HostIp": "", "HostPort": "8111"}]}, 56 | ) 57 | finally: 58 | self.run_subprocess_assert_returncode([ 59 | podman_compose_path(), 60 | "-f", 61 | compose_yaml_path(), 62 | "down", 63 | ]) 64 | -------------------------------------------------------------------------------- /tests/integration/merge/reset_and_override_tags/override_tag_service/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/merge/reset_and_override_tags/override_tag_service/docker-compose.override_service.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | app: !override 4 | image: busybox 5 | command: ["/bin/busybox", "echo", "One"] 6 | ports: 7 | - "8111:81" 8 | -------------------------------------------------------------------------------- /tests/integration/merge/reset_and_override_tags/override_tag_service/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | app: 4 | image: busybox 5 | command: ["/bin/busybox", "echo", "Zero"] 6 | ports: 7 | - "8080:80" 8 | -------------------------------------------------------------------------------- /tests/integration/merge/reset_and_override_tags/override_tag_service/test_podman_compose_override_tag_service.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | import json 4 | import os 5 | import unittest 6 | 7 | from tests.integration.test_utils import RunSubprocessMixin 8 | from tests.integration.test_utils import podman_compose_path 9 | from tests.integration.test_utils import test_path 10 | 11 | 12 | def compose_yaml_path() -> str: 13 | return os.path.join( 14 | test_path(), 15 | "merge/reset_and_override_tags/override_tag_service/docker-compose.yaml", 16 | ) 17 | 18 | 19 | class TestComposeOverrideTagService(unittest.TestCase, RunSubprocessMixin): 20 | # test if whole service from docker-compose.yaml file is overridden in another file 21 | def test_override_tag_service(self) -> None: 22 | override_file = os.path.join( 23 | test_path(), 24 | "merge/reset_and_override_tags/override_tag_service/docker-compose.override_service.yaml", 25 | ) 26 | try: 27 | self.run_subprocess_assert_returncode([ 28 | podman_compose_path(), 29 | "-f", 30 | compose_yaml_path(), 31 | "-f", 32 | override_file, 33 | "up", 34 | ]) 35 | 36 | # Whole app service was overridden in the docker-compose.override_tag_service.yaml file. 37 | # Command and port is overridden accordingly. 38 | output, _ = self.run_subprocess_assert_returncode([ 39 | podman_compose_path(), 40 | "-f", 41 | compose_yaml_path(), 42 | "-f", 43 | override_file, 44 | "logs", 45 | ]) 46 | self.assertEqual(output, b"One\n") 47 | 48 | output, _ = self.run_subprocess_assert_returncode([ 49 | "podman", 50 | "inspect", 51 | "override_tag_service_app_1", 52 | ]) 53 | container_info = json.loads(output.decode('utf-8'))[0] 54 | self.assertEqual( 55 | container_info['NetworkSettings']["Ports"], 56 | {"81/tcp": [{"HostIp": "", "HostPort": "8111"}]}, 57 | ) 58 | finally: 59 | self.run_subprocess_assert_returncode([ 60 | podman_compose_path(), 61 | "-f", 62 | compose_yaml_path(), 63 | "down", 64 | ]) 65 | -------------------------------------------------------------------------------- /tests/integration/merge/reset_and_override_tags/reset_tag_attribute/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/merge/reset_and_override_tags/reset_tag_attribute/docker-compose.reset_attribute.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | app: 4 | image: busybox 5 | command: !reset {} 6 | depends_on: !reset null 7 | -------------------------------------------------------------------------------- /tests/integration/merge/reset_and_override_tags/reset_tag_attribute/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | app: 4 | image: busybox 5 | command: ["/bin/busybox", "echo", "Zero"] 6 | depends_on: 7 | - db 8 | -------------------------------------------------------------------------------- /tests/integration/merge/reset_and_override_tags/reset_tag_attribute/test_podman_compose_reset_tag_attribute.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | import os 4 | import unittest 5 | 6 | from tests.integration.test_utils import RunSubprocessMixin 7 | from tests.integration.test_utils import podman_compose_path 8 | from tests.integration.test_utils import test_path 9 | 10 | 11 | def compose_yaml_path() -> str: 12 | return os.path.join( 13 | test_path(), 14 | "merge/reset_and_override_tags/reset_tag_attribute/docker-compose.yaml", 15 | ) 16 | 17 | 18 | class TestComposeResetTagAttribute(unittest.TestCase, RunSubprocessMixin): 19 | # test if the attribute of the service is correctly reset 20 | def test_reset_tag_attribute(self) -> None: 21 | reset_file = os.path.join( 22 | test_path(), 23 | "merge/reset_and_override_tags/reset_tag_attribute/docker-compose.reset_attribute.yaml", 24 | ) 25 | try: 26 | self.run_subprocess_assert_returncode([ 27 | podman_compose_path(), 28 | "-f", 29 | compose_yaml_path(), 30 | "-f", 31 | reset_file, 32 | "up", 33 | ]) 34 | 35 | # the service still exists, but its command attribute was reset in 36 | # docker-compose.reset_tag_attribute.yaml file and is now empty 37 | output, _ = self.run_subprocess_assert_returncode([ 38 | podman_compose_path(), 39 | "-f", 40 | compose_yaml_path(), 41 | "-f", 42 | reset_file, 43 | "ps", 44 | ]) 45 | self.assertIn(b"reset_tag_attribute_app_1", output) 46 | 47 | output, _ = self.run_subprocess_assert_returncode([ 48 | podman_compose_path(), 49 | "-f", 50 | compose_yaml_path(), 51 | "-f", 52 | reset_file, 53 | "logs", 54 | ]) 55 | self.assertEqual(output, b"") 56 | # depends_on: !reset null testing: if this test works, depends_on is correctly reset. 57 | # Otherwise the test would break, since "db" dependency service is not provided 58 | finally: 59 | self.run_subprocess_assert_returncode([ 60 | podman_compose_path(), 61 | "-f", 62 | compose_yaml_path(), 63 | "down", 64 | ]) 65 | -------------------------------------------------------------------------------- /tests/integration/merge/reset_and_override_tags/reset_tag_service/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/merge/reset_and_override_tags/reset_tag_service/docker-compose.reset_service.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | app: !reset 4 | app2: 5 | image: busybox 6 | command: ["/bin/busybox", "echo", "One"] 7 | -------------------------------------------------------------------------------- /tests/integration/merge/reset_and_override_tags/reset_tag_service/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | app: 4 | image: busybox 5 | command: ["/bin/busybox", "echo", "Zero"] 6 | -------------------------------------------------------------------------------- /tests/integration/merge/reset_and_override_tags/reset_tag_service/test_podman_compose_reset_tag_service.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | import os 4 | import unittest 5 | 6 | from tests.integration.test_utils import RunSubprocessMixin 7 | from tests.integration.test_utils import podman_compose_path 8 | from tests.integration.test_utils import test_path 9 | 10 | 11 | def compose_yaml_path() -> str: 12 | return os.path.join( 13 | test_path(), "merge/reset_and_override_tags/reset_tag_service/docker-compose.yaml" 14 | ) 15 | 16 | 17 | class TestComposeResetTagService(unittest.TestCase, RunSubprocessMixin): 18 | # test if whole service from docker-compose.yaml file is reset 19 | def test_reset_tag_service(self) -> None: 20 | reset_file = os.path.join( 21 | test_path(), 22 | "merge/reset_and_override_tags/reset_tag_service/docker-compose.reset_service.yaml", 23 | ) 24 | try: 25 | self.run_subprocess_assert_returncode([ 26 | podman_compose_path(), 27 | "-f", 28 | compose_yaml_path(), 29 | "-f", 30 | reset_file, 31 | "up", 32 | ]) 33 | 34 | # app service was fully reset in docker-compose.reset_tag_service.yaml file, therefore 35 | # does not exist. A new service was created instead. 36 | output, _ = self.run_subprocess_assert_returncode([ 37 | podman_compose_path(), 38 | "-f", 39 | compose_yaml_path(), 40 | "-f", 41 | reset_file, 42 | "ps", 43 | ]) 44 | self.assertNotIn(b"reset_tag_service_app_1", output) 45 | self.assertIn(b"reset_tag_service_app2_1", output) 46 | 47 | output, _ = self.run_subprocess_assert_returncode([ 48 | podman_compose_path(), 49 | "-f", 50 | compose_yaml_path(), 51 | "-f", 52 | reset_file, 53 | "logs", 54 | ]) 55 | self.assertEqual(output, b"One\n") 56 | finally: 57 | self.run_subprocess_assert_returncode([ 58 | podman_compose_path(), 59 | "-f", 60 | compose_yaml_path(), 61 | "down", 62 | ]) 63 | -------------------------------------------------------------------------------- /tests/integration/merge/volumes_merge/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/merge/volumes_merge/docker-compose.override.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | web: 4 | volumes: 5 | - ./override.txt:/var/www/html/index.html:ro,z 6 | - ./override.txt:/var/www/html/index2.html:z 7 | - ./override.txt:/var/www/html/index3.html 8 | -------------------------------------------------------------------------------- /tests/integration/merge/volumes_merge/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | web: 4 | image: busybox 5 | command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8080"] 6 | ports: 7 | - 8080:8080 8 | volumes: 9 | - ./index.txt:/var/www/html/index.html:ro,z 10 | - ./index.txt:/var/www/html/index2.html 11 | - ./index.txt:/var/www/html/index3.html:ro 12 | -------------------------------------------------------------------------------- /tests/integration/merge/volumes_merge/index.txt: -------------------------------------------------------------------------------- 1 | The file from docker-compose.yaml 2 | -------------------------------------------------------------------------------- /tests/integration/merge/volumes_merge/override.txt: -------------------------------------------------------------------------------- 1 | The file from docker-compose.override.yaml 2 | -------------------------------------------------------------------------------- /tests/integration/multicompose/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/multicompose/d1/1.env: -------------------------------------------------------------------------------- 1 | var1=d1/1.env 2 | -------------------------------------------------------------------------------- /tests/integration/multicompose/d1/12.env: -------------------------------------------------------------------------------- 1 | var12=d1/12.env 2 | -------------------------------------------------------------------------------- /tests/integration/multicompose/d1/2.env: -------------------------------------------------------------------------------- 1 | var2=d1/2.env 2 | -------------------------------------------------------------------------------- /tests/integration/multicompose/d1/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | web1: 4 | image: busybox 5 | command: busybox httpd -h /var/www/html/ -f -p 8001 6 | volumes: 7 | - ./1.env:/var/www/html/index.txt:z 8 | env_file: ./1.env 9 | labels: 10 | l1: v1 11 | environment: 12 | - mykey1=myval1 13 | 14 | -------------------------------------------------------------------------------- /tests/integration/multicompose/d2/12.env: -------------------------------------------------------------------------------- 1 | var12=d2/12.env 2 | -------------------------------------------------------------------------------- /tests/integration/multicompose/d2/2.env: -------------------------------------------------------------------------------- 1 | var2=d2/2.env 2 | -------------------------------------------------------------------------------- /tests/integration/multicompose/d2/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | web1: 4 | image: busybox 5 | env_file: ./12.env 6 | labels: 7 | - l1=v2 8 | - l2=v2 9 | environment: 10 | mykey1: myval2 11 | mykey2: myval2 12 | 13 | web2: 14 | image: busybox 15 | command: busybox httpd -h /var/www/html/ -f -p 8002 16 | volumes: 17 | - ./2.env:/var/www/html/index.txt:z 18 | env_file: ./2.env 19 | 20 | -------------------------------------------------------------------------------- /tests/integration/nethost/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/nethost/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | web: 4 | image: busybox 5 | command: httpd -f -p 8123 -h /tmp/ 6 | network_mode: host 7 | -------------------------------------------------------------------------------- /tests/integration/nethost/test_podman_compose_nethost.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | import os 4 | import unittest 5 | 6 | import requests 7 | 8 | from tests.integration.test_utils import RunSubprocessMixin 9 | from tests.integration.test_utils import podman_compose_path 10 | from tests.integration.test_utils import test_path 11 | 12 | 13 | def compose_yaml_path() -> str: 14 | return os.path.join(os.path.join(test_path(), "nethost"), "docker-compose.yaml") 15 | 16 | 17 | class TestComposeNethost(unittest.TestCase, RunSubprocessMixin): 18 | # check if container listens for http requests and sends response back 19 | # as network_mode: host allows to connect to container easily 20 | def test_nethost(self) -> None: 21 | try: 22 | self.run_subprocess_assert_returncode( 23 | [podman_compose_path(), "-f", compose_yaml_path(), "up", "-d"], 24 | ) 25 | 26 | container_id_out, _ = self.run_subprocess_assert_returncode( 27 | [ 28 | podman_compose_path(), 29 | "-f", 30 | compose_yaml_path(), 31 | "ps", 32 | "--format", 33 | '{{.ID}}', 34 | ], 35 | ) 36 | container_id = container_id_out.decode('utf-8').split('\n')[0] 37 | output, _ = self.run_subprocess_assert_returncode( 38 | [ 39 | "podman", 40 | "exec", 41 | "-it", 42 | container_id, 43 | "sh", 44 | "-c", 45 | "echo test_123 >> /tmp/test.txt", 46 | ], 47 | ) 48 | response = requests.get('http://localhost:8123/test.txt') 49 | self.assertEqual(response.ok, True) 50 | self.assertEqual(response.text, "test_123\n") 51 | finally: 52 | self.run_subprocess_assert_returncode([ 53 | podman_compose_path(), 54 | "-f", 55 | compose_yaml_path(), 56 | "down", 57 | "-t", 58 | "0", 59 | ]) 60 | -------------------------------------------------------------------------------- /tests/integration/nets_test1/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/nets_test1/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | web1: 4 | image: busybox 5 | hostname: web1 6 | command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"] 7 | working_dir: /var/www/html 8 | ports: 9 | - 8001:8001 10 | volumes: 11 | - ./test1.txt:/var/www/html/index.txt:ro,z 12 | web2: 13 | image: busybox 14 | hostname: web2 15 | command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"] 16 | working_dir: /var/www/html 17 | ports: 18 | - 8002:8001 19 | volumes: 20 | - ./test2.txt:/var/www/html/index.txt:ro,z 21 | 22 | -------------------------------------------------------------------------------- /tests/integration/nets_test1/test1.txt: -------------------------------------------------------------------------------- 1 | test1 2 | -------------------------------------------------------------------------------- /tests/integration/nets_test1/test2.txt: -------------------------------------------------------------------------------- 1 | test2 2 | -------------------------------------------------------------------------------- /tests/integration/nets_test2/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/nets_test2/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | networks: 3 | mystack: 4 | services: 5 | web1: 6 | image: busybox 7 | hostname: web1 8 | command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"] 9 | working_dir: /var/www/html 10 | ports: 11 | - 8001:8001 12 | volumes: 13 | - ./test1.txt:/var/www/html/index.txt:ro,z 14 | web2: 15 | image: busybox 16 | hostname: web2 17 | command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"] 18 | working_dir: /var/www/html 19 | ports: 20 | - 8002:8001 21 | volumes: 22 | - ./test2.txt:/var/www/html/index.txt:ro,z 23 | 24 | -------------------------------------------------------------------------------- /tests/integration/nets_test2/test1.txt: -------------------------------------------------------------------------------- 1 | test1 2 | -------------------------------------------------------------------------------- /tests/integration/nets_test2/test2.txt: -------------------------------------------------------------------------------- 1 | test2 2 | -------------------------------------------------------------------------------- /tests/integration/nets_test3/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/containers/podman-compose/4c6df85efac6dea83cea68b406d3d1abfb7cacef/tests/integration/nets_test3/__init__.py -------------------------------------------------------------------------------- /tests/integration/nets_test3/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | networks: 3 | net1: 4 | net2: 5 | services: 6 | web1: 7 | image: busybox 8 | #container_name: web1 9 | hostname: web1 10 | command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"] 11 | working_dir: /var/www/html 12 | networks: 13 | - net1 14 | ports: 15 | - 8001:8001 16 | volumes: 17 | - ./test1.txt:/var/www/html/index.txt:ro,z 18 | web2: 19 | image: busybox 20 | #container_name: web2 21 | hostname: web2 22 | command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"] 23 | working_dir: /var/www/html 24 | networks: 25 | - net1 26 | - net2 27 | ports: 28 | - 8002:8001 29 | volumes: 30 | - ./test2.txt:/var/www/html/index.txt:ro,z 31 | web3: 32 | image: busybox 33 | command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"] 34 | working_dir: /var/www/html 35 | networks: 36 | net1: 37 | aliases: 38 | - alias11 39 | - alias12 40 | net2: 41 | aliases: 42 | - alias21 43 | volumes: 44 | - ./test3.txt:/var/www/html/index.txt:ro,z 45 | 46 | -------------------------------------------------------------------------------- /tests/integration/nets_test3/test1.txt: -------------------------------------------------------------------------------- 1 | test1 2 | -------------------------------------------------------------------------------- /tests/integration/nets_test3/test2.txt: -------------------------------------------------------------------------------- 1 | test2 2 | -------------------------------------------------------------------------------- /tests/integration/nets_test3/test3.txt: -------------------------------------------------------------------------------- 1 | test3 2 | -------------------------------------------------------------------------------- /tests/integration/nets_test_ip/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/containers/podman-compose/4c6df85efac6dea83cea68b406d3d1abfb7cacef/tests/integration/nets_test_ip/__init__.py -------------------------------------------------------------------------------- /tests/integration/nets_test_ip/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | networks: 3 | shared-network: 4 | driver: bridge 5 | ipam: 6 | config: 7 | - subnet: "172.19.1.0/24" 8 | internal-network: 9 | driver: bridge 10 | ipam: 11 | config: 12 | - subnet: "172.19.2.0/24" 13 | 14 | services: 15 | web1: 16 | image: busybox 17 | hostname: web1 18 | command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"] 19 | working_dir: /var/www/html 20 | networks: 21 | shared-network: 22 | ipv4_address: "172.19.1.10" 23 | x-podman.mac_address: "02:01:01:00:01:01" 24 | internal-network: 25 | ipv4_address: "172.19.2.10" 26 | mac_address: "02:01:01:00:02:01" 27 | volumes: 28 | - ./test1.txt:/var/www/html/index.txt:ro,z 29 | web2: 30 | image: busybox 31 | hostname: web2 32 | command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"] 33 | working_dir: /var/www/html 34 | mac_address: "02:01:01:00:02:02" 35 | networks: 36 | internal-network: 37 | ipv4_address: "172.19.2.11" 38 | volumes: 39 | - ./test2.txt:/var/www/html/index.txt:ro,z 40 | 41 | web3: 42 | image: busybox 43 | hostname: web2 44 | command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"] 45 | working_dir: /var/www/html 46 | networks: 47 | internal-network: 48 | volumes: 49 | - ./test3.txt:/var/www/html/index.txt:ro,z 50 | 51 | web4: 52 | image: busybox 53 | hostname: web2 54 | command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"] 55 | working_dir: /var/www/html 56 | networks: 57 | internal-network: 58 | shared-network: 59 | ipv4_address: "172.19.1.13" 60 | volumes: 61 | - ./test4.txt:/var/www/html/index.txt:ro,z 62 | -------------------------------------------------------------------------------- /tests/integration/nets_test_ip/test1.txt: -------------------------------------------------------------------------------- 1 | test1 2 | -------------------------------------------------------------------------------- /tests/integration/nets_test_ip/test2.txt: -------------------------------------------------------------------------------- 1 | test2 2 | -------------------------------------------------------------------------------- /tests/integration/nets_test_ip/test3.txt: -------------------------------------------------------------------------------- 1 | test3 2 | -------------------------------------------------------------------------------- /tests/integration/nets_test_ip/test4.txt: -------------------------------------------------------------------------------- 1 | test4 2 | -------------------------------------------------------------------------------- /tests/integration/network/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/network/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | networks: 3 | mystack: 4 | services: 5 | web1: 6 | image: busybox 7 | hostname: web1 8 | command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"] 9 | working_dir: /var/www/html 10 | ports: 11 | - 8001:8001 12 | volumes: 13 | - ./test1.txt:/var/www/html/index.txt:ro,z 14 | web2: 15 | image: busybox 16 | hostname: web2 17 | command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"] 18 | working_dir: /var/www/html 19 | ports: 20 | - 8002:8001 21 | volumes: 22 | - ./test2.txt:/var/www/html/index.txt:ro,z 23 | 24 | -------------------------------------------------------------------------------- /tests/integration/network/test1.txt: -------------------------------------------------------------------------------- 1 | test1 2 | -------------------------------------------------------------------------------- /tests/integration/network/test2.txt: -------------------------------------------------------------------------------- 1 | test2 2 | -------------------------------------------------------------------------------- /tests/integration/network_interface_name/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/network_interface_name/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | networks: 3 | mystack: 4 | services: 5 | web: 6 | image: busybox 7 | command: ["/bin/busybox", "httpd", "-f", "-h", ".", "-p", "8004"] 8 | networks: 9 | mystack: 10 | x-podman.interface_name: customName0 11 | -------------------------------------------------------------------------------- /tests/integration/network_interface_name/test_podman_compose_network_interface_name.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | # pylint: disable=redefined-outer-name 4 | import os 5 | import unittest 6 | 7 | from tests.integration.test_utils import RunSubprocessMixin 8 | from tests.integration.test_utils import podman_compose_path 9 | from tests.integration.test_utils import test_path 10 | 11 | 12 | class TestPodmanComposeNetworkInterfaceName(RunSubprocessMixin, unittest.TestCase): 13 | def compose_file(self) -> str: 14 | return os.path.join(test_path(), "network_interface_name", "docker-compose.yml") 15 | 16 | def up(self) -> None: 17 | up_cmd = [ 18 | "coverage", 19 | "run", 20 | podman_compose_path(), 21 | "-f", 22 | self.compose_file(), 23 | "up", 24 | "-d", 25 | "--force-recreate", 26 | ] 27 | self.run_subprocess_assert_returncode(up_cmd) 28 | 29 | def down(self) -> None: 30 | down_cmd = [ 31 | "coverage", 32 | "run", 33 | podman_compose_path(), 34 | "-f", 35 | self.compose_file(), 36 | "kill", 37 | "-a", 38 | ] 39 | self.run_subprocess(down_cmd) 40 | 41 | def test_interface_name(self) -> None: 42 | try: 43 | self.up() 44 | 45 | interfaces_cmd = [ 46 | podman_compose_path(), 47 | "-f", 48 | self.compose_file(), 49 | "exec", 50 | "web", 51 | "ls", 52 | "/sys/class/net", 53 | "--color=never", 54 | ] 55 | out, _ = self.run_subprocess_assert_returncode(interfaces_cmd) 56 | self.assertEqual("customName0 lo\r\n", out.decode()) 57 | finally: 58 | self.down() 59 | -------------------------------------------------------------------------------- /tests/integration/network_scoped_aliases/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/network_scoped_aliases/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | networks: 3 | net0: 4 | ipam: 5 | config: 6 | - subnet: "172.19.3.0/24" 7 | net1: 8 | ipam: 9 | config: 10 | - subnet: "172.19.4.0/24" 11 | services: 12 | web1: 13 | image: busybox 14 | command: ["/bin/busybox", "httpd", "-f", "-h", "/tmp", "-p", "8001"] 15 | networks: 16 | net0: 17 | ipv4_address: "172.19.3.11" 18 | aliases: 19 | - secure-web 20 | net1: 21 | ipv4_address: "172.19.4.11" 22 | aliases: 23 | - insecure-web 24 | utils-net0: 25 | image: busybox 26 | command: ["/bin/busybox", "httpd", "-f", "-h", "/tmp", "-p", "8001"] 27 | networks: 28 | - net0 29 | utils-net1: 30 | image: busybox 31 | command: ["/bin/busybox", "httpd", "-f", "-h", "/tmp", "-p", "8001"] 32 | networks: 33 | - net1 34 | -------------------------------------------------------------------------------- /tests/integration/no_services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/containers/podman-compose/4c6df85efac6dea83cea68b406d3d1abfb7cacef/tests/integration/no_services/__init__.py -------------------------------------------------------------------------------- /tests/integration/no_services/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | networks: 3 | shared-network: 4 | driver: bridge 5 | ipam: 6 | config: 7 | - subnet: 172.19.0.0/24 8 | -------------------------------------------------------------------------------- /tests/integration/no_services/test_podman_compose_no_services.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | import os 4 | import unittest 5 | 6 | from tests.integration.test_utils import RunSubprocessMixin 7 | from tests.integration.test_utils import podman_compose_path 8 | from tests.integration.test_utils import test_path 9 | 10 | 11 | def compose_yaml_path() -> str: 12 | return os.path.join(os.path.join(test_path(), "no_services"), "docker-compose.yaml") 13 | 14 | 15 | class TestComposeNoServices(unittest.TestCase, RunSubprocessMixin): 16 | # test if a network was created, but not the services 17 | def test_no_services(self) -> None: 18 | try: 19 | output, return_code = self.run_subprocess_assert_returncode( 20 | [ 21 | podman_compose_path(), 22 | "-f", 23 | compose_yaml_path(), 24 | "up", 25 | "-d", 26 | ], 27 | ) 28 | self.assertEqual( 29 | b'WARNING:__main__:WARNING: unused networks: shared-network\n', return_code 30 | ) 31 | 32 | container_id, _ = self.run_subprocess_assert_returncode([ 33 | podman_compose_path(), 34 | "-f", 35 | compose_yaml_path(), 36 | "ps", 37 | "--format", 38 | '{{.ID}}', 39 | ]) 40 | self.assertEqual(container_id, b"") 41 | finally: 42 | self.run_subprocess_assert_returncode([ 43 | podman_compose_path(), 44 | "-f", 45 | compose_yaml_path(), 46 | "down", 47 | "-t", 48 | "0", 49 | ]) 50 | -------------------------------------------------------------------------------- /tests/integration/pid/README.txt: -------------------------------------------------------------------------------- 1 | This is the output of command "up -d" with corresponding "pid/docker-compose.yml" file: 2 | WARN[0000] freezer not supported: openat2 /sys/fs/cgroup/machine.slice/libpod-SHA.scope/cgroup.freeze: no such file or directory 3 | WARN[0000] lstat /sys/fs/cgroup/machine.slice/libpod-SHA.scope: no such file or directory 4 | pid_serv_1 5 | 6 | Command output corresponds to a closed (but not fixed) issue in "containers/podman": 7 | https://github.com/containers/podman/issues/11784 8 | 9 | The command was tested on: 10 | podman-compose version 1.3.0 11 | podman version 4.3.1 12 | 13 | Operating System: Debian GNU/Linux 12 (bookworm) 14 | -------------------------------------------------------------------------------- /tests/integration/pid/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | serv: 4 | image: busybox 5 | pid: host 6 | command: sh -c "ps all" 7 | -------------------------------------------------------------------------------- /tests/integration/pod_args/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/pod_args/custom_pod_args_empty/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | cont: 4 | image: nopush/podman-compose-test 5 | command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-p", "8080"] 6 | x-podman: 7 | pod_args: [] 8 | -------------------------------------------------------------------------------- /tests/integration/pod_args/custom_pod_args_set/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | cont: 4 | image: nopush/podman-compose-test 5 | command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-p", "8080"] 6 | x-podman: 7 | pod_args: ["--infra=false", "--share=", "--cpus=2"] 8 | -------------------------------------------------------------------------------- /tests/integration/pod_args/custom_pod_args_unset/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | cont: 4 | image: nopush/podman-compose-test 5 | command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-p", "8080"] 6 | -------------------------------------------------------------------------------- /tests/integration/ports/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/ports/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | web1: 4 | image: nopush/podman-compose-test 5 | hostname: web1 6 | command: ["dumb-init", "sleep", "infinity"] 7 | ports: 8 | - 8000:8000 9 | - 8001 10 | web2: 11 | image: nopush/podman-compose-test 12 | hostname: web2 13 | command: ["dumb-init", "sleep", "infinity"] 14 | ports: 15 | - 8002:8002 16 | - target: 8003 17 | host_ip: 127.0.0.1 18 | published: 8003 19 | protocol: udp 20 | - target: 8004 21 | host_ip: 127.0.0.1 22 | published: 8004 23 | protocol: tcp 24 | - target: 8005 25 | published: 8005 26 | - target: 8006 27 | protocol: udp 28 | - target: 8007 29 | host_ip: 127.0.0.1 30 | 31 | -------------------------------------------------------------------------------- /tests/integration/profile/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/profile/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | default-service: 4 | image: nopush/podman-compose-test 5 | command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", "/etc/", "-p", "8000"] 6 | tmpfs: 7 | - /run 8 | - /tmp 9 | service-1: 10 | image: nopush/podman-compose-test 11 | command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", "/etc/", "-p", "8000"] 12 | tmpfs: 13 | - /run 14 | - /tmp 15 | profiles: 16 | - profile-1 17 | service-2: 18 | image: nopush/podman-compose-test 19 | command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", "/etc/", "-p", "8000"] 20 | tmpfs: 21 | - /run 22 | - /tmp 23 | profiles: 24 | - profile-2 25 | -------------------------------------------------------------------------------- /tests/integration/seccomp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/containers/podman-compose/4c6df85efac6dea83cea68b406d3d1abfb7cacef/tests/integration/seccomp/__init__.py -------------------------------------------------------------------------------- /tests/integration/seccomp/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultAction": "SCMP_ACT_ALLOW", 3 | "syscalls": [ 4 | { 5 | "name": "mkdir", 6 | "action": "SCMP_ACT_ERRNO", 7 | "args": [] 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /tests/integration/seccomp/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | web1: 4 | image: busybox 5 | command: sh -c "mkdir /tmp_test" 6 | security_opt: 7 | # Currently only absolute path works, like this: 8 | # - seccomp:/.../tests/integration/seccomp/default.json 9 | - seccomp:./default.json 10 | -------------------------------------------------------------------------------- /tests/integration/seccomp/test_podman_compose_seccomp.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | import os 4 | import unittest 5 | 6 | from tests.integration.test_utils import RunSubprocessMixin 7 | from tests.integration.test_utils import podman_compose_path 8 | from tests.integration.test_utils import test_path 9 | 10 | 11 | def compose_yaml_path() -> str: 12 | return os.path.join(os.path.join(test_path(), "seccomp"), "docker-compose.yml") 13 | 14 | 15 | class TestComposeSeccomp(unittest.TestCase, RunSubprocessMixin): 16 | @unittest.skip( 17 | "Skip till security_opt seccomp from 'docker-compose.yml' will be able to accept a " 18 | "relative path of 'default.json' file. Now test works as expected but only with the " 19 | "absolute path." 20 | ) 21 | # test if seccomp uses custom seccomp profile file 'default.json' where command mkdir is not 22 | # allowed 23 | def test_seccomp(self) -> None: 24 | try: 25 | output, _, return_code = self.run_subprocess( 26 | [podman_compose_path(), "-f", compose_yaml_path(), "run", "--rm", "web1"], 27 | ) 28 | self.assertEqual(return_code, 1) 29 | self.assertIn( 30 | b"mkdir: can't create directory '/tmp_test': Operation not permitted", output 31 | ) 32 | finally: 33 | self.run_subprocess_assert_returncode([ 34 | podman_compose_path(), 35 | "-f", 36 | compose_yaml_path(), 37 | "down", 38 | "-t", 39 | "0", 40 | ]) 41 | -------------------------------------------------------------------------------- /tests/integration/secrets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/containers/podman-compose/4c6df85efac6dea83cea68b406d3d1abfb7cacef/tests/integration/secrets/__init__.py -------------------------------------------------------------------------------- /tests/integration/secrets/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | test: 4 | image: busybox 5 | command: 6 | - /tmp/print_secrets.sh 7 | tmpfs: 8 | - /run 9 | - /tmp 10 | volumes: 11 | - ./print_secrets.sh:/tmp/print_secrets.sh:z 12 | secrets: 13 | - podman_compose_test_secret 14 | # Custom name reference for mounted external secret is not supported 15 | #- podman_compose_test_secret_2 16 | - source: podman_compose_test_secret_3 17 | # warning about un-supported "target" field 18 | target: podman_compose_test_secret_3 19 | uid: '103' 20 | gid: '103' 21 | mode: 400 22 | - file_secret 23 | - source: file_secret 24 | target: custom_name 25 | - source: file_secret 26 | target: /etc/custom_location 27 | - source: file_secret 28 | # warning about un-supported "uid", "gid", "mode" fields 29 | target: unused_params_warning 30 | uid: '103' 31 | gid: '103' 32 | mode: 400 33 | - source: podman_compose_test_secret 34 | target: ENV_SECRET 35 | type: env 36 | 37 | secrets: 38 | podman_compose_test_secret: 39 | external: true 40 | # Custom name reference for mounted external secret is not supported 41 | #podman_compose_test_secret_2: 42 | #external: true 43 | #name: podman_compose_test_secret_custom_name 44 | podman_compose_test_secret_3: 45 | external: true 46 | name: podman_compose_test_secret_3 47 | file_secret: 48 | file: ./my_secret 49 | -------------------------------------------------------------------------------- /tests/integration/secrets/my_secret: -------------------------------------------------------------------------------- 1 | important-secret-is-important 2 | -------------------------------------------------------------------------------- /tests/integration/secrets/print_secrets.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | grep . /run/secrets/* 4 | grep . /etc/custom_location 5 | echo "$ENV_SECRET" 6 | -------------------------------------------------------------------------------- /tests/integration/selinux/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/containers/podman-compose/4c6df85efac6dea83cea68b406d3d1abfb7cacef/tests/integration/selinux/__init__.py -------------------------------------------------------------------------------- /tests/integration/selinux/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | container1: 4 | image: busybox 5 | command: ["busybox", "sleep", "infinity"] 6 | volumes: 7 | - type: bind 8 | source: ./host_test_text.txt 9 | target: /test_text.txt 10 | bind: 11 | selinux: z 12 | container2: 13 | image: busybox 14 | command: ["busybox", "sleep", "infinity"] 15 | volumes: 16 | - type: bind 17 | source: ./host_test_text.txt 18 | target: /test_text.txt 19 | -------------------------------------------------------------------------------- /tests/integration/selinux/host_test_text.txt: -------------------------------------------------------------------------------- 1 | # There must be a source file in the host for volumes type: bind 2 | -------------------------------------------------------------------------------- /tests/integration/selinux/test_podman_compose_selinux.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | import json 4 | import os 5 | import subprocess 6 | import unittest 7 | 8 | from tests.integration.test_utils import RunSubprocessMixin 9 | from tests.integration.test_utils import podman_compose_path 10 | from tests.integration.test_utils import test_path 11 | 12 | 13 | class TestPodmanCompose(unittest.TestCase, RunSubprocessMixin): 14 | def test_selinux(self) -> None: 15 | # test if when using volumes type:bind with selinux:z option, container ackquires a 16 | # respective host:source:z mapping in CreateCommand list 17 | compose_path = os.path.join(test_path(), "selinux", "docker-compose.yml") 18 | try: 19 | # change working directory to where docker-compose.yml file is so that containers can 20 | # directly access host source file for mounting from that working directory 21 | subprocess.run( 22 | [ 23 | podman_compose_path(), 24 | "-f", 25 | compose_path, 26 | "up", 27 | "-d", 28 | "container1", 29 | "container2", 30 | ], 31 | cwd=os.path.join(test_path(), 'selinux'), 32 | ) 33 | out, _ = self.run_subprocess_assert_returncode([ 34 | "podman", 35 | "inspect", 36 | "selinux_container1_1", 37 | ]) 38 | inspect_out = json.loads(out) 39 | create_command_list = inspect_out[0].get("Config", []).get("CreateCommand", {}) 40 | self.assertIn('./host_test_text.txt:/test_text.txt:z', create_command_list) 41 | 42 | out, _ = self.run_subprocess_assert_returncode([ 43 | "podman", 44 | "inspect", 45 | "selinux_container2_1", 46 | ]) 47 | inspect_out = json.loads(out) 48 | create_command_list = inspect_out[0].get("Config", []).get("CreateCommand", {}) 49 | self.assertIn('./host_test_text.txt:/test_text.txt', create_command_list) 50 | finally: 51 | out, _ = self.run_subprocess_assert_returncode([ 52 | podman_compose_path(), 53 | "-f", 54 | compose_path, 55 | "down", 56 | "-t", 57 | "0", 58 | ]) 59 | -------------------------------------------------------------------------------- /tests/integration/service_scale/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/containers/podman-compose/4c6df85efac6dea83cea68b406d3d1abfb7cacef/tests/integration/service_scale/__init__.py -------------------------------------------------------------------------------- /tests/integration/service_scale/scaleup_cli/docker-compose.yml: -------------------------------------------------------------------------------- 1 | name: podman-compose 2 | 3 | services: 4 | service1: 5 | image: docker.io/library/busybox:latest 6 | tty: true 7 | -------------------------------------------------------------------------------- /tests/integration/service_scale/scaleup_deploy_replicas_parameter/docker-compose.yml: -------------------------------------------------------------------------------- 1 | name: podman-compose 2 | 3 | services: 4 | service1: 5 | image: docker.io/library/busybox:latest 6 | tty: true 7 | deploy: 8 | mode: replicated 9 | replicas: 3 10 | -------------------------------------------------------------------------------- /tests/integration/service_scale/scaleup_scale_parameter/docker-compose.yml: -------------------------------------------------------------------------------- 1 | name: podman-compose 2 | 3 | services: 4 | service1: 5 | image: docker.io/library/busybox:latest 6 | tty: true 7 | scale: 2 8 | -------------------------------------------------------------------------------- /tests/integration/short/data/redis/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/containers/podman-compose/4c6df85efac6dea83cea68b406d3d1abfb7cacef/tests/integration/short/data/redis/.keep -------------------------------------------------------------------------------- /tests/integration/short/data/web/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/containers/podman-compose/4c6df85efac6dea83cea68b406d3d1abfb7cacef/tests/integration/short/data/web/.keep -------------------------------------------------------------------------------- /tests/integration/short/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | redis: 4 | image: redis:alpine 5 | command: ["redis-server", "--appendonly yes", "--notify-keyspace-events", "Ex"] 6 | volumes: 7 | - ./data/redis:/data:z 8 | tmpfs: /run1 9 | ports: 10 | - "6379" 11 | environment: 12 | - SECRET_KEY=aabbcc 13 | - ENV_IS_SET 14 | web: 15 | image: busybox 16 | command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8000"] 17 | working_dir: /var/www/html 18 | volumes: 19 | - /var/www/html 20 | tmpfs: 21 | - /run 22 | - /tmp 23 | web1: 24 | image: busybox 25 | command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"] 26 | working_dir: /var/www/html 27 | volumes: 28 | - ./data/web:/var/www/html:ro,z 29 | web2: 30 | image: busybox 31 | command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8002"] 32 | working_dir: /var/www/html 33 | volumes: 34 | - ~/Downloads/www:/var/www/html:ro,z 35 | web3: 36 | image: busybox 37 | command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8003"] 38 | working_dir: /var/www/html 39 | volumes: 40 | - /var/www/html:/var/www/html:ro,z 41 | -------------------------------------------------------------------------------- /tests/integration/testlogs/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | loop1: 4 | image: busybox 5 | command: ["/bin/sh", "-c", "for i in `seq 1 10000`; do echo \"loop1: $$i\"; sleep 1; done"] 6 | loop2: 7 | image: busybox 8 | command: ["/bin/sh", "-c", "for i in `seq 1 10000`; do echo \"loop2: $$i\"; sleep 3; done"] 9 | 10 | -------------------------------------------------------------------------------- /tests/integration/uidmaps/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/containers/podman-compose/4c6df85efac6dea83cea68b406d3d1abfb7cacef/tests/integration/uidmaps/__init__.py -------------------------------------------------------------------------------- /tests/integration/uidmaps/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | services: 3 | touch: 4 | image: busybox 5 | command: 'touch /mnt/test' 6 | volumes: 7 | - ./:/mnt 8 | user: 999:999 9 | x-podman.uidmaps: 10 | - "0:1:1" 11 | - "999:0:1" 12 | x-podman.gidmaps: 13 | - "0:1:1" 14 | - "999:0:1" 15 | -------------------------------------------------------------------------------- /tests/integration/uidmaps/test_podman_compose_uidmaps.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | import json 4 | import os 5 | import unittest 6 | 7 | from tests.integration.test_utils import RunSubprocessMixin 8 | from tests.integration.test_utils import podman_compose_path 9 | from tests.integration.test_utils import test_path 10 | 11 | 12 | class TestPodmanCompose(unittest.TestCase, RunSubprocessMixin): 13 | def test_uidmaps(self) -> None: 14 | compose_path = os.path.join(test_path(), "uidmaps", "docker-compose.yml") 15 | try: 16 | self.run_subprocess_assert_returncode([ 17 | podman_compose_path(), 18 | "-f", 19 | compose_path, 20 | "up", 21 | "-d", 22 | ]) 23 | 24 | out, _ = self.run_subprocess_assert_returncode([ 25 | "podman", 26 | "inspect", 27 | "uidmaps_touch_1", 28 | ]) 29 | 30 | inspect_out = json.loads(out) 31 | host_config_map = inspect_out[0].get("HostConfig", {}).get("IDMappings", {}) 32 | self.assertEqual(['0:1:1', '999:0:1'], host_config_map['UidMap']) 33 | self.assertEqual(['0:1:1', '999:0:1'], host_config_map['GidMap']) 34 | finally: 35 | out, _ = self.run_subprocess_assert_returncode([ 36 | podman_compose_path(), 37 | "-f", 38 | compose_path, 39 | "down", 40 | ]) 41 | -------------------------------------------------------------------------------- /tests/integration/ulimit/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM busybox 2 | 3 | COPY ./ulimit.sh /bin/ulimit.sh 4 | -------------------------------------------------------------------------------- /tests/integration/ulimit/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/ulimit/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | ulimit1: 4 | image: ulimit_test 5 | command: ["ulimit.sh" ] 6 | ulimits: nofile=1001 7 | build: 8 | context: ./ 9 | dockerfile: Dockerfile 10 | ulimit2: 11 | image: ulimit_test 12 | command: ["ulimit.sh" ] 13 | ulimits: 14 | - nproc=1002:2002 15 | - nofile=1002 16 | build: 17 | context: ./ 18 | dockerfile: Dockerfile 19 | ulimit3: 20 | image: ulimit_test 21 | command: [ "ulimit.sh" ] 22 | ulimits: 23 | nofile: 1003 24 | nproc: 25 | soft: 1003 26 | hard: 2003 27 | build: 28 | context: ./ 29 | dockerfile: Dockerfile 30 | 31 | -------------------------------------------------------------------------------- /tests/integration/ulimit/test_podman_compose_ulimit.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | 4 | import os 5 | import unittest 6 | 7 | from tests.integration.test_utils import RunSubprocessMixin 8 | from tests.integration.test_utils import podman_compose_path 9 | from tests.integration.test_utils import test_path 10 | 11 | 12 | class TestUlimit(unittest.TestCase, RunSubprocessMixin): 13 | def test_ulimit(self) -> None: 14 | compose_path = os.path.join(test_path(), "ulimit/docker-compose.yaml") 15 | try: 16 | self.run_subprocess_assert_returncode([ 17 | podman_compose_path(), 18 | "-f", 19 | compose_path, 20 | "up", 21 | "-d", 22 | ]) 23 | 24 | out, _ = self.run_subprocess_assert_returncode([ 25 | "podman", 26 | "logs", 27 | "ulimit_ulimit1_1", 28 | ]) 29 | split_output = out.strip(b"\n").split(b"\n") 30 | 31 | # trow away system specific default ulimit values 32 | output_part = [ 33 | el 34 | for el in split_output 35 | if not el.startswith(b"soft process") and not el.startswith(b"hard process") 36 | ] 37 | self.assertEqual( 38 | output_part, 39 | [ 40 | b"soft nofile limit 1001", 41 | b"hard nofile limit 1001", 42 | ], 43 | ) 44 | 45 | out, _ = self.run_subprocess_assert_returncode([ 46 | "podman", 47 | "logs", 48 | "ulimit_ulimit2_1", 49 | ]) 50 | self.assertEqual( 51 | out, 52 | b"soft process limit 1002\nhard process limit 2002\nsoft nofile limit 1002\n" 53 | b"hard nofile limit 1002\n", 54 | ) 55 | 56 | out, _ = self.run_subprocess_assert_returncode([ 57 | "podman", 58 | "logs", 59 | "ulimit_ulimit3_1", 60 | ]) 61 | self.assertEqual( 62 | out, 63 | b"soft process limit 1003\nhard process limit 2003\nsoft nofile limit 1003\n" 64 | b"hard nofile limit 1003\n", 65 | ) 66 | finally: 67 | self.run_subprocess_assert_returncode([ 68 | podman_compose_path(), 69 | "-f", 70 | compose_path, 71 | "down", 72 | ]) 73 | -------------------------------------------------------------------------------- /tests/integration/ulimit/ulimit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo -n "soft process limit " 4 | ulimit -S -u 5 | echo -n "hard process limit " 6 | ulimit -H -u 7 | echo -n "soft nofile limit " 8 | ulimit -S -n 9 | echo -n "hard nofile limit " 10 | ulimit -H -n 11 | -------------------------------------------------------------------------------- /tests/integration/ulimit_build/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM busybox 2 | 3 | COPY ./ulimit.sh /bin/ulimit.sh 4 | 5 | RUN /bin/ulimit.sh 6 | -------------------------------------------------------------------------------- /tests/integration/ulimit_build/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/ulimit_build/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | ulimit1: 4 | image: ulimit_build_test 5 | build: 6 | context: ./ 7 | dockerfile: Dockerfile 8 | ulimits: nofile=1001 9 | ulimit2: 10 | image: ulimit_build_test 11 | build: 12 | context: ./ 13 | dockerfile: Dockerfile 14 | ulimits: 15 | - nproc=1002:2002 16 | - nofile=1002 17 | ulimit3: 18 | image: ulimit_build_test 19 | build: 20 | context: ./ 21 | dockerfile: Dockerfile 22 | ulimits: 23 | nofile: 1003 24 | nproc: 25 | soft: 1003 26 | hard: 2003 27 | -------------------------------------------------------------------------------- /tests/integration/ulimit_build/ulimit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "soft process limit:" $(ulimit -S -u) 4 | echo "hard process limit:" $(ulimit -H -u) 5 | echo "soft nofile limit:" $(ulimit -S -n) 6 | echo "hard nofile limit:" $(ulimit -H -n) 7 | -------------------------------------------------------------------------------- /tests/integration/up_down/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/library/debian:bookworm-slim 2 | RUN apt-get update \ 3 | && apt-get install -y \ 4 | dumb-init \ 5 | && apt-get autoremove 6 | RUN mkdir -p /mnt/test/ 7 | -------------------------------------------------------------------------------- /tests/integration/up_down/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/up_down/docker-compose-orphans.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | volumes: 3 | web1_vol: 4 | web2_vol: 5 | services: 6 | web1: 7 | image: podman-compose-up-down-test 8 | build: . 9 | hostname: web1 10 | command: ["dumb-init", "sleep", "infinity"] 11 | volumes: 12 | - web1_vol:/mnt/test/ 13 | -------------------------------------------------------------------------------- /tests/integration/up_down/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | volumes: 3 | web1_vol: 4 | web2_vol: 5 | services: 6 | web1: 7 | image: podman-compose-up-down-test 8 | build: . 9 | hostname: web1 10 | command: ["dumb-init", "sleep", "infinity"] 11 | volumes: 12 | - web1_vol:/mnt/test/ 13 | web2: 14 | image: docker.io/library/debian:up-down-test 15 | hostname: web2 16 | command: ["sleep", "infinity"] 17 | volumes: 18 | - web2_vol:/mnt/test/ 19 | 20 | -------------------------------------------------------------------------------- /tests/integration/vol/README.md: -------------------------------------------------------------------------------- 1 | # to test create the two external volumes 2 | 3 | ``` 4 | podman volume create my-app-data 5 | podman volume create actual-name-of-volume 6 | podman-compose up 7 | ``` 8 | 9 | -------------------------------------------------------------------------------- /tests/integration/vol/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/integration/vol/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | web: 4 | image: nopush/podman-compose-test 5 | command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8000"] 6 | working_dir: /var/www/html 7 | restart: always 8 | volumes: 9 | - /var/www/html 10 | tmpfs: 11 | - /run 12 | - /tmp 13 | web1: 14 | image: nopush/podman-compose-test 15 | command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"] 16 | restart: unless-stopped 17 | working_dir: /var/www/html 18 | volumes: 19 | - myvol1:/var/www/html:ro,z 20 | web2: 21 | image: nopush/podman-compose-test 22 | command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8002"] 23 | working_dir: /var/www/html 24 | volumes: 25 | - myvol2:/var/www/html:ro 26 | web3: 27 | image: nopush/podman-compose-test 28 | command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8003"] 29 | working_dir: /var/www/html 30 | volumes: 31 | - myvol2:/var/www/html 32 | - data:/var/www/html_data 33 | - data2:/var/www/html_data2 34 | - data3:/var/www/html_data3 35 | 36 | volumes: 37 | myvol1: 38 | myvol2: 39 | labels: 40 | mylabel: myval 41 | data: 42 | name: my-app-data 43 | external: true 44 | data2: 45 | external: 46 | name: actual-name-of-volume 47 | data3: 48 | name: my-app-data3 49 | 50 | -------------------------------------------------------------------------------- /tests/integration/vol/test_podman_compose_vol.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | """ 4 | test_podman_compose_up_down.py 5 | 6 | Tests the podman compose up and down commands used to create and remove services. 7 | """ 8 | 9 | # pylint: disable=redefined-outer-name 10 | import os 11 | import unittest 12 | 13 | from tests.integration.test_utils import RunSubprocessMixin 14 | from tests.integration.test_utils import podman_compose_path 15 | from tests.integration.test_utils import test_path 16 | 17 | 18 | class TestPodmanCompose(unittest.TestCase, RunSubprocessMixin): 19 | def test_down_with_vols(self) -> None: 20 | up_cmd = [ 21 | "coverage", 22 | "run", 23 | podman_compose_path(), 24 | "-f", 25 | os.path.join(test_path(), "vol", "docker-compose.yaml"), 26 | "up", 27 | "-d", 28 | ] 29 | 30 | down_cmd = [ 31 | "coverage", 32 | "run", 33 | podman_compose_path(), 34 | "-f", 35 | os.path.join(test_path(), "vol", "docker-compose.yaml"), 36 | "down", 37 | "--volumes", 38 | ] 39 | 40 | try: 41 | self.run_subprocess_assert_returncode(["podman", "volume", "create", "my-app-data"]) 42 | self.run_subprocess_assert_returncode([ 43 | "podman", 44 | "volume", 45 | "create", 46 | "actual-name-of-volume", 47 | ]) 48 | 49 | self.run_subprocess_assert_returncode(up_cmd) 50 | self.run_subprocess(["podman", "inspect", "volume", ""]) 51 | 52 | finally: 53 | out, _, return_code = self.run_subprocess(down_cmd) 54 | self.run_subprocess(["podman", "volume", "rm", "my-app-data"]) 55 | self.run_subprocess(["podman", "volume", "rm", "actual-name-of-volume"]) 56 | self.assertEqual(return_code, 0) 57 | -------------------------------------------------------------------------------- /tests/integration/yamlmagic/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.6' 2 | 3 | x-deploy-base: &deploy-base 4 | restart_policy: 5 | delay: 2s 6 | 7 | x-common: &common 8 | network: host 9 | deploy: 10 | <<: *deploy-base 11 | networks: 12 | hostnet: {} 13 | 14 | networks: 15 | hostnet: 16 | external: true 17 | name: host 18 | 19 | volumes: 20 | node-red_data: 21 | 22 | services: 23 | node-red: 24 | <<: *common 25 | image: busybox 26 | command: busybox httpd -h /data -f -p 8080 27 | deploy: 28 | <<: *deploy-base 29 | resources: 30 | limits: 31 | cpus: '0.5' 32 | memory: 32M 33 | volumes: 34 | - node-red_data:/data 35 | -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/containers/podman-compose/4c6df85efac6dea83cea68b406d3d1abfb7cacef/tests/unit/__init__.py -------------------------------------------------------------------------------- /tests/unit/test_compose_exec_args.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | import argparse 4 | import unittest 5 | 6 | from podman_compose import compose_exec_args 7 | 8 | 9 | class TestComposeExecArgs(unittest.TestCase): 10 | def test_minimal(self) -> None: 11 | cnt = get_minimal_container() 12 | args = get_minimal_args() 13 | 14 | result = compose_exec_args(cnt, "container_name", args) 15 | expected = ["--interactive", "--tty", "container_name"] 16 | self.assertEqual(result, expected) 17 | 18 | def test_additional_env_value_equals(self) -> None: 19 | cnt = get_minimal_container() 20 | args = get_minimal_args() 21 | args.env = ["key=valuepart1=valuepart2"] 22 | 23 | result = compose_exec_args(cnt, "container_name", args) 24 | expected = [ 25 | "--interactive", 26 | "--tty", 27 | "--env", 28 | "key=valuepart1=valuepart2", 29 | "container_name", 30 | ] 31 | self.assertEqual(result, expected) 32 | 33 | 34 | def get_minimal_container() -> dict: 35 | return {} 36 | 37 | 38 | def get_minimal_args() -> argparse.Namespace: 39 | return argparse.Namespace( 40 | T=None, 41 | cnt_command=None, 42 | env=None, 43 | privileged=None, 44 | user=None, 45 | workdir=None, 46 | ) 47 | -------------------------------------------------------------------------------- /tests/unit/test_compose_run_update_container_from_args.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | import argparse 4 | import unittest 5 | 6 | from podman_compose import PodmanCompose 7 | from podman_compose import compose_run_update_container_from_args 8 | 9 | 10 | class TestComposeRunUpdateContainerFromArgs(unittest.TestCase): 11 | def test_minimal(self) -> None: 12 | cnt = get_minimal_container() 13 | compose = get_minimal_compose() 14 | args = get_minimal_args() 15 | 16 | compose_run_update_container_from_args(compose, cnt, args) 17 | 18 | expected_cnt = {"name": "default_name", "tty": True} 19 | self.assertEqual(cnt, expected_cnt) 20 | 21 | def test_additional_env_value_equals(self) -> None: 22 | cnt = get_minimal_container() 23 | compose = get_minimal_compose() 24 | args = get_minimal_args() 25 | args.env = ["key=valuepart1=valuepart2"] 26 | 27 | compose_run_update_container_from_args(compose, cnt, args) 28 | 29 | expected_cnt = { 30 | "environment": { 31 | "key": "valuepart1=valuepart2", 32 | }, 33 | "name": "default_name", 34 | "tty": True, 35 | } 36 | self.assertEqual(cnt, expected_cnt) 37 | 38 | def test_publish_ports(self) -> None: 39 | cnt = get_minimal_container() 40 | compose = get_minimal_compose() 41 | args = get_minimal_args() 42 | args.publish = ["1111", "2222:2222"] 43 | 44 | compose_run_update_container_from_args(compose, cnt, args) 45 | 46 | expected_cnt = { 47 | "name": "default_name", 48 | "ports": ["1111", "2222:2222"], 49 | "tty": True, 50 | } 51 | self.assertEqual(cnt, expected_cnt) 52 | 53 | 54 | def get_minimal_container() -> dict: 55 | return {} 56 | 57 | 58 | def get_minimal_compose() -> PodmanCompose: 59 | return PodmanCompose() 60 | 61 | 62 | def get_minimal_args() -> argparse.Namespace: 63 | return argparse.Namespace( 64 | T=None, 65 | cnt_command=None, 66 | entrypoint=None, 67 | env=None, 68 | name="default_name", 69 | rm=None, 70 | service=None, 71 | publish=None, 72 | service_ports=None, 73 | user=None, 74 | volume=None, 75 | workdir=None, 76 | ) 77 | -------------------------------------------------------------------------------- /tests/unit/test_is_path_git_url.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | 3 | import unittest 4 | 5 | from parameterized import parameterized 6 | 7 | from podman_compose import is_path_git_url 8 | 9 | 10 | class TestIsPathGitUrl(unittest.TestCase): 11 | @parameterized.expand([ 12 | ("prefix_git", "git://host.xz/path/to/repo", True), 13 | ("prefix_almost_git", "gitt://host.xz/path/to/repo", False), 14 | ("prefix_wrong", "http://host.xz/path/to/repo", False), 15 | ("suffix_git", "http://host.xz/path/to/repo.git", True), 16 | ("suffix_wrong", "http://host.xz/path/to/repo", False), 17 | ("suffix_with_url_fragment", "http://host.xz/path/to/repo.git#fragment", True), 18 | ("suffix_and_prefix", "git://host.xz/path/to/repo.git", True), 19 | ("empty_url_path", "http://#fragment", False), 20 | ]) 21 | def test_is_path_git_url(self, test_name: str, path: str, result: bool) -> None: 22 | self.assertEqual(is_path_git_url(path), result) 23 | -------------------------------------------------------------------------------- /tests/unit/test_normalize_depends_on.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import copy 4 | import unittest 5 | from typing import Any 6 | 7 | from parameterized import parameterized 8 | 9 | from podman_compose import normalize_service 10 | 11 | 12 | class TestNormalizeServicesSimple(unittest.TestCase): 13 | @parameterized.expand([ 14 | ( 15 | {"depends_on": "my_service"}, 16 | {"depends_on": {"my_service": {"condition": "service_started"}}}, 17 | ), 18 | ( 19 | {"depends_on": ["my_service"]}, 20 | {"depends_on": {"my_service": {"condition": "service_started"}}}, 21 | ), 22 | ( 23 | {"depends_on": ["my_service1", "my_service2"]}, 24 | { 25 | "depends_on": { 26 | "my_service1": {"condition": "service_started"}, 27 | "my_service2": {"condition": "service_started"}, 28 | }, 29 | }, 30 | ), 31 | ( 32 | {"depends_on": {"my_service": {"condition": "service_started"}}}, 33 | {"depends_on": {"my_service": {"condition": "service_started"}}}, 34 | ), 35 | ( 36 | {"depends_on": {"my_service": {"condition": "service_healthy"}}}, 37 | {"depends_on": {"my_service": {"condition": "service_healthy"}}}, 38 | ), 39 | ]) 40 | def test_normalize_service_simple( 41 | self, test_case: dict[str, Any], expected: dict[str, Any] 42 | ) -> None: 43 | copy.deepcopy(test_case) 44 | result = normalize_service(test_case) 45 | self.assertEqual(result, expected) 46 | -------------------------------------------------------------------------------- /tests/unit/test_volumes.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | # pylint: disable=redefined-outer-name 3 | import unittest 4 | 5 | from podman_compose import parse_short_mount 6 | 7 | 8 | class ParseShortMountTests(unittest.TestCase): 9 | def test_multi_propagation(self) -> None: 10 | self.assertEqual( 11 | parse_short_mount("/foo/bar:/baz:U,Z", "/"), 12 | { 13 | "type": "bind", 14 | "source": "/foo/bar", 15 | "target": "/baz", 16 | "bind": { 17 | "propagation": "U,Z", 18 | }, 19 | }, 20 | ) 21 | --------------------------------------------------------------------------------