mirror of
https://github.com/containers/podman-compose.git
synced 2025-07-01 13:10:24 +02:00
Compare commits
16 Commits
Author | SHA1 | Date | |
---|---|---|---|
e6accb0eb7 | |||
b5e47230ef | |||
18876d2c09 | |||
1fcf4fe6e1 | |||
544fd7fe12 | |||
e9b103eb23 | |||
bbaa786739 | |||
d1d0f9e452 | |||
d8dba61e08 | |||
3343910763 | |||
34ec4b3cb9 | |||
f4a78ae812 | |||
00b9ce1ee4 | |||
749d188321 | |||
e879529976 | |||
1555417958 |
@ -1,4 +0,0 @@
|
||||
[codespell]
|
||||
skip = .git,*.pdf,*.svg,requirements.txt,test-requirements.txt
|
||||
# poped - loved variable name
|
||||
ignore-words-list = poped
|
@ -1,2 +0,0 @@
|
||||
[run]
|
||||
parallel=True
|
@ -1,19 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = tab
|
||||
tab_width = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
max_line_length = 100
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.py]
|
||||
indent_style = space
|
||||
|
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -35,7 +35,7 @@ What is the behavior you actually got and that should not happen.
|
||||
```
|
||||
$ podman-compose version
|
||||
using podman version: 3.4.0
|
||||
podman-compose version 0.1.7dev
|
||||
podman-composer version 0.1.7dev
|
||||
podman --version
|
||||
podman version 3.4.0
|
||||
|
||||
|
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,7 +0,0 @@
|
||||
|
||||
## Contributor Checklist:
|
||||
|
||||
If this PR adds a new feature that improves compatibility with docker-compose, please add a link
|
||||
to the exact part of compose spec that the PR touches.
|
||||
|
||||
All changes require additional unit tests.
|
6
.github/dependabot.yml
vendored
6
.github/dependabot.yml
vendored
@ -1,6 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
20
.github/workflows/codespell.yml
vendored
20
.github/workflows/codespell.yml
vendored
@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Codespell
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
codespell:
|
||||
name: Check for spelling errors
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Codespell
|
||||
uses: codespell-project/actions-codespell@v2
|
25
.github/workflows/static-checks.yml
vendored
25
.github/workflows/static-checks.yml
vendored
@ -1,25 +0,0 @@
|
||||
name: Static checks
|
||||
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
jobs:
|
||||
static-checks:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: docker.io/library/python:3.11-bookworm
|
||||
# cgroupns needed to address the following error:
|
||||
# write /sys/fs/cgroup/cgroup.subtree_control: operation not supported
|
||||
options: --privileged --cgroupns=host
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Analysing the code with ruff
|
||||
run: |
|
||||
set -e
|
||||
pip install -r test-requirements.txt
|
||||
ruff format --check
|
||||
ruff check
|
||||
- name: Analysing the code with pylint
|
||||
run: |
|
||||
pylint podman_compose.py
|
40
.github/workflows/test.yml
vendored
40
.github/workflows/test.yml
vendored
@ -1,40 +0,0 @@
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: [ '3.8', '3.9', '3.10', '3.11', '3.12' ]
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: "docker.io/library/python:${{ matrix.python-version }}-bookworm"
|
||||
# cgroupns needed to address the following error:
|
||||
# write /sys/fs/cgroup/cgroup.subtree_control: operation not supported
|
||||
options: --privileged --cgroupns=host
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
set -e
|
||||
apt update && apt install -y podman
|
||||
python -m pip install --upgrade pip
|
||||
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
||||
if [ -f test-requirements.txt ]; then pip install -r test-requirements.txt; fi
|
||||
- name: Run tests in tests/
|
||||
run: |
|
||||
python -m unittest tests/*.py
|
||||
env:
|
||||
TESTS_DEBUG: 1
|
||||
- name: Run tests in pytests/
|
||||
run: |
|
||||
coverage run --source podman_compose -m unittest pytests/*.py
|
||||
- name: Report coverage
|
||||
run: |
|
||||
coverage combine
|
||||
coverage report --format=markdown | tee -a $GITHUB_STEP_SUMMARY
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -47,8 +47,6 @@ coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
test-compose.yaml
|
||||
test-compose-?.yaml
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
@ -1,36 +0,0 @@
|
||||
repos:
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 23.3.0
|
||||
hooks:
|
||||
- id: black
|
||||
# It is recommended to specify the latest version of Python
|
||||
# supported by your project here, or alternatively use
|
||||
# pre-commit's default_language_version, see
|
||||
# https://pre-commit.com/#top_level-default_language_version
|
||||
language_version: python3.10
|
||||
types: [python]
|
||||
args: [
|
||||
"--check", # Don't apply changes automatically
|
||||
]
|
||||
- repo: https://github.com/pycqa/flake8
|
||||
rev: 6.0.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
types: [python]
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: pylint
|
||||
name: pylint
|
||||
entry: pylint
|
||||
language: system
|
||||
types: [python]
|
||||
args:
|
||||
[
|
||||
"-rn", # Only display messages
|
||||
"-sn", # Don't display the score
|
||||
"--rcfile=.pylintrc", # Link to your config file
|
||||
]
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.2.5
|
||||
hooks:
|
||||
- id: codespell
|
11
.pylintrc
11
.pylintrc
@ -1,18 +1,13 @@
|
||||
[MESSAGES CONTROL]
|
||||
# C0111 missing-docstring: missing-class-docstring, missing-function-docstring, missing-method-docstring, missing-module-docstrin
|
||||
# consider-using-with: we need it for color formatter pipe
|
||||
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
|
||||
disable=W0614,C0410,C0321,C0111,I0011,C0103
|
||||
# allow _ for ignored variables
|
||||
# allow generic names like a,b,c and i,j,k,l,m,n and x,y,z
|
||||
# allow k,v for key/value
|
||||
# allow e for exceptions, it for iterator, ix for index
|
||||
# allow ip for ip address
|
||||
# allow e for exceptions, it for iterator
|
||||
# allow w,h for width, height
|
||||
# allow op for operation/operator/opcode
|
||||
# allow t, t0, t1, t2, and t3 for time
|
||||
# allow dt for delta time
|
||||
# allow db for database
|
||||
# allow ls for list
|
||||
# allow p for pipe
|
||||
# allow ex for examples, exists ..etc
|
||||
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
|
||||
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,op
|
||||
|
143
CONTRIBUTING.md
143
CONTRIBUTING.md
@ -1,141 +1,74 @@
|
||||
# Contributing to podman-compose
|
||||
|
||||
## Who can contribute?
|
||||
|
||||
- Users that found a bug,
|
||||
- Users that want to propose new functionalities or enhancements,
|
||||
- Users that want to help other users to troubleshoot their environments,
|
||||
- Developers that want to fix bugs,
|
||||
- Developers that want to implement new functionalities or enhancements.
|
||||
|
||||
## Branches
|
||||
|
||||
Please request your pull request to be merged into the `devel` branch.
|
||||
Changes to the `stable` branch are managed by the repository maintainers.
|
||||
|
||||
## Development environment setup
|
||||
|
||||
Note: Some steps are OPTIONAL but all are RECOMMENDED.
|
||||
|
||||
1. Fork the project repository and clone it:
|
||||
|
||||
```shell
|
||||
$ git clone https://github.com/USERNAME/podman-compose.git
|
||||
$ cd podman-compose
|
||||
```
|
||||
|
||||
2. (OPTIONAL) Create a Python virtual environment. Example using
|
||||
[virtualenv wrapper](https://virtualenvwrapper.readthedocs.io/en/latest/):
|
||||
|
||||
```shell
|
||||
$ mkvirtualenv podman-compose
|
||||
```
|
||||
|
||||
3. Install the project runtime and development requirements:
|
||||
|
||||
```shell
|
||||
$ pip install '.[devel]'
|
||||
```
|
||||
|
||||
4. (OPTIONAL) Install `pre-commit` git hook scripts
|
||||
(https://pre-commit.com/#3-install-the-git-hook-scripts):
|
||||
|
||||
```shell
|
||||
$ pre-commit install
|
||||
```
|
||||
|
||||
5. Create a new branch, develop and add tests when possible.
|
||||
6. Run linting and testing before committing code. Ensure all the hooks are passing.
|
||||
|
||||
```shell
|
||||
$ pre-commit run --all-files
|
||||
```
|
||||
|
||||
7. Run code coverage:
|
||||
|
||||
```shell
|
||||
$ coverage run --source podman_compose -m unittest pytests/*.py
|
||||
$ python -m unittest tests/*.py
|
||||
$ coverage combine
|
||||
$ coverage report
|
||||
$ coverage html
|
||||
```
|
||||
|
||||
8. Commit your code to your fork's branch.
|
||||
- Make sure you include a `Signed-off-by` message in your commits.
|
||||
Read [this guide](https://github.com/containers/common/blob/main/CONTRIBUTING.md#sign-your-prs)
|
||||
to learn how to sign your commits.
|
||||
- In the commit message, reference the Issue ID that your code fixes and a brief description of
|
||||
the changes.
|
||||
Example: `Fixes #516: Allow empty network`
|
||||
9. Open a pull request to `containers/podman-compose:devel` and wait for a maintainer to review your
|
||||
work.
|
||||
|
||||
## Adding new commands
|
||||
|
||||
To add a command, you need to add a function that is decorated with `@cmd_run`.
|
||||
To add a command you need to add a function that is decorated
|
||||
with `@cmd_run` passing the compose instance, command name and
|
||||
description. the wrapped function should accept two arguments
|
||||
the compose instance and the command-specific arguments (resulted
|
||||
from python's `argparse` package) inside that command you can
|
||||
run PodMan like this `compose.podman.run(['inspect', 'something'])`
|
||||
and inside that function you can access `compose.pods`
|
||||
and `compose.containers` ...etc.
|
||||
Here is an example
|
||||
|
||||
The decorated function must be declared `async` and should accept two arguments: The compose
|
||||
instance and the command-specific arguments (resulted from the Python's `argparse` package).
|
||||
|
||||
In this function, you can run Podman (e.g. `await compose.podman.run(['inspect', 'something'])`),
|
||||
access `compose.pods`, `compose.containers` etc.
|
||||
|
||||
Here is an example:
|
||||
|
||||
```python
|
||||
```
|
||||
@cmd_run(podman_compose, 'build', 'build images defined in the stack')
|
||||
async def compose_build(compose, args):
|
||||
await compose.podman.run(['build', 'something'])
|
||||
def compose_build(compose, args):
|
||||
compose.podman.run(['build', 'something'])
|
||||
```
|
||||
|
||||
## Command arguments parsing
|
||||
|
||||
To add arguments to be parsed by a command, you need to add a function that is decorated with
|
||||
`@cmd_parse` which accepts the compose instance and the command's name (as a string list or as a
|
||||
single string).
|
||||
Add a function that accept `parser` which is an instance from `argparse`.
|
||||
In side that function you can call `parser.add_argument()`.
|
||||
The function decorated with `@cmd_parse` accepting the compose instance,
|
||||
and command names (as a list or as a string).
|
||||
You can do this multiple times.
|
||||
|
||||
The decorated function should accept a single argument: An instance of `argparse`.
|
||||
Here is an example
|
||||
|
||||
In this function, you can call `parser.add_argument()` to add a new argument to the command.
|
||||
|
||||
Note you can add such a function multiple times.
|
||||
|
||||
Here is an example:
|
||||
|
||||
```python
|
||||
```
|
||||
@cmd_parse(podman_compose, 'build')
|
||||
def compose_build_parse(parser):
|
||||
parser.add_argument("--pull",
|
||||
help="attempt to pull a newer version of the image", action='store_true')
|
||||
parser.add_argument("--pull-always",
|
||||
help="Attempt to pull a newer version of the image, "
|
||||
"raise an error even if the image is present locally.",
|
||||
action='store_true')
|
||||
help="attempt to pull a newer version of the image, Raise an error even if the image is present locally.", action='store_true')
|
||||
```
|
||||
|
||||
NOTE: `@cmd_parse` should be after `@cmd_run`.
|
||||
NOTE: `@cmd_parse` should be after `@cmd_run`
|
||||
|
||||
## Calling a command from another one
|
||||
## Calling a command from inside another
|
||||
|
||||
If you need to call `podman-compose down` from `podman-compose up`, do something like:
|
||||
If you need to call `podman-compose down` from inside `podman-compose up`
|
||||
do something like:
|
||||
|
||||
```python
|
||||
```
|
||||
@cmd_run(podman_compose, 'up', 'up desc')
|
||||
async def compose_up(compose, args):
|
||||
await compose.commands['down'](compose, args)
|
||||
def compose_up(compose, args):
|
||||
compose.commands['down'](compose, args)
|
||||
# or
|
||||
await compose.commands['down'](argparse.Namespace(foo=123))
|
||||
compose.commands['down'](argparse.Namespace(foo=123))
|
||||
```
|
||||
|
||||
|
||||
## Missing Commands (help needed)
|
||||
|
||||
```
|
||||
bundle Generate a Docker bundle from the Compose file
|
||||
config Validate and view the Compose file
|
||||
create Create services
|
||||
events Receive real time events from containers
|
||||
images List images
|
||||
kill Kill containers
|
||||
logs View output from containers
|
||||
pause Pause services
|
||||
port Print the public port for a port binding
|
||||
ps List containers
|
||||
rm Remove stopped containers
|
||||
run Run a one-off command
|
||||
scale Set number of containers for a service
|
||||
top Display the running processes
|
||||
unpause Unpause services
|
||||
version Show the Docker-Compose version information
|
||||
```
|
||||
|
73
README.md
73
README.md
@ -1,54 +1,44 @@
|
||||
# Podman Compose
|
||||
## [](https://github.com/containers/podman-compose/actions/workflows/test.yml)
|
||||
|
||||
An implementation of [Compose Spec](https://compose-spec.io/) with [Podman](https://podman.io/) backend.
|
||||
This project focuses on:
|
||||
An implementation of `docker-compose` with [Podman](https://podman.io/) backend.
|
||||
The main objective of this project is to be able to run `docker-compose.yml` unmodified and rootless.
|
||||
This project is aimed to provide drop-in replacement for `docker-compose`,
|
||||
and it's very useful for certain cases because:
|
||||
|
||||
* rootless
|
||||
* daemon-less process model, we directly execute podman, no running daemon.
|
||||
- can run rootless
|
||||
- no daemon, no setup.
|
||||
- can be used by developers to run single-machine containerized stacks using single familiar YAML file
|
||||
|
||||
This project only depends on:
|
||||
This project only depend on:
|
||||
|
||||
* `podman`
|
||||
* [podman dnsname plugin](https://github.com/containers/dnsname): It is usually found in the `podman-plugins` or `podman-dnsname` distro packages, those packages are not pulled by default and you need to install them. This allows containers to be able to resolve each other if they are on the same CNI network.
|
||||
* Python3
|
||||
* [PyYAML](https://pyyaml.org/)
|
||||
* [python-dotenv](https://pypi.org/project/python-dotenv/)
|
||||
|
||||
And it's formed as a single Python file script that you can drop into your PATH and run.
|
||||
And it's formed as a single python file script that you can drop into your PATH and run.
|
||||
|
||||
## References:
|
||||
|
||||
* [spec.md](https://github.com/compose-spec/compose-spec/blob/master/spec.md)
|
||||
* [docker-compose compose-file-v3](https://docs.docker.com/compose/compose-file/compose-file-v3/)
|
||||
* [docker-compose compose-file-v2](https://docs.docker.com/compose/compose-file/compose-file-v2/)
|
||||
|
||||
## Alternatives
|
||||
|
||||
As in [this article](https://fedoramagazine.org/use-docker-compose-with-podman-to-orchestrate-containers-on-fedora/) you can setup a `podman.socket` and use unmodified `docker-compose` that talks to that socket but in this case you lose the process-model (ex. `docker-compose build` will send a possibly large context tarball to the daemon)
|
||||
|
||||
For production-like single-machine containerized environment consider
|
||||
|
||||
- [k3s](https://k3s.io) | [k3s github](https://github.com/rancher/k3s)
|
||||
- [MiniKube](https://minikube.sigs.k8s.io/)
|
||||
- [MiniShift](https://www.okd.io/minishift/)
|
||||
|
||||
|
||||
For the real thing (multi-node clusters) check any production
|
||||
OpenShift/Kubernetes distribution like [OKD](https://www.okd.io/).
|
||||
OpenShift/Kubernetes distribution like [OKD](https://www.okd.io/minishift/).
|
||||
|
||||
## Versions
|
||||
|
||||
If you have legacy version of `podman` (before 3.1.0) you might need to stick with legacy `podman-compose` `0.1.x` branch.
|
||||
If you have legacy version of `podman` (before 3.x) you might need to stick with legacy `podman-compose` `0.1.x` branch.
|
||||
The legacy branch 0.1.x uses mappings and workarounds to compensate for rootless limitations.
|
||||
|
||||
Modern podman versions (>=3.4) do not have those limitations, and thus you can use latest and stable 1.x branch.
|
||||
|
||||
If you are upgrading from `podman-compose` version `0.1.x` then we no longer have global option `-t` to set mapping type
|
||||
like `hostnet`. If you desire that behavior, pass it the standard way like `network_mode: host` in the YAML.
|
||||
|
||||
Modern podman versions (>=3.4) do not have those limitations and thus you can use latest and stable 1.x branch.
|
||||
|
||||
## Installation
|
||||
|
||||
Install the latest stable version from PyPI:
|
||||
Install latest stable version from PyPI:
|
||||
|
||||
```
|
||||
pip3 install podman-compose
|
||||
@ -62,6 +52,19 @@ Or latest development version from GitHub:
|
||||
pip3 install https://github.com/containers/podman-compose/archive/devel.tar.gz
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
curl -o /usr/local/bin/podman-compose https://raw.githubusercontent.com/containers/podman-compose/devel/podman_compose.py
|
||||
chmod +x /usr/local/bin/podman-compose
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
curl -o ~/.local/bin/podman-compose https://raw.githubusercontent.com/containers/podman-compose/devel/podman_compose.py
|
||||
chmod +x ~/.local/bin/podman-compose
|
||||
```
|
||||
|
||||
or install from Fedora (starting from f31) repositories:
|
||||
|
||||
@ -72,8 +75,6 @@ sudo dnf install podman-compose
|
||||
## Basic Usage
|
||||
|
||||
We have included fully functional sample stacks inside `examples/` directory.
|
||||
You can get more examples from [awesome-compose](https://github.com/docker/awesome-compose).
|
||||
|
||||
|
||||
A quick example would be
|
||||
|
||||
@ -94,21 +95,19 @@ which have
|
||||
- a django tasks
|
||||
|
||||
|
||||
When testing the `AWX3` example, if you got errors, just wait for db migrations to end.
|
||||
There is also AWX 17.1.0
|
||||
When testing the `AWX3` example, if you got errors just wait for db migrations to end.
|
||||
|
||||
|
||||
## Tests
|
||||
|
||||
Inside `tests/` directory we have many useless docker-compose stacks
|
||||
that are meant to test as many cases as we can to make sure we are compatible
|
||||
that are meant to test as much cases as we can to make sure we are compatible
|
||||
|
||||
### Unit tests with unittest
|
||||
run a unittest with following command
|
||||
## How it works
|
||||
|
||||
```shell
|
||||
python -m unittest pytests/*.py
|
||||
```
|
||||
The default mapping `1podfw` creates a single pod and attach all containers to
|
||||
its network namespace so that all containers talk via localhost.
|
||||
For more information see [docs/Mappings.md](docs/Mappings.md).
|
||||
|
||||
# Contributing guide
|
||||
If you are running as root, you might use identity mapping.
|
||||
|
||||
If you are a user or a developer and want to contribute please check the [CONTRIBUTING](CONTRIBUTING.md) section
|
||||
|
@ -1,411 +0,0 @@
|
||||
# Naming convention:
|
||||
# * _camelCase for function names
|
||||
# * snake_case for variable names
|
||||
|
||||
# all functions will return 0 if they successfully complete the argument
|
||||
# (or establish there is no need or no way to complete), and something
|
||||
# other than 0 if that's not the case
|
||||
|
||||
# complete arguments to global options
|
||||
_completeGlobalOptArgs() {
|
||||
# arguments to options that take paths as arguments: complete paths
|
||||
for el in ${path_arg_global_opts}; do
|
||||
if [[ ${prev} == ${el} ]]; then
|
||||
COMPREPLY=( $(compgen -f -- ${cur}) )
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
# arguments to options that take generic arguments: don't complete
|
||||
for el in ${generic_arg_global_opts}; do
|
||||
if [[ ${prev} == ${el} ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# complete root subcommands and options
|
||||
_completeRoot() {
|
||||
# if we're completing an option
|
||||
if [[ ${cur} == -* ]]; then
|
||||
COMPREPLY=( $(compgen -W "${global_opts}" -- ${cur}) )
|
||||
return 0
|
||||
fi
|
||||
# complete root commands
|
||||
COMPREPLY=( $(compgen -W "${root_commands}" -- ${cur}) )
|
||||
return 0
|
||||
}
|
||||
|
||||
# complete names of Compose services
|
||||
_completeServiceNames() {
|
||||
# ideally we should complete service names,
|
||||
# but parsing the compose spec file in the
|
||||
# completion script is quite complex
|
||||
return 0
|
||||
}
|
||||
|
||||
# complete commands to run inside containers
|
||||
_completeCommand() {
|
||||
# we would need to complete commands to run inside
|
||||
# a container
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
# complete the arguments for `podman-compose up` and return 0
|
||||
_completeUpArgs() {
|
||||
up_opts="${help_opts} -d --detach --no-color --quiet-pull --no-deps --force-recreate --always-recreate-deps --no-recreate --no-build --no-start --build --abort-on-container-exit -t --timeout -V --renew-anon-volumes --remove-orphans --scale --exit-code-from --pull --pull-always --build-arg --no-cache"
|
||||
if [[ ${prev} == "--scale" || ${prev} == "-t" || ${prev} == "--timeout" ]]; then
|
||||
return 0
|
||||
elif [[ ${cur} == -* ]]; then
|
||||
COMPREPLY=( $(compgen -W "${up_opts}" -- ${cur}) )
|
||||
return 0
|
||||
else
|
||||
_completeServiceNames
|
||||
if [[ $? -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
# complete the arguments for `podman-compose exec` and return 0
|
||||
_completeExecArgs() {
|
||||
exec_opts="${help_opts} -d --detach --privileged -u --user -T --index -e --env -w --workdir"
|
||||
if [[ ${prev} == "-u" || ${prev} == "--user" || ${prev} == "--index" || ${prev} == "-e" || ${prev} == "--env" || ${prev} == "-w" || ${prev} == "--workdir" ]]; then
|
||||
return 0
|
||||
elif [[ ${cur} == -* ]]; then
|
||||
COMPREPLY=( $(compgen -W "${exec_opts}" -- ${cur}) )
|
||||
return 0
|
||||
elif [[ ${comp_cword_adj} -eq 2 ]]; then
|
||||
# complete service name
|
||||
_completeServiceNames
|
||||
if [[ $? -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
elif [[ ${comp_cword_adj} -eq 3 ]]; then
|
||||
_completeCommand
|
||||
if [[ $? -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
|
||||
# complete the arguments for `podman-compose down` and return 0
|
||||
_completeDownArgs() {
|
||||
down_opts="${help_opts} -v --volumes -t --timeout --remove-orphans"
|
||||
if [[ ${prev} == "-t" || ${prev} == "--timeout" ]]; then
|
||||
return 0
|
||||
elif [[ ${cur} == -* ]]; then
|
||||
COMPREPLY=( $(compgen -W "${down_opts}" -- ${cur}) )
|
||||
return 0
|
||||
else
|
||||
_completeServiceNames
|
||||
if [[ $? -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
|
||||
# complete the arguments for `podman-compose build` and return 0
|
||||
_completeBuildArgs() {
|
||||
build_opts="${help_opts} --pull --pull-always --build-arg --no-cache"
|
||||
if [[ ${prev} == "--build-arg" ]]; then
|
||||
return 0
|
||||
elif [[ ${cur} == -* ]]; then
|
||||
COMPREPLY=( $(compgen -W "${build_opts}" -- ${cur}) )
|
||||
return 0
|
||||
else
|
||||
_completeServiceNames
|
||||
if [[ $? -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# complete the arguments for `podman-compose logs` and return 0
|
||||
_completeLogsArgs() {
|
||||
logs_opts="${help_opts} -f --follow -l --latest -n --names --since -t --timestamps --tail --until"
|
||||
if [[ ${prev} == "--since" || ${prev} == "--tail" || ${prev} == "--until" ]]; then
|
||||
return 0
|
||||
elif [[ ${cur} == -* ]]; then
|
||||
COMPREPLY=( $(compgen -W "${logs_opts}" -- ${cur}) )
|
||||
return 0
|
||||
else
|
||||
_completeServiceNames
|
||||
if [[ $? -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# complete the arguments for `podman-compose ps` and return 0
|
||||
_completePsArgs() {
|
||||
ps_opts="${help_opts} -q --quiet"
|
||||
if [[ ${cur} == -* ]]; then
|
||||
COMPREPLY=( $(compgen -W "${ps_opts}" -- ${cur}) )
|
||||
return 0
|
||||
else
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# complete the arguments for `podman-compose pull` and return 0
|
||||
_completePullArgs() {
|
||||
pull_opts="${help_opts} --force-local"
|
||||
if [[ ${cur} == -* ]]; then
|
||||
COMPREPLY=( $(compgen -W "${pull_opts}" -- ${cur}) )
|
||||
return 0
|
||||
else
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# complete the arguments for `podman-compose push` and return 0
|
||||
_completePushArgs() {
|
||||
push_opts="${help_opts} --ignore-push-failures"
|
||||
if [[ ${cur} == -* ]]; then
|
||||
COMPREPLY=( $(compgen -W "${push_opts}" -- ${cur}) )
|
||||
return 0
|
||||
else
|
||||
_completeServiceNames
|
||||
if [[ $? -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# complete the arguments for `podman-compose restart` and return 0
|
||||
_completeRestartArgs() {
|
||||
restart_opts="${help_opts} -t --timeout"
|
||||
if [[ ${prev} == "-t" || ${prev} == "--timeout" ]]; then
|
||||
return 0
|
||||
elif [[ ${cur} == -* ]]; then
|
||||
COMPREPLY=( $(compgen -W "${restart_opts}" -- ${cur}) )
|
||||
return 0
|
||||
else
|
||||
_completeServiceNames
|
||||
if [[ $? -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# complete the arguments for `podman-compose stop` and return 0
|
||||
_completeStopArgs() {
|
||||
stop_opts="${help_opts} -t --timeout"
|
||||
if [[ ${prev} == "-t" || ${prev} == "--timeout" ]]; then
|
||||
return 0
|
||||
elif [[ ${cur} == -* ]]; then
|
||||
COMPREPLY=( $(compgen -W "${stop_opts}" -- ${cur}) )
|
||||
return 0
|
||||
else
|
||||
_completeServiceNames
|
||||
if [[ $? -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# complete the arguments for `podman-compose start` and return 0
|
||||
_completeStartArgs() {
|
||||
start_opts="${help_opts}"
|
||||
if [[ ${cur} == -* ]]; then
|
||||
COMPREPLY=( $(compgen -W "${start_opts}" -- ${cur}) )
|
||||
return 0
|
||||
else
|
||||
_completeServiceNames
|
||||
if [[ $? -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# complete the arguments for `podman-compose run` and return 0
|
||||
_completeRunArgs() {
|
||||
run_opts="${help_opts} -d --detach --privileged -u --user -T --index -e --env -w --workdir"
|
||||
if [[ ${prev} == "-u" || ${prev} == "--user" || ${prev} == "--index" || ${prev} == "-e" || ${prev} == "--env" || ${prev} == "-w" || ${prev} == "--workdir" ]]; then
|
||||
return 0
|
||||
elif [[ ${cur} == -* ]]; then
|
||||
COMPREPLY=( $(compgen -W "${run_opts}" -- ${cur}) )
|
||||
return 0
|
||||
elif [[ ${comp_cword_adj} -eq 2 ]]; then
|
||||
# complete service name
|
||||
_completeServiceNames
|
||||
if [[ $? -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
elif [[ ${comp_cword_adj} -eq 3 ]]; then
|
||||
_completeCommand
|
||||
if [[ $? -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
_podmanCompose() {
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
root_commands="help version pull push build up down ps run exec start stop restart logs"
|
||||
|
||||
# options to output help text (used as global and subcommand options)
|
||||
help_opts="-h --help"
|
||||
|
||||
# global options that don't take additional arguments
|
||||
basic_global_opts="${help_opts} -v --no-ansi --no-cleanup --dry-run"
|
||||
|
||||
# global options that take paths as arguments
|
||||
path_arg_global_opts="-f --file --podman-path"
|
||||
path_arg_global_opts_array=($arg_global_opts)
|
||||
|
||||
# global options that take arguments that are not files
|
||||
generic_arg_global_opts="-p --project-name --podman-path --podman-args --podman-pull-args --podman-push-args --podman-build-args --podman-inspect-args --podman-run-args --podman-start-args --podman-stop-args --podman-rm-args --podman-volume-args"
|
||||
generic_arg_global_opts_array=($generic_arg_global_opts)
|
||||
|
||||
# all global options that take arguments
|
||||
arg_global_opts="${path_arg_global_opts} ${generic_arg_global_opts}"
|
||||
arg_global_opts_array=($arg_global_opts)
|
||||
|
||||
# all global options
|
||||
global_opts="${basic_global_opts} ${arg_global_opts}"
|
||||
|
||||
chosen_root_command=""
|
||||
|
||||
|
||||
_completeGlobalOptArgs
|
||||
if [[ $? -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# computing comp_cword_adj, which thruthfully tells us how deep in the subcommands tree we are
|
||||
# additionally, set the chosen_root_command if possible
|
||||
comp_cword_adj=${COMP_CWORD}
|
||||
if [[ ${COMP_CWORD} -ge 2 ]]; then
|
||||
skip_next="no"
|
||||
for el in ${COMP_WORDS[@]}; do
|
||||
# if the user has asked for help text there's no need to complete further
|
||||
if [[ ${el} == "-h" || ${el} == "--help" ]]; then
|
||||
return 0
|
||||
fi
|
||||
if [[ ${skip_next} == "yes" ]]; then
|
||||
let "comp_cword_adj--"
|
||||
skip_next="no"
|
||||
continue
|
||||
fi
|
||||
if [[ ${el} == -* && ${el} != ${cur} ]]; then
|
||||
let "comp_cword_adj--"
|
||||
|
||||
for opt in ${arg_global_opts_array[@]}; do
|
||||
if [[ ${el} == ${opt} ]]; then
|
||||
skip_next="yes"
|
||||
fi
|
||||
done
|
||||
elif [[ ${el} != ${cur} && ${el} != ${COMP_WORDS[0]} && ${chosen_root_command} == "" ]]; then
|
||||
chosen_root_command=${el}
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ ${comp_cword_adj} -eq 1 ]]; then
|
||||
_completeRoot
|
||||
|
||||
# Given that we check the value of comp_cword_adj outside
|
||||
# of it, at the moment _completeRoot should always return
|
||||
# 0, this is just here in case changes are made. The same
|
||||
# will apply to similar functions below
|
||||
if [[ $? -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
case $chosen_root_command in
|
||||
up)
|
||||
_completeUpArgs
|
||||
if [[ $? -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
down)
|
||||
_completeDownArgs
|
||||
if [[ $? -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
exec)
|
||||
_completeExecArgs
|
||||
if [[ $? -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
build)
|
||||
_completeBuildArgs
|
||||
if [[ $? -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
logs)
|
||||
_completeLogsArgs
|
||||
if [[ $? -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
ps)
|
||||
_completePsArgs
|
||||
if [[ $? -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
pull)
|
||||
_completePullArgs
|
||||
if [[ $? -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
push)
|
||||
_completePushArgs
|
||||
if [[ $? -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
restart)
|
||||
_completeRestartArgs
|
||||
if [[ $? -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
start)
|
||||
_completeStartArgs
|
||||
if [[ $? -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
_completeStopArgs
|
||||
if [[ $? -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
run)
|
||||
_completeRunArgs
|
||||
if [[ $? -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
complete -F _podmanCompose podman-compose
|
@ -1,33 +0,0 @@
|
||||
Version v1.1.0 (2024-04-17)
|
||||
===========================
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
- Fixed support for values with equals sign in `-e` argument of `run` and `exec` commands.
|
||||
- Fixed duplicate arguments being emitted in `stop` and `restart` commands.
|
||||
- Removed extraneous debug output. `--verbose` flag has been added to preserve verbose output.
|
||||
- Links aliases are now added to service aliases.
|
||||
- Fixed image build process to use defined environmental variables.
|
||||
- Empty list is now allowed to be `COMMAND` and `ENTRYPOINT`.
|
||||
- Environment files are now resolved relative to current working directory.
|
||||
- Exit code of container build is now preserved as return code of `build` command.
|
||||
|
||||
New features
|
||||
------------
|
||||
|
||||
- Added support for `uidmap`, `gidmap`, `http_proxy` and `runtime` service configuration keys.
|
||||
- Added support for `enable_ipv6` network configuration key.
|
||||
- Added `--parallel` option to support parallel pulling and building of images.
|
||||
- Implemented support for maps in `sysctls` container configuration key.
|
||||
- Implemented `stats` command.
|
||||
- Added `--no-normalize` flag to `config` command.
|
||||
- Added support for `include` global configuration key.
|
||||
- Added support for `build` command.
|
||||
- Added support to start containers with multiple networks.
|
||||
- Added support for `profile` argument.
|
||||
- Added support for starting podman in existing network namespace.
|
||||
- Added IPAM driver support.
|
||||
- Added support for file secrets being passed to `podman build` via `--secret` argument.
|
||||
- Added support for multiple networks with separately specified IP and MAC address.
|
||||
- Added support for `service.build.ulimits` when building image.
|
@ -1,67 +0,0 @@
|
||||
# Podman specific extensions to the docker-compose format
|
||||
|
||||
Podman-compose supports the following extension to the docker-compose format.
|
||||
|
||||
## Per-network MAC-addresses
|
||||
|
||||
Generic docker-compose files support specification of the MAC address on the container level. If the
|
||||
container has multiple network interfaces, the specified MAC address is applied to the first
|
||||
specified network.
|
||||
|
||||
Podman-compose in addition supports the specification of MAC addresses on a per-network basis. This
|
||||
is done by adding a `x-podman.mac_address` key to the network configuration in the container. The
|
||||
value of the `x-podman.mac_address` key is the MAC address to be used for the network interface.
|
||||
|
||||
Specifying a MAC address for the container and for individual networks at the same time is not
|
||||
supported.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
---
|
||||
version: "3"
|
||||
|
||||
networks:
|
||||
net0:
|
||||
driver: "bridge"
|
||||
ipam:
|
||||
config:
|
||||
- subnet: "192.168.0.0/24"
|
||||
net1:
|
||||
driver: "bridge"
|
||||
ipam:
|
||||
config:
|
||||
- subnet: "192.168.1.0/24"
|
||||
|
||||
services:
|
||||
webserver
|
||||
image: "busybox"
|
||||
command: ["/bin/busybox", "httpd", "-f", "-h", "/etc", "-p", "8001"]
|
||||
networks:
|
||||
net0:
|
||||
ipv4_address: "192.168.0.10"
|
||||
x-podman.mac_address: "02:aa:aa:aa:aa:aa"
|
||||
net1:
|
||||
ipv4_address: "192.168.1.10"
|
||||
x-podman.mac_address: "02:bb:bb:bb:bb:bb"
|
||||
```
|
||||
|
||||
## Podman-specific network modes
|
||||
|
||||
Generic docker-compose supports the following values for `network-mode` for a container:
|
||||
|
||||
- `bridge`
|
||||
- `host`
|
||||
- `none`
|
||||
- `service`
|
||||
- `container`
|
||||
|
||||
In addition, podman-compose supports the following podman-specific values for `network-mode`:
|
||||
|
||||
- `slirp4netns[:<options>,...]`
|
||||
- `ns:<options>`
|
||||
- `pasta[:<options>,...]`
|
||||
- `private`
|
||||
|
||||
The options to the network modes are passed to the `--network` option of the `podman create` command
|
||||
as-is.
|
@ -1,7 +1,7 @@
|
||||
---
|
||||
- name: Manage AWX Container Images
|
||||
block:
|
||||
- name: Export Docker awx image if it isn't local and there isn't a registry defined
|
||||
- name: Export Docker awx image if it isnt local and there isnt a registry defined
|
||||
docker_image:
|
||||
name: "{{ awx_image }}"
|
||||
tag: "{{ awx_version }}"
|
||||
|
@ -1,17 +0,0 @@
|
||||
# Azure Vote Example
|
||||
|
||||
This example have two containers:
|
||||
|
||||
* backend: `redis` used as storage
|
||||
* frontend: having supervisord, nginx, uwsgi/python
|
||||
|
||||
|
||||
```
|
||||
echo "HOST_PORT=8080" > .env
|
||||
podman-compose up
|
||||
```
|
||||
|
||||
after typing the commands above open your browser on the host port you picked above like
|
||||
[http://localhost:8080/](http://localhost:8080/)
|
||||
|
||||
|
@ -1,16 +0,0 @@
|
||||
---
|
||||
# from https://github.com/Azure-Samples/azure-voting-app-redis/blob/master/docker-compose.yaml
|
||||
version: '3'
|
||||
services:
|
||||
azure-vote-back:
|
||||
image: mcr.microsoft.com/oss/bitnami/redis:6.0.8
|
||||
container_name: azure-vote-back
|
||||
environment:
|
||||
ALLOW_EMPTY_PASSWORD: "yes"
|
||||
azure-vote-front:
|
||||
image: mcr.microsoft.com/azuredocs/azure-vote-front:v1
|
||||
environment:
|
||||
REDIS: azure-vote-back
|
||||
ports:
|
||||
- "${HOST_PORT:-8080}:80"
|
||||
|
@ -1,31 +0,0 @@
|
||||
# Echo Service example
|
||||
|
||||
```
|
||||
podman-compose up
|
||||
```
|
||||
|
||||
Test the service with `curl like this`
|
||||
|
||||
```
|
||||
$ curl -X POST -d "foobar" http://localhost:8080/; echo
|
||||
|
||||
CLIENT VALUES:
|
||||
client_address=10.89.31.2
|
||||
command=POST
|
||||
real path=/
|
||||
query=nil
|
||||
request_version=1.1
|
||||
request_uri=http://localhost:8080/
|
||||
|
||||
SERVER VALUES:
|
||||
server_version=nginx: 1.10.0 - lua: 10001
|
||||
|
||||
HEADERS RECEIVED:
|
||||
accept=*/*
|
||||
content-length=6
|
||||
content-type=application/x-www-form-urlencoded
|
||||
host=localhost:8080
|
||||
user-agent=curl/7.76.1
|
||||
BODY:
|
||||
foobar
|
||||
```
|
@ -1,8 +0,0 @@
|
||||
---
|
||||
version: '3'
|
||||
services:
|
||||
web:
|
||||
image: k8s.gcr.io/echoserver:1.4
|
||||
ports:
|
||||
- "${HOST_PORT:-8080}:8080"
|
||||
|
@ -1,12 +0,0 @@
|
||||
# GCR Hello App Redis
|
||||
|
||||
A 6-node redis cluster using [Bitnami](https://github.com/bitnami/bitnami-docker-redis-cluster)
|
||||
with a [simple hit counter](https://github.com/GoogleCloudPlatform/kubernetes-engine-samples/tree/main/hello-app-redis) that persists on that redis cluster
|
||||
|
||||
```
|
||||
podman-compose up
|
||||
```
|
||||
|
||||
then open your browser on [http://localhost:8080/](http://localhost:8080/)
|
||||
|
||||
|
@ -1,67 +0,0 @@
|
||||
---
|
||||
version: '3'
|
||||
volumes:
|
||||
redis-node1-data:
|
||||
redis-node2-data:
|
||||
redis-node3-data:
|
||||
redis-node4-data:
|
||||
redis-node5-data:
|
||||
redis-data:
|
||||
services:
|
||||
web:
|
||||
image: gcr.io/google-samples/hello-app-redis:1.0
|
||||
depends_on:
|
||||
- redis-cluster
|
||||
ports:
|
||||
- "${HOST_PORT:-8080}:8080"
|
||||
redis-node1:
|
||||
image: docker.io/bitnami/redis-cluster:6.2
|
||||
volumes:
|
||||
- redis-node1-data:/bitnami/redis/data
|
||||
environment:
|
||||
- ALLOW_EMPTY_PASSWORD=yes
|
||||
- REDIS_NODES=redis-node1 redis-node2 redis-node3 redis-node4 redis-node5 redis-cluster
|
||||
redis-node2:
|
||||
image: docker.io/bitnami/redis-cluster:6.2
|
||||
volumes:
|
||||
- redis-node2-data:/bitnami/redis/data
|
||||
environment:
|
||||
- ALLOW_EMPTY_PASSWORD=yes
|
||||
- REDIS_NODES=redis-node1 redis-node2 redis-node3 redis-node4 redis-node5 redis-cluster
|
||||
redis-node3:
|
||||
image: docker.io/bitnami/redis-cluster:6.2
|
||||
volumes:
|
||||
- redis-node3-data:/bitnami/redis/data
|
||||
environment:
|
||||
- ALLOW_EMPTY_PASSWORD=yes
|
||||
- REDIS_NODES=redis-node1 redis-node2 redis-node3 redis-node4 redis-node5 redis-cluster
|
||||
redis-node4:
|
||||
image: docker.io/bitnami/redis-cluster:6.2
|
||||
volumes:
|
||||
- redis-node4-data:/bitnami/redis/data
|
||||
environment:
|
||||
- ALLOW_EMPTY_PASSWORD=yes
|
||||
- REDIS_NODES=redis-node1 redis-node2 redis-node3 redis-node4 redis-node5 redis-cluster
|
||||
redis-node5:
|
||||
image: docker.io/bitnami/redis-cluster:6.2
|
||||
volumes:
|
||||
- redis-node5-data:/bitnami/redis/data
|
||||
environment:
|
||||
- ALLOW_EMPTY_PASSWORD=yes
|
||||
- REDIS_NODES=redis-node1 redis-node2 redis-node3 redis-node4 redis-node5 redis-cluster
|
||||
|
||||
redis-cluster:
|
||||
image: docker.io/bitnami/redis-cluster:6.2
|
||||
volumes:
|
||||
- redis-data:/bitnami/redis/data
|
||||
depends_on:
|
||||
- redis-node1
|
||||
- redis-node2
|
||||
- redis-node3
|
||||
- redis-node4
|
||||
- redis-node5
|
||||
environment:
|
||||
- ALLOW_EMPTY_PASSWORD=yes
|
||||
- REDIS_NODES=redis-node1 redis-node2 redis-node3 redis-node4 redis-node5 redis-cluster
|
||||
- REDIS_CLUSTER_CREATOR=yes
|
||||
|
@ -1,10 +0,0 @@
|
||||
# GCR Hello App
|
||||
|
||||
A small ~2MB image, type
|
||||
|
||||
```
|
||||
podman-compose up
|
||||
```
|
||||
|
||||
then open your browser on [http://localhost:8080/](http://localhost:8080/)
|
||||
|
@ -1,8 +0,0 @@
|
||||
---
|
||||
version: '3'
|
||||
services:
|
||||
web:
|
||||
image: gcr.io/google-samples/hello-app:1.0
|
||||
ports:
|
||||
- "${HOST_PORT:-8080}:8080"
|
||||
|
@ -1,12 +0,0 @@
|
||||
FROM python:3.9-alpine
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY requirements.txt ./
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD [ "python", "-m", "app.web" ]
|
||||
EXPOSE 8080
|
||||
|
@ -1,8 +0,0 @@
|
||||
# Simple Python Demo
|
||||
## A Redis counter
|
||||
|
||||
```
|
||||
podman-compose up -d
|
||||
curl localhost:8080/
|
||||
curl localhost:8080/hello.json
|
||||
```
|
@ -1,39 +0,0 @@
|
||||
# pylint: disable=import-error
|
||||
# pylint: disable=unused-import
|
||||
import asyncio # noqa: F401
|
||||
import os
|
||||
|
||||
import aioredis
|
||||
from aiohttp import web
|
||||
|
||||
REDIS_HOST = os.environ.get("REDIS_HOST", "localhost")
|
||||
REDIS_PORT = int(os.environ.get("REDIS_PORT", "6379"))
|
||||
REDIS_DB = int(os.environ.get("REDIS_DB", "0"))
|
||||
|
||||
redis = aioredis.from_url(f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB}")
|
||||
app = web.Application()
|
||||
routes = web.RouteTableDef()
|
||||
|
||||
|
||||
@routes.get("/")
|
||||
async def hello(request): # pylint: disable=unused-argument
|
||||
counter = await redis.incr("mycounter")
|
||||
return web.Response(text=f"counter={counter}")
|
||||
|
||||
|
||||
@routes.get("/hello.json")
|
||||
async def hello_json(request): # pylint: disable=unused-argument
|
||||
counter = await redis.incr("mycounter")
|
||||
data = {"counter": counter}
|
||||
return web.json_response(data)
|
||||
|
||||
|
||||
app.add_routes(routes)
|
||||
|
||||
|
||||
def main():
|
||||
web.run_app(app, port=8080)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,21 +0,0 @@
|
||||
---
|
||||
version: '3'
|
||||
volumes:
|
||||
redis:
|
||||
services:
|
||||
redis:
|
||||
read_only: true
|
||||
image: docker.io/redis:alpine
|
||||
command: ["redis-server", "--appendonly", "yes", "--notify-keyspace-events", "Ex"]
|
||||
volumes:
|
||||
- redis:/data
|
||||
web:
|
||||
read_only: true
|
||||
build:
|
||||
context: .
|
||||
image: hello-py-aioweb
|
||||
ports:
|
||||
- 8080:8080
|
||||
environment:
|
||||
REDIS_HOST: redis
|
||||
|
@ -1,3 +0,0 @@
|
||||
aiohttp
|
||||
aioredis
|
||||
# aioredis[hiredis]
|
@ -1,71 +0,0 @@
|
||||
{
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
"node": {
|
||||
"extensions": [".js", ".mjs", ".ts", ".cjs"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020,
|
||||
"sourceType": "module",
|
||||
"allowImportExportEverywhere": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:import/errors",
|
||||
"plugin:import/warnings",
|
||||
"plugin:import/typescript",
|
||||
"plugin:promise/recommended",
|
||||
"google",
|
||||
"plugin:security/recommended"
|
||||
],
|
||||
"plugins": ["promise", "security", "import"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": "public/**/*.min.js",
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": false,
|
||||
"es6": false
|
||||
},
|
||||
"parserOptions": {
|
||||
"sourceType": "script"
|
||||
},
|
||||
"extends": ["plugin:compat/recommended"],
|
||||
"plugins": [],
|
||||
"rules": {
|
||||
"no-var": ["off"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"rules": {
|
||||
"security/detect-non-literal-fs-filename":["off"],
|
||||
"security/detect-object-injection":["off"],
|
||||
"camelcase": ["off"],
|
||||
"no-console": ["off"],
|
||||
"require-jsdoc": ["off"],
|
||||
"one-var": ["off"],
|
||||
"guard-for-in": ["off"],
|
||||
"max-len": [
|
||||
"warn",
|
||||
{
|
||||
"ignoreComments": true,
|
||||
"ignoreTrailingComments": true,
|
||||
"ignoreUrls": true,
|
||||
"code": 200
|
||||
}
|
||||
],
|
||||
"indent": ["warn", 4],
|
||||
"no-unused-vars": ["warn"],
|
||||
"no-extra-semi": ["warn"],
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"quotes": ["warn", "double"],
|
||||
"semi": ["error", "always"]
|
||||
}
|
||||
}
|
5
examples/nodeproj/.gitignore
vendored
5
examples/nodeproj/.gitignore
vendored
@ -1,5 +0,0 @@
|
||||
local.env
|
||||
.env
|
||||
*.pid
|
||||
node_modules
|
||||
|
1
examples/nodeproj/.home/.gitignore
vendored
1
examples/nodeproj/.home/.gitignore
vendored
@ -1 +0,0 @@
|
||||
*
|
@ -1,16 +0,0 @@
|
||||
# How to run example
|
||||
|
||||
|
||||
|
||||
```
|
||||
cp example.local.env local.env
|
||||
cp example.env .env
|
||||
cat local.env
|
||||
cat .env
|
||||
echo "UID=$UID" >> .env
|
||||
cat .env
|
||||
podman-compose build
|
||||
podman-compose run --rm --no-deps init
|
||||
podman-compose up
|
||||
```
|
||||
|
@ -1,12 +0,0 @@
|
||||
FROM registry.fedoraproject.org/fedora-minimal:35
|
||||
ARG NODE_VER=16
|
||||
# microdnf -y module enable nodejs:${NODE_VER}
|
||||
RUN \
|
||||
echo -e "[nodejs]\nname=nodejs\nstream=${NODE_VER}\nprofiles=\nstate=enabled\n" > /etc/dnf/modules.d/nodejs.module && \
|
||||
microdnf -y install shadow-utils nodejs zopfli findutils busybox && \
|
||||
microdnf clean all
|
||||
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
|
||||
ENV XDG_CONFIG_HOME=/app/code/.home
|
||||
ENV HOME=/app/code/.home
|
||||
WORKDIR /app/code
|
||||
|
@ -1,48 +0,0 @@
|
||||
version: '3'
|
||||
volumes:
|
||||
redis:
|
||||
services:
|
||||
redis:
|
||||
read_only: true
|
||||
image: docker.io/redis:alpine
|
||||
command: ["redis-server", "--appendonly", "yes", "--notify-keyspace-events", "Ex"]
|
||||
volumes:
|
||||
- redis:/data
|
||||
tmpfs:
|
||||
- /tmp
|
||||
- /var/run
|
||||
- /run
|
||||
init:
|
||||
read_only: true
|
||||
#userns_mode: keep-id
|
||||
user: ${UID:-1000}
|
||||
build:
|
||||
context: ./containers/${NODE_IMG:-node16-runtime}
|
||||
image: ${NODE_IMG:-node16-runtime}
|
||||
env_file:
|
||||
- local.env
|
||||
volumes:
|
||||
- .:/app/code
|
||||
command: ["/bin/sh", "-c", "mkdir -p ~/; [ -d ./node_modules ] && echo '** node_modules exists' || npm install"]
|
||||
tmpfs:
|
||||
- /tmp
|
||||
- /run
|
||||
task:
|
||||
extends:
|
||||
service: init
|
||||
command: ["npm", "run", "cli", "--", "task"]
|
||||
links:
|
||||
- redis
|
||||
depends_on:
|
||||
- redis
|
||||
web:
|
||||
extends:
|
||||
service: init
|
||||
command: ["npm", "run", "cli", "--", "web"]
|
||||
ports:
|
||||
- ${WEB_LISTEN_PORT:-3000}:3000
|
||||
depends_on:
|
||||
- redis
|
||||
links:
|
||||
- mongo
|
||||
|
@ -1,3 +0,0 @@
|
||||
WEB_LISTEN_PORT=3000
|
||||
# pass UID= your IDE user
|
||||
|
@ -1,2 +0,0 @@
|
||||
REDIS_HOST=redis
|
||||
|
@ -1,6 +0,0 @@
|
||||
#! /usr/bin/env node
|
||||
"use strict";
|
||||
import {start} from "./lib";
|
||||
|
||||
start();
|
||||
|
@ -1,14 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2020",
|
||||
"module": "es2020",
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"files": [
|
||||
"index.js"
|
||||
],
|
||||
"include": [
|
||||
"lib/**/*.js"
|
||||
]
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
"use strict";
|
||||
import {proj} from "../proj";
|
||||
|
||||
async function loop() {
|
||||
const poped = await proj.predis.blpop("queue", 5);
|
||||
const task_desc_s = poped[1];
|
||||
let task_desc;
|
||||
try {
|
||||
task_desc = JSON.parse(task_desc_s);
|
||||
} catch (e) {
|
||||
console.exception(e);
|
||||
}
|
||||
console.info("got task "+task_desc.func);
|
||||
const func = task_desc.func;
|
||||
const args = task_desc.args;
|
||||
if (typeof(proj.tasks[func])!="function") {
|
||||
console.log(`task ${func} not found`);
|
||||
process.exit(-1)
|
||||
}
|
||||
try {
|
||||
await ((this.tasks[func])(...args));
|
||||
} catch (e) {
|
||||
console.exception(e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function start() {
|
||||
while(true) {
|
||||
loop();
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
"use strict";
|
||||
import {proj} from "../proj";
|
||||
|
||||
import http from "http";
|
||||
import express from "express";
|
||||
|
||||
|
||||
export async function start() {
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
|
||||
// Routing
|
||||
app.use(express.static(proj.config.basedir + "/public"));
|
||||
app.get("/healthz", function(req, res) {
|
||||
res.send("ok@"+Date.now());
|
||||
});
|
||||
|
||||
server.listen(proj.config.LISTEN_PORT, proj.config.LISTEN_HOST, function() {
|
||||
console.warn(`listening at port ${proj.config.LISTEN_PORT}`);
|
||||
});
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
{
|
||||
"name": "nodeproj",
|
||||
"version": "0.0.1",
|
||||
"description": "nodejs example project",
|
||||
"exports": {
|
||||
".": "./index.js",
|
||||
"./lib": "./lib"
|
||||
},
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"cli": "nodemon -w lib -w index.js --es-module-specifier-resolution=node ./index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "~4.16.4",
|
||||
"redis": "^3.1.2"
|
||||
},
|
||||
"private": true,
|
||||
"author": "",
|
||||
"license": "proprietary",
|
||||
"devDependencies": {
|
||||
"nodemon": "^2.0.14"
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Vote</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/browse/normalize.css@8.0.1/normalize.css">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>This is a Heading</h1>
|
||||
<p>This is a paragraph.</p>
|
||||
</body>
|
||||
<script type="text/javascript" src="main.css"></script>
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
console.log("loaded");
|
||||
//]]>
|
||||
</script>
|
||||
</html>
|
@ -1,24 +0,0 @@
|
||||
---
|
||||
volumes:
|
||||
db_data:
|
||||
services:
|
||||
wordpress:
|
||||
image: docker.io/library/wordpress:latest
|
||||
ports:
|
||||
- 8080:80
|
||||
environment:
|
||||
- WORDPRESS_DB_HOST=db
|
||||
- WORDPRESS_DB_USER=wordpress
|
||||
- WORDPRESS_DB_PASSWORD=password
|
||||
- WORDPRESS_DB_NAME=wordpress
|
||||
db:
|
||||
image: docker.io/library/mariadb:10.6.4-focal
|
||||
command: '--default-authentication-plugin=mysql_native_password'
|
||||
volumes:
|
||||
- db_data:/var/lib/mysql
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=somewordpress
|
||||
- MYSQL_DATABASE=wordpress
|
||||
- MYSQL_USER=wordpress
|
||||
- MYSQL_PASSWORD=password
|
||||
|
3371
podman_compose.py
3371
podman_compose.py
File diff suppressed because it is too large
Load Diff
@ -1,15 +0,0 @@
|
||||
[tool.ruff]
|
||||
line-length = 100
|
||||
target-version = "py38"
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["W", "E", "F", "I"]
|
||||
ignore = [
|
||||
]
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
force-single-line = true
|
||||
|
||||
[tool.ruff.format]
|
||||
preview = true # needed for quote-style
|
||||
quote-style = "preserve"
|
@ -1,143 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import unittest
|
||||
|
||||
import yaml
|
||||
from parameterized import parameterized
|
||||
|
||||
from podman_compose import PodmanCompose
|
||||
from podman_compose import normalize_service
|
||||
|
||||
|
||||
class TestCanMergeBuild(unittest.TestCase):
|
||||
@parameterized.expand([
|
||||
({"test": "test"}, {"test": "test"}),
|
||||
({"build": "."}, {"build": {"context": "."}}),
|
||||
({"build": "./dir-1"}, {"build": {"context": "./dir-1"}}),
|
||||
({"build": {"context": "./dir-1"}}, {"build": {"context": "./dir-1"}}),
|
||||
(
|
||||
{"build": {"dockerfile": "dockerfile-1"}},
|
||||
{"build": {"dockerfile": "dockerfile-1"}},
|
||||
),
|
||||
(
|
||||
{"build": {"context": "./dir-1", "dockerfile": "dockerfile-1"}},
|
||||
{"build": {"context": "./dir-1", "dockerfile": "dockerfile-1"}},
|
||||
),
|
||||
])
|
||||
def test_simple(self, input, expected):
|
||||
self.assertEqual(normalize_service(input), expected)
|
||||
|
||||
@parameterized.expand([
|
||||
({"test": "test"}, {"test": "test"}),
|
||||
({"build": "."}, {"build": {"context": "./sub_dir/."}}),
|
||||
({"build": "./dir-1"}, {"build": {"context": "./sub_dir/dir-1"}}),
|
||||
({"build": {"context": "./dir-1"}}, {"build": {"context": "./sub_dir/dir-1"}}),
|
||||
(
|
||||
{"build": {"dockerfile": "dockerfile-1"}},
|
||||
{"build": {"context": "./sub_dir", "dockerfile": "dockerfile-1"}},
|
||||
),
|
||||
(
|
||||
{"build": {"context": "./dir-1", "dockerfile": "dockerfile-1"}},
|
||||
{"build": {"context": "./sub_dir/dir-1", "dockerfile": "dockerfile-1"}},
|
||||
),
|
||||
])
|
||||
def test_normalize_service_with_sub_dir(self, input, expected):
|
||||
self.assertEqual(normalize_service(input, sub_dir="./sub_dir"), expected)
|
||||
|
||||
@parameterized.expand([
|
||||
({}, {}, {}),
|
||||
({}, {"test": "test"}, {"test": "test"}),
|
||||
({"test": "test"}, {}, {"test": "test"}),
|
||||
({"test": "test-1"}, {"test": "test-2"}, {"test": "test-2"}),
|
||||
({}, {"build": "."}, {"build": {"context": "."}}),
|
||||
({"build": "."}, {}, {"build": {"context": "."}}),
|
||||
({"build": "./dir-1"}, {"build": "./dir-2"}, {"build": {"context": "./dir-2"}}),
|
||||
({}, {"build": {"context": "./dir-1"}}, {"build": {"context": "./dir-1"}}),
|
||||
({"build": {"context": "./dir-1"}}, {}, {"build": {"context": "./dir-1"}}),
|
||||
(
|
||||
{"build": {"context": "./dir-1"}},
|
||||
{"build": {"context": "./dir-2"}},
|
||||
{"build": {"context": "./dir-2"}},
|
||||
),
|
||||
(
|
||||
{},
|
||||
{"build": {"dockerfile": "dockerfile-1"}},
|
||||
{"build": {"dockerfile": "dockerfile-1"}},
|
||||
),
|
||||
(
|
||||
{"build": {"dockerfile": "dockerfile-1"}},
|
||||
{},
|
||||
{"build": {"dockerfile": "dockerfile-1"}},
|
||||
),
|
||||
(
|
||||
{"build": {"dockerfile": "./dockerfile-1"}},
|
||||
{"build": {"dockerfile": "./dockerfile-2"}},
|
||||
{"build": {"dockerfile": "./dockerfile-2"}},
|
||||
),
|
||||
(
|
||||
{"build": {"dockerfile": "./dockerfile-1"}},
|
||||
{"build": {"context": "./dir-2"}},
|
||||
{"build": {"dockerfile": "./dockerfile-1", "context": "./dir-2"}},
|
||||
),
|
||||
(
|
||||
{"build": {"dockerfile": "./dockerfile-1", "context": "./dir-1"}},
|
||||
{"build": {"dockerfile": "./dockerfile-2", "context": "./dir-2"}},
|
||||
{"build": {"dockerfile": "./dockerfile-2", "context": "./dir-2"}},
|
||||
),
|
||||
(
|
||||
{"build": {"dockerfile": "./dockerfile-1"}},
|
||||
{"build": {"dockerfile": "./dockerfile-2", "args": ["ENV1=1"]}},
|
||||
{"build": {"dockerfile": "./dockerfile-2", "args": ["ENV1=1"]}},
|
||||
),
|
||||
(
|
||||
{"build": {"dockerfile": "./dockerfile-2", "args": ["ENV1=1"]}},
|
||||
{"build": {"dockerfile": "./dockerfile-1"}},
|
||||
{"build": {"dockerfile": "./dockerfile-1", "args": ["ENV1=1"]}},
|
||||
),
|
||||
(
|
||||
{"build": {"dockerfile": "./dockerfile-2", "args": ["ENV1=1"]}},
|
||||
{"build": {"dockerfile": "./dockerfile-1", "args": ["ENV2=2"]}},
|
||||
{"build": {"dockerfile": "./dockerfile-1", "args": ["ENV1=1", "ENV2=2"]}},
|
||||
),
|
||||
])
|
||||
def test_parse_compose_file_when_multiple_composes(self, input, override, expected):
|
||||
compose_test_1 = {"services": {"test-service": input}}
|
||||
compose_test_2 = {"services": {"test-service": override}}
|
||||
dump_yaml(compose_test_1, "test-compose-1.yaml")
|
||||
dump_yaml(compose_test_2, "test-compose-2.yaml")
|
||||
|
||||
podman_compose = PodmanCompose()
|
||||
set_args(podman_compose, ["test-compose-1.yaml", "test-compose-2.yaml"])
|
||||
|
||||
podman_compose._parse_compose_file() # pylint: disable=protected-access
|
||||
|
||||
actual_compose = {}
|
||||
if podman_compose.services:
|
||||
podman_compose.services["test-service"].pop("_deps")
|
||||
actual_compose = podman_compose.services["test-service"]
|
||||
self.assertEqual(actual_compose, expected)
|
||||
|
||||
|
||||
def set_args(podman_compose: PodmanCompose, file_names: list[str]) -> None:
|
||||
podman_compose.global_args = argparse.Namespace()
|
||||
podman_compose.global_args.file = file_names
|
||||
podman_compose.global_args.project_name = None
|
||||
podman_compose.global_args.env_file = None
|
||||
podman_compose.global_args.profile = []
|
||||
podman_compose.global_args.in_pod = True
|
||||
podman_compose.global_args.no_normalize = True
|
||||
|
||||
|
||||
def dump_yaml(compose: dict, name: str) -> None:
|
||||
with open(name, "w", encoding="utf-8") as outfile:
|
||||
yaml.safe_dump(compose, outfile, default_flow_style=False)
|
||||
|
||||
|
||||
def test_clean_test_yamls() -> None:
|
||||
test_files = ["test-compose-1.yaml", "test-compose-2.yaml"]
|
||||
for file in test_files:
|
||||
if os.path.exists(file):
|
||||
os.remove(file)
|
@ -1,115 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import copy
|
||||
import os
|
||||
import unittest
|
||||
|
||||
import yaml
|
||||
from parameterized import parameterized
|
||||
|
||||
from podman_compose import PodmanCompose
|
||||
from podman_compose import normalize_service
|
||||
|
||||
test_keys = ["command", "entrypoint"]
|
||||
|
||||
|
||||
class TestCanMergeCmdEnt(unittest.TestCase):
|
||||
@parameterized.expand([
|
||||
({"$$$": []}, {"$$$": []}),
|
||||
({"$$$": ["sh"]}, {"$$$": ["sh"]}),
|
||||
({"$$$": ["sh", "-c", "date"]}, {"$$$": ["sh", "-c", "date"]}),
|
||||
({"$$$": "sh"}, {"$$$": ["sh"]}),
|
||||
({"$$$": "sleep infinity"}, {"$$$": ["sleep", "infinity"]}),
|
||||
(
|
||||
{"$$$": "bash -c 'sleep infinity'"},
|
||||
{"$$$": ["bash", "-c", "sleep infinity"]},
|
||||
),
|
||||
])
|
||||
def test_normalize_service(self, input_template, expected_template):
|
||||
for key in test_keys:
|
||||
test_input, _, expected = template_to_expression(
|
||||
input_template, {}, expected_template, key
|
||||
)
|
||||
self.assertEqual(normalize_service(test_input), expected)
|
||||
|
||||
@parameterized.expand([
|
||||
({}, {"$$$": []}, {"$$$": []}),
|
||||
({"$$$": []}, {}, {"$$$": []}),
|
||||
({"$$$": []}, {"$$$": "sh-2"}, {"$$$": ["sh-2"]}),
|
||||
({"$$$": "sh-2"}, {"$$$": []}, {"$$$": []}),
|
||||
({}, {"$$$": "sh"}, {"$$$": ["sh"]}),
|
||||
({"$$$": "sh"}, {}, {"$$$": ["sh"]}),
|
||||
({"$$$": "sh-1"}, {"$$$": "sh-2"}, {"$$$": ["sh-2"]}),
|
||||
({"$$$": ["sh-1"]}, {"$$$": "sh-2"}, {"$$$": ["sh-2"]}),
|
||||
({"$$$": "sh-1"}, {"$$$": ["sh-2"]}, {"$$$": ["sh-2"]}),
|
||||
({"$$$": "sh-1"}, {"$$$": ["sh-2", "sh-3"]}, {"$$$": ["sh-2", "sh-3"]}),
|
||||
({"$$$": ["sh-1"]}, {"$$$": ["sh-2", "sh-3"]}, {"$$$": ["sh-2", "sh-3"]}),
|
||||
({"$$$": ["sh-1", "sh-2"]}, {"$$$": ["sh-3", "sh-4"]}, {"$$$": ["sh-3", "sh-4"]}),
|
||||
({}, {"$$$": ["sh-3", "sh 4"]}, {"$$$": ["sh-3", "sh 4"]}),
|
||||
({"$$$": "sleep infinity"}, {"$$$": "sh"}, {"$$$": ["sh"]}),
|
||||
({"$$$": "sh"}, {"$$$": "sleep infinity"}, {"$$$": ["sleep", "infinity"]}),
|
||||
(
|
||||
{},
|
||||
{"$$$": "bash -c 'sleep infinity'"},
|
||||
{"$$$": ["bash", "-c", "sleep infinity"]},
|
||||
),
|
||||
])
|
||||
def test_parse_compose_file_when_multiple_composes(
|
||||
self, base_template, override_template, expected_template
|
||||
):
|
||||
for key in test_keys:
|
||||
base, override, expected = template_to_expression(
|
||||
base_template, override_template, expected_template, key
|
||||
)
|
||||
compose_test_1 = {"services": {"test-service": base}}
|
||||
compose_test_2 = {"services": {"test-service": override}}
|
||||
dump_yaml(compose_test_1, "test-compose-1.yaml")
|
||||
dump_yaml(compose_test_2, "test-compose-2.yaml")
|
||||
|
||||
podman_compose = PodmanCompose()
|
||||
set_args(podman_compose, ["test-compose-1.yaml", "test-compose-2.yaml"])
|
||||
|
||||
podman_compose._parse_compose_file() # pylint: disable=protected-access
|
||||
|
||||
actual = {}
|
||||
if podman_compose.services:
|
||||
podman_compose.services["test-service"].pop("_deps")
|
||||
actual = podman_compose.services["test-service"]
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
|
||||
def template_to_expression(base, override, expected, key):
|
||||
base_copy = copy.deepcopy(base)
|
||||
override_copy = copy.deepcopy(override)
|
||||
expected_copy = copy.deepcopy(expected)
|
||||
|
||||
expected_copy[key] = expected_copy.pop("$$$")
|
||||
if "$$$" in base:
|
||||
base_copy[key] = base_copy.pop("$$$")
|
||||
if "$$$" in override:
|
||||
override_copy[key] = override_copy.pop("$$$")
|
||||
return base_copy, override_copy, expected_copy
|
||||
|
||||
|
||||
def set_args(podman_compose: PodmanCompose, file_names: list[str]) -> None:
|
||||
podman_compose.global_args = argparse.Namespace()
|
||||
podman_compose.global_args.file = file_names
|
||||
podman_compose.global_args.project_name = None
|
||||
podman_compose.global_args.env_file = None
|
||||
podman_compose.global_args.profile = []
|
||||
podman_compose.global_args.in_pod = True
|
||||
podman_compose.global_args.no_normalize = None
|
||||
|
||||
|
||||
def dump_yaml(compose: dict, name: str) -> None:
|
||||
with open(name, "w", encoding="utf-8") as outfile:
|
||||
yaml.safe_dump(compose, outfile, default_flow_style=False)
|
||||
|
||||
|
||||
def test_clean_test_yamls() -> None:
|
||||
test_files = ["test-compose-1.yaml", "test-compose-2.yaml"]
|
||||
for file in test_files:
|
||||
if os.path.exists(file):
|
||||
os.remove(file)
|
@ -1,46 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
import argparse
|
||||
import unittest
|
||||
|
||||
from podman_compose import compose_exec_args
|
||||
|
||||
|
||||
class TestComposeExecArgs(unittest.TestCase):
|
||||
def test_minimal(self):
|
||||
cnt = get_minimal_container()
|
||||
args = get_minimal_args()
|
||||
|
||||
result = compose_exec_args(cnt, "container_name", args)
|
||||
expected = ["--interactive", "--tty", "container_name"]
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_additional_env_value_equals(self):
|
||||
cnt = get_minimal_container()
|
||||
args = get_minimal_args()
|
||||
args.env = ["key=valuepart1=valuepart2"]
|
||||
|
||||
result = compose_exec_args(cnt, "container_name", args)
|
||||
expected = [
|
||||
"--interactive",
|
||||
"--tty",
|
||||
"--env",
|
||||
"key=valuepart1=valuepart2",
|
||||
"container_name",
|
||||
]
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
|
||||
def get_minimal_container():
|
||||
return {}
|
||||
|
||||
|
||||
def get_minimal_args():
|
||||
return argparse.Namespace(
|
||||
T=None,
|
||||
cnt_command=None,
|
||||
env=None,
|
||||
privileged=None,
|
||||
user=None,
|
||||
workdir=None,
|
||||
)
|
@ -1,60 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
import argparse
|
||||
import unittest
|
||||
|
||||
from podman_compose import PodmanCompose
|
||||
from podman_compose import compose_run_update_container_from_args
|
||||
|
||||
|
||||
class TestComposeRunUpdateContainerFromArgs(unittest.TestCase):
|
||||
def test_minimal(self):
|
||||
cnt = get_minimal_container()
|
||||
compose = get_minimal_compose()
|
||||
args = get_minimal_args()
|
||||
|
||||
compose_run_update_container_from_args(compose, cnt, args)
|
||||
|
||||
expected_cnt = {"name": "default_name", "tty": True}
|
||||
self.assertEqual(cnt, expected_cnt)
|
||||
|
||||
def test_additional_env_value_equals(self):
|
||||
cnt = get_minimal_container()
|
||||
compose = get_minimal_compose()
|
||||
args = get_minimal_args()
|
||||
args.env = ["key=valuepart1=valuepart2"]
|
||||
|
||||
compose_run_update_container_from_args(compose, cnt, args)
|
||||
|
||||
expected_cnt = {
|
||||
"environment": {
|
||||
"key": "valuepart1=valuepart2",
|
||||
},
|
||||
"name": "default_name",
|
||||
"tty": True,
|
||||
}
|
||||
self.assertEqual(cnt, expected_cnt)
|
||||
|
||||
|
||||
def get_minimal_container():
|
||||
return {}
|
||||
|
||||
|
||||
def get_minimal_compose():
|
||||
return PodmanCompose()
|
||||
|
||||
|
||||
def get_minimal_args():
|
||||
return argparse.Namespace(
|
||||
T=None,
|
||||
cnt_command=None,
|
||||
entrypoint=None,
|
||||
env=None,
|
||||
name="default_name",
|
||||
rm=None,
|
||||
service=None,
|
||||
service_ports=None,
|
||||
user=None,
|
||||
volume=None,
|
||||
workdir=None,
|
||||
)
|
@ -1,163 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
from podman_compose import container_to_args
|
||||
|
||||
|
||||
def create_compose_mock(project_name="test_project_name"):
|
||||
compose = mock.Mock()
|
||||
compose.project_name = project_name
|
||||
compose.dirname = "test_dirname"
|
||||
compose.container_names_by_service.get = mock.Mock(return_value=None)
|
||||
compose.prefer_volume_over_mount = False
|
||||
compose.default_net = None
|
||||
compose.networks = {}
|
||||
return compose
|
||||
|
||||
|
||||
def get_minimal_container():
|
||||
return {
|
||||
"name": "project_name_service_name1",
|
||||
"service_name": "service_name",
|
||||
"image": "busybox",
|
||||
}
|
||||
|
||||
|
||||
class TestContainerToArgs(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_minimal(self):
|
||||
c = create_compose_mock()
|
||||
|
||||
cnt = get_minimal_container()
|
||||
|
||||
args = await container_to_args(c, cnt)
|
||||
self.assertEqual(
|
||||
args,
|
||||
[
|
||||
"--name=project_name_service_name1",
|
||||
"-d",
|
||||
"--network=bridge",
|
||||
"--network-alias=service_name",
|
||||
"busybox",
|
||||
],
|
||||
)
|
||||
|
||||
async def test_runtime(self):
|
||||
c = create_compose_mock()
|
||||
|
||||
cnt = get_minimal_container()
|
||||
cnt["runtime"] = "runsc"
|
||||
|
||||
args = await container_to_args(c, cnt)
|
||||
self.assertEqual(
|
||||
args,
|
||||
[
|
||||
"--name=project_name_service_name1",
|
||||
"-d",
|
||||
"--network=bridge",
|
||||
"--network-alias=service_name",
|
||||
"--runtime",
|
||||
"runsc",
|
||||
"busybox",
|
||||
],
|
||||
)
|
||||
|
||||
async def test_sysctl_list(self):
|
||||
c = create_compose_mock()
|
||||
|
||||
cnt = get_minimal_container()
|
||||
cnt["sysctls"] = [
|
||||
"net.core.somaxconn=1024",
|
||||
"net.ipv4.tcp_syncookies=0",
|
||||
]
|
||||
|
||||
args = await container_to_args(c, cnt)
|
||||
self.assertEqual(
|
||||
args,
|
||||
[
|
||||
"--name=project_name_service_name1",
|
||||
"-d",
|
||||
"--network=bridge",
|
||||
"--network-alias=service_name",
|
||||
"--sysctl",
|
||||
"net.core.somaxconn=1024",
|
||||
"--sysctl",
|
||||
"net.ipv4.tcp_syncookies=0",
|
||||
"busybox",
|
||||
],
|
||||
)
|
||||
|
||||
async def test_sysctl_map(self):
|
||||
c = create_compose_mock()
|
||||
|
||||
cnt = get_minimal_container()
|
||||
cnt["sysctls"] = {
|
||||
"net.core.somaxconn": 1024,
|
||||
"net.ipv4.tcp_syncookies": 0,
|
||||
}
|
||||
|
||||
args = await container_to_args(c, cnt)
|
||||
self.assertEqual(
|
||||
args,
|
||||
[
|
||||
"--name=project_name_service_name1",
|
||||
"-d",
|
||||
"--network=bridge",
|
||||
"--network-alias=service_name",
|
||||
"--sysctl",
|
||||
"net.core.somaxconn=1024",
|
||||
"--sysctl",
|
||||
"net.ipv4.tcp_syncookies=0",
|
||||
"busybox",
|
||||
],
|
||||
)
|
||||
|
||||
async def test_sysctl_wrong_type(self):
|
||||
c = create_compose_mock()
|
||||
cnt = get_minimal_container()
|
||||
|
||||
# check whether wrong types are correctly rejected
|
||||
for wrong_type in [True, 0, 0.0, "wrong", ()]:
|
||||
with self.assertRaises(TypeError):
|
||||
cnt["sysctls"] = wrong_type
|
||||
await container_to_args(c, cnt)
|
||||
|
||||
async def test_pid(self):
|
||||
c = create_compose_mock()
|
||||
cnt = get_minimal_container()
|
||||
|
||||
cnt["pid"] = "host"
|
||||
|
||||
args = await container_to_args(c, cnt)
|
||||
self.assertEqual(
|
||||
args,
|
||||
[
|
||||
"--name=project_name_service_name1",
|
||||
"-d",
|
||||
"--network=bridge",
|
||||
"--network-alias=service_name",
|
||||
"--pid",
|
||||
"host",
|
||||
"busybox",
|
||||
],
|
||||
)
|
||||
|
||||
async def test_http_proxy(self):
|
||||
c = create_compose_mock()
|
||||
|
||||
cnt = get_minimal_container()
|
||||
cnt["http_proxy"] = False
|
||||
|
||||
args = await container_to_args(c, cnt)
|
||||
self.assertEqual(
|
||||
args,
|
||||
[
|
||||
"--name=project_name_service_name1",
|
||||
"-d",
|
||||
"--http-proxy=false",
|
||||
"--network=bridge",
|
||||
"--network-alias=service_name",
|
||||
"busybox",
|
||||
],
|
||||
)
|
@ -1,298 +0,0 @@
|
||||
import unittest
|
||||
|
||||
from parameterized import parameterized
|
||||
|
||||
from podman_compose import get_net_args
|
||||
|
||||
from .test_container_to_args import create_compose_mock
|
||||
|
||||
PROJECT_NAME = "test_project_name"
|
||||
SERVICE_NAME = "service_name"
|
||||
CONTAINER_NAME = f"{PROJECT_NAME}_{SERVICE_NAME}_1"
|
||||
|
||||
|
||||
def get_networked_compose(num_networks=1):
|
||||
compose = create_compose_mock(PROJECT_NAME)
|
||||
for network in range(num_networks):
|
||||
compose.networks[f"net{network}"] = {
|
||||
"driver": "bridge",
|
||||
"ipam": {
|
||||
"config": [
|
||||
{"subnet": f"192.168.{network}.0/24"},
|
||||
{"subnet": f"fd00:{network}::/64"},
|
||||
]
|
||||
},
|
||||
"enable_ipv6": True,
|
||||
}
|
||||
|
||||
return compose
|
||||
|
||||
|
||||
def get_minimal_container():
|
||||
return {
|
||||
"name": CONTAINER_NAME,
|
||||
"service_name": SERVICE_NAME,
|
||||
"image": "busybox",
|
||||
}
|
||||
|
||||
|
||||
class TestGetNetArgs(unittest.TestCase):
|
||||
def test_minimal(self):
|
||||
compose = get_networked_compose()
|
||||
container = get_minimal_container()
|
||||
|
||||
expected_args = [
|
||||
"--network=bridge",
|
||||
f"--network-alias={SERVICE_NAME}",
|
||||
]
|
||||
args = get_net_args(compose, container)
|
||||
self.assertListEqual(expected_args, args)
|
||||
|
||||
def test_one_net(self):
|
||||
compose = get_networked_compose()
|
||||
container = get_minimal_container()
|
||||
container["networks"] = {"net0": {}}
|
||||
|
||||
expected_args = [
|
||||
f"--network={PROJECT_NAME}_net0",
|
||||
f"--network-alias={SERVICE_NAME}",
|
||||
]
|
||||
args = get_net_args(compose, container)
|
||||
self.assertListEqual(expected_args, args)
|
||||
|
||||
def test_alias(self):
|
||||
compose = get_networked_compose()
|
||||
container = get_minimal_container()
|
||||
container["networks"] = {"net0": {}}
|
||||
container["_aliases"] = ["alias1", "alias2"]
|
||||
|
||||
expected_args = [
|
||||
f"--network={PROJECT_NAME}_net0",
|
||||
f"--network-alias={SERVICE_NAME}",
|
||||
"--network-alias=alias1",
|
||||
"--network-alias=alias2",
|
||||
]
|
||||
args = get_net_args(compose, container)
|
||||
self.assertListEqual(expected_args, args)
|
||||
|
||||
def test_one_ipv4(self):
|
||||
ip = "192.168.0.42"
|
||||
compose = get_networked_compose()
|
||||
container = get_minimal_container()
|
||||
container["networks"] = {"net0": {"ipv4_address": ip}}
|
||||
|
||||
expected_args = [
|
||||
f"--network={PROJECT_NAME}_net0",
|
||||
f"--ip={ip}",
|
||||
f"--network-alias={SERVICE_NAME}",
|
||||
]
|
||||
args = get_net_args(compose, container)
|
||||
self.assertEqual(expected_args, args)
|
||||
|
||||
def test_one_ipv6(self):
|
||||
ipv6_address = "fd00:0::42"
|
||||
compose = get_networked_compose()
|
||||
container = get_minimal_container()
|
||||
container["networks"] = {"net0": {"ipv6_address": ipv6_address}}
|
||||
|
||||
expected_args = [
|
||||
f"--network={PROJECT_NAME}_net0",
|
||||
f"--ip6={ipv6_address}",
|
||||
f"--network-alias={SERVICE_NAME}",
|
||||
]
|
||||
args = get_net_args(compose, container)
|
||||
self.assertListEqual(expected_args, args)
|
||||
|
||||
def test_one_mac(self):
|
||||
mac = "00:11:22:33:44:55"
|
||||
compose = get_networked_compose()
|
||||
container = get_minimal_container()
|
||||
container["networks"] = {"net0": {}}
|
||||
container["mac_address"] = mac
|
||||
|
||||
expected_args = [
|
||||
f"--network={PROJECT_NAME}_net0",
|
||||
f"--mac-address={mac}",
|
||||
f"--network-alias={SERVICE_NAME}",
|
||||
]
|
||||
args = get_net_args(compose, container)
|
||||
self.assertListEqual(expected_args, args)
|
||||
|
||||
def test_one_mac_two_nets(self):
|
||||
mac = "00:11:22:33:44:55"
|
||||
compose = get_networked_compose(num_networks=6)
|
||||
container = get_minimal_container()
|
||||
container["networks"] = {"net0": {}, "net1": {}}
|
||||
container["mac_address"] = mac
|
||||
|
||||
expected_args = [
|
||||
f"--network={PROJECT_NAME}_net0:mac={mac}",
|
||||
f"--network={PROJECT_NAME}_net1",
|
||||
f"--network-alias={SERVICE_NAME}",
|
||||
]
|
||||
args = get_net_args(compose, container)
|
||||
self.assertListEqual(expected_args, args)
|
||||
|
||||
def test_two_nets_as_dict(self):
|
||||
compose = get_networked_compose(num_networks=2)
|
||||
container = get_minimal_container()
|
||||
container["networks"] = {"net0": {}, "net1": {}}
|
||||
|
||||
expected_args = [
|
||||
f"--network={PROJECT_NAME}_net0",
|
||||
f"--network={PROJECT_NAME}_net1",
|
||||
f"--network-alias={SERVICE_NAME}",
|
||||
]
|
||||
args = get_net_args(compose, container)
|
||||
self.assertListEqual(expected_args, args)
|
||||
|
||||
def test_two_nets_as_list(self):
|
||||
compose = get_networked_compose(num_networks=2)
|
||||
container = get_minimal_container()
|
||||
container["networks"] = ["net0", "net1"]
|
||||
|
||||
expected_args = [
|
||||
f"--network={PROJECT_NAME}_net0",
|
||||
f"--network={PROJECT_NAME}_net1",
|
||||
f"--network-alias={SERVICE_NAME}",
|
||||
]
|
||||
args = get_net_args(compose, container)
|
||||
self.assertListEqual(expected_args, args)
|
||||
|
||||
def test_two_ipv4(self):
|
||||
ip0 = "192.168.0.42"
|
||||
ip1 = "192.168.1.42"
|
||||
compose = get_networked_compose(num_networks=2)
|
||||
container = get_minimal_container()
|
||||
container["networks"] = {"net0": {"ipv4_address": ip0}, "net1": {"ipv4_address": ip1}}
|
||||
|
||||
expected_args = [
|
||||
f"--network={PROJECT_NAME}_net0:ip={ip0}",
|
||||
f"--network={PROJECT_NAME}_net1:ip={ip1}",
|
||||
f"--network-alias={SERVICE_NAME}",
|
||||
]
|
||||
args = get_net_args(compose, container)
|
||||
self.assertListEqual(expected_args, args)
|
||||
|
||||
def test_two_ipv6(self):
|
||||
ip0 = "fd00:0::42"
|
||||
ip1 = "fd00:1::42"
|
||||
compose = get_networked_compose(num_networks=2)
|
||||
container = get_minimal_container()
|
||||
container["networks"] = {"net0": {"ipv6_address": ip0}, "net1": {"ipv6_address": ip1}}
|
||||
|
||||
expected_args = [
|
||||
f"--network={PROJECT_NAME}_net0:ip={ip0}",
|
||||
f"--network={PROJECT_NAME}_net1:ip={ip1}",
|
||||
f"--network-alias={SERVICE_NAME}",
|
||||
]
|
||||
args = get_net_args(compose, container)
|
||||
self.assertListEqual(expected_args, args)
|
||||
|
||||
# custom extension; not supported by docker-compose
|
||||
def test_two_mac(self):
|
||||
mac0 = "00:00:00:00:00:01"
|
||||
mac1 = "00:00:00:00:00:02"
|
||||
compose = get_networked_compose(num_networks=2)
|
||||
container = get_minimal_container()
|
||||
container["networks"] = {
|
||||
"net0": {"x-podman.mac_address": mac0},
|
||||
"net1": {"x-podman.mac_address": mac1},
|
||||
}
|
||||
|
||||
expected_args = [
|
||||
f"--network={PROJECT_NAME}_net0:mac={mac0}",
|
||||
f"--network={PROJECT_NAME}_net1:mac={mac1}",
|
||||
f"--network-alias={SERVICE_NAME}",
|
||||
]
|
||||
args = get_net_args(compose, container)
|
||||
self.assertListEqual(expected_args, args)
|
||||
|
||||
def test_mixed_mac(self):
|
||||
ip4_0 = "192.168.0.42"
|
||||
ip4_1 = "192.168.1.42"
|
||||
ip4_2 = "192.168.2.42"
|
||||
mac_0 = "00:00:00:00:00:01"
|
||||
mac_1 = "00:00:00:00:00:02"
|
||||
|
||||
compose = get_networked_compose(num_networks=3)
|
||||
container = get_minimal_container()
|
||||
container["networks"] = {
|
||||
"net0": {"ipv4_address": ip4_0},
|
||||
"net1": {"ipv4_address": ip4_1, "x-podman.mac_address": mac_0},
|
||||
"net2": {"ipv4_address": ip4_2},
|
||||
}
|
||||
container["mac_address"] = mac_1
|
||||
|
||||
expected_exception = (
|
||||
r"specifying mac_address on both container and network level " r"is not supported"
|
||||
)
|
||||
self.assertRaisesRegex(RuntimeError, expected_exception, get_net_args, compose, container)
|
||||
|
||||
def test_mixed_config(self):
|
||||
ip4_0 = "192.168.0.42"
|
||||
ip4_1 = "192.168.1.42"
|
||||
ip6_0 = "fd00:0::42"
|
||||
ip6_2 = "fd00:2::42"
|
||||
mac = "00:11:22:33:44:55"
|
||||
compose = get_networked_compose(num_networks=4)
|
||||
container = get_minimal_container()
|
||||
container["networks"] = {
|
||||
"net0": {"ipv4_address": ip4_0, "ipv6_address": ip6_0},
|
||||
"net1": {"ipv4_address": ip4_1},
|
||||
"net2": {"ipv6_address": ip6_2},
|
||||
"net3": {},
|
||||
}
|
||||
container["mac_address"] = mac
|
||||
|
||||
expected_args = [
|
||||
f"--network={PROJECT_NAME}_net0:ip={ip4_0},ip={ip6_0},mac={mac}",
|
||||
f"--network={PROJECT_NAME}_net1:ip={ip4_1}",
|
||||
f"--network={PROJECT_NAME}_net2:ip={ip6_2}",
|
||||
f"--network={PROJECT_NAME}_net3",
|
||||
f"--network-alias={SERVICE_NAME}",
|
||||
]
|
||||
args = get_net_args(compose, container)
|
||||
self.assertListEqual(expected_args, args)
|
||||
|
||||
@parameterized.expand([
|
||||
("bridge", ["--network=bridge", f"--network-alias={SERVICE_NAME}"]),
|
||||
("host", ["--network=host"]),
|
||||
("none", []),
|
||||
("slirp4netns", ["--network=slirp4netns"]),
|
||||
("slirp4netns:cidr=10.42.0.0/24", ["--network=slirp4netns:cidr=10.42.0.0/24"]),
|
||||
("private", ["--network=private"]),
|
||||
("pasta", ["--network=pasta"]),
|
||||
("pasta:--ipv4-only,-a,10.0.2.0", ["--network=pasta:--ipv4-only,-a,10.0.2.0"]),
|
||||
("ns:my_namespace", ["--network=ns:my_namespace"]),
|
||||
("container:my_container", ["--network=container:my_container"]),
|
||||
])
|
||||
def test_network_modes(self, network_mode, expected_args):
|
||||
compose = get_networked_compose()
|
||||
container = get_minimal_container()
|
||||
container["network_mode"] = network_mode
|
||||
|
||||
args = get_net_args(compose, container)
|
||||
self.assertListEqual(expected_args, args)
|
||||
|
||||
def test_network_mode_invalid(self):
|
||||
compose = get_networked_compose()
|
||||
container = get_minimal_container()
|
||||
container["network_mode"] = "invalid_mode"
|
||||
|
||||
with self.assertRaises(SystemExit):
|
||||
get_net_args(compose, container)
|
||||
|
||||
def test_network__mode_service(self):
|
||||
compose = get_networked_compose()
|
||||
compose.container_names_by_service = {
|
||||
"service_1": ["container_1"],
|
||||
"service_2": ["container_2"],
|
||||
}
|
||||
|
||||
container = get_minimal_container()
|
||||
container["network_mode"] = "service:service_2"
|
||||
|
||||
expected_args = ["--network=container:container_2"]
|
||||
args = get_net_args(compose, container)
|
||||
self.assertListEqual(expected_args, args)
|
@ -1,167 +0,0 @@
|
||||
import unittest
|
||||
|
||||
from podman_compose import get_network_create_args
|
||||
|
||||
|
||||
class TestGetNetworkCreateArgs(unittest.TestCase):
|
||||
def test_minimal(self):
|
||||
net_desc = {
|
||||
"labels": [],
|
||||
"internal": False,
|
||||
"driver": None,
|
||||
"driver_opts": {},
|
||||
"ipam": {"config": []},
|
||||
"enable_ipv6": False,
|
||||
}
|
||||
proj_name = "test_project"
|
||||
net_name = "test_network"
|
||||
expected_args = [
|
||||
"create",
|
||||
"--label",
|
||||
f"io.podman.compose.project={proj_name}",
|
||||
"--label",
|
||||
f"com.docker.compose.project={proj_name}",
|
||||
net_name,
|
||||
]
|
||||
args = get_network_create_args(net_desc, proj_name, net_name)
|
||||
self.assertEqual(args, expected_args)
|
||||
|
||||
def test_ipv6(self):
|
||||
net_desc = {
|
||||
"labels": [],
|
||||
"internal": False,
|
||||
"driver": None,
|
||||
"driver_opts": {},
|
||||
"ipam": {"config": []},
|
||||
"enable_ipv6": True,
|
||||
}
|
||||
proj_name = "test_project"
|
||||
net_name = "test_network"
|
||||
expected_args = [
|
||||
"create",
|
||||
"--label",
|
||||
f"io.podman.compose.project={proj_name}",
|
||||
"--label",
|
||||
f"com.docker.compose.project={proj_name}",
|
||||
"--ipv6",
|
||||
net_name,
|
||||
]
|
||||
args = get_network_create_args(net_desc, proj_name, net_name)
|
||||
self.assertEqual(args, expected_args)
|
||||
|
||||
def test_bridge(self):
|
||||
net_desc = {
|
||||
"labels": [],
|
||||
"internal": False,
|
||||
"driver": "bridge",
|
||||
"driver_opts": {"opt1": "value1", "opt2": "value2"},
|
||||
"ipam": {"config": []},
|
||||
"enable_ipv6": False,
|
||||
}
|
||||
proj_name = "test_project"
|
||||
net_name = "test_network"
|
||||
expected_args = [
|
||||
"create",
|
||||
"--label",
|
||||
f"io.podman.compose.project={proj_name}",
|
||||
"--label",
|
||||
f"com.docker.compose.project={proj_name}",
|
||||
"--driver",
|
||||
"bridge",
|
||||
"--opt",
|
||||
"opt1=value1",
|
||||
"--opt",
|
||||
"opt2=value2",
|
||||
net_name,
|
||||
]
|
||||
args = get_network_create_args(net_desc, proj_name, net_name)
|
||||
self.assertEqual(args, expected_args)
|
||||
|
||||
def test_ipam_driver(self):
|
||||
net_desc = {
|
||||
"labels": [],
|
||||
"internal": False,
|
||||
"driver": None,
|
||||
"driver_opts": {},
|
||||
"ipam": {
|
||||
"driver": "default",
|
||||
"config": [
|
||||
{
|
||||
"subnet": "192.168.0.0/24",
|
||||
"ip_range": "192.168.0.2/24",
|
||||
"gateway": "192.168.0.1",
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
proj_name = "test_project"
|
||||
net_name = "test_network"
|
||||
expected_args = [
|
||||
"create",
|
||||
"--label",
|
||||
f"io.podman.compose.project={proj_name}",
|
||||
"--label",
|
||||
f"com.docker.compose.project={proj_name}",
|
||||
"--ipam-driver",
|
||||
"default",
|
||||
"--subnet",
|
||||
"192.168.0.0/24",
|
||||
"--ip-range",
|
||||
"192.168.0.2/24",
|
||||
"--gateway",
|
||||
"192.168.0.1",
|
||||
net_name,
|
||||
]
|
||||
args = get_network_create_args(net_desc, proj_name, net_name)
|
||||
self.assertEqual(args, expected_args)
|
||||
|
||||
def test_complete(self):
|
||||
net_desc = {
|
||||
"labels": ["label1", "label2"],
|
||||
"internal": True,
|
||||
"driver": "bridge",
|
||||
"driver_opts": {"opt1": "value1", "opt2": "value2"},
|
||||
"ipam": {
|
||||
"driver": "default",
|
||||
"config": [
|
||||
{
|
||||
"subnet": "192.168.0.0/24",
|
||||
"ip_range": "192.168.0.2/24",
|
||||
"gateway": "192.168.0.1",
|
||||
}
|
||||
],
|
||||
},
|
||||
"enable_ipv6": True,
|
||||
}
|
||||
proj_name = "test_project"
|
||||
net_name = "test_network"
|
||||
expected_args = [
|
||||
"create",
|
||||
"--label",
|
||||
f"io.podman.compose.project={proj_name}",
|
||||
"--label",
|
||||
f"com.docker.compose.project={proj_name}",
|
||||
"--label",
|
||||
"label1",
|
||||
"--label",
|
||||
"label2",
|
||||
"--internal",
|
||||
"--driver",
|
||||
"bridge",
|
||||
"--opt",
|
||||
"opt1=value1",
|
||||
"--opt",
|
||||
"opt2=value2",
|
||||
"--ipam-driver",
|
||||
"default",
|
||||
"--ipv6",
|
||||
"--subnet",
|
||||
"192.168.0.0/24",
|
||||
"--ip-range",
|
||||
"192.168.0.2/24",
|
||||
"--gateway",
|
||||
"192.168.0.1",
|
||||
net_name,
|
||||
]
|
||||
args = get_network_create_args(net_desc, proj_name, net_name)
|
||||
self.assertEqual(args, expected_args)
|
@ -1,271 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# pylint: disable=protected-access
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import unittest
|
||||
|
||||
import yaml
|
||||
from parameterized import parameterized
|
||||
|
||||
from podman_compose import PodmanCompose
|
||||
from podman_compose import normalize_final
|
||||
from podman_compose import normalize_service_final
|
||||
|
||||
cwd = os.path.abspath(".")
|
||||
|
||||
|
||||
class TestNormalizeFinalBuild(unittest.TestCase):
|
||||
cases_simple_normalization = [
|
||||
({"image": "test-image"}, {"image": "test-image"}),
|
||||
(
|
||||
{"build": "."},
|
||||
{
|
||||
"build": {"context": cwd, "dockerfile": "Dockerfile"},
|
||||
},
|
||||
),
|
||||
(
|
||||
{"build": "../relative"},
|
||||
{
|
||||
"build": {
|
||||
"context": os.path.normpath(os.path.join(cwd, "../relative")),
|
||||
"dockerfile": "Dockerfile",
|
||||
},
|
||||
},
|
||||
),
|
||||
(
|
||||
{"build": "./relative"},
|
||||
{
|
||||
"build": {
|
||||
"context": os.path.normpath(os.path.join(cwd, "./relative")),
|
||||
"dockerfile": "Dockerfile",
|
||||
},
|
||||
},
|
||||
),
|
||||
(
|
||||
{"build": "/workspace/absolute"},
|
||||
{
|
||||
"build": {
|
||||
"context": "/workspace/absolute",
|
||||
"dockerfile": "Dockerfile",
|
||||
},
|
||||
},
|
||||
),
|
||||
(
|
||||
{
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile",
|
||||
},
|
||||
},
|
||||
{
|
||||
"build": {
|
||||
"context": cwd,
|
||||
"dockerfile": "Dockerfile",
|
||||
},
|
||||
},
|
||||
),
|
||||
(
|
||||
{
|
||||
"build": {
|
||||
"context": ".",
|
||||
},
|
||||
},
|
||||
{
|
||||
"build": {
|
||||
"context": cwd,
|
||||
"dockerfile": "Dockerfile",
|
||||
},
|
||||
},
|
||||
),
|
||||
(
|
||||
{
|
||||
"build": {"context": "../", "dockerfile": "test-dockerfile"},
|
||||
},
|
||||
{
|
||||
"build": {
|
||||
"context": os.path.normpath(os.path.join(cwd, "../")),
|
||||
"dockerfile": "test-dockerfile",
|
||||
},
|
||||
},
|
||||
),
|
||||
(
|
||||
{
|
||||
"build": {"context": ".", "dockerfile": "./dev/test-dockerfile"},
|
||||
},
|
||||
{
|
||||
"build": {
|
||||
"context": cwd,
|
||||
"dockerfile": "./dev/test-dockerfile",
|
||||
},
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
@parameterized.expand(cases_simple_normalization)
|
||||
def test_normalize_service_final_returns_absolute_path_in_context(self, input, expected):
|
||||
# Tests that [service.build] is normalized after merges
|
||||
project_dir = cwd
|
||||
self.assertEqual(normalize_service_final(input, project_dir), expected)
|
||||
|
||||
@parameterized.expand(cases_simple_normalization)
|
||||
def test_normalize_returns_absolute_path_in_context(self, input, expected):
|
||||
project_dir = cwd
|
||||
compose_test = {"services": {"test-service": input}}
|
||||
compose_expected = {"services": {"test-service": expected}}
|
||||
self.assertEqual(normalize_final(compose_test, project_dir), compose_expected)
|
||||
|
||||
@parameterized.expand(cases_simple_normalization)
|
||||
def test_parse_compose_file_when_single_compose(self, input, expected):
|
||||
compose_test = {"services": {"test-service": input}}
|
||||
dump_yaml(compose_test, "test-compose.yaml")
|
||||
|
||||
podman_compose = PodmanCompose()
|
||||
set_args(podman_compose, ["test-compose.yaml"], no_normalize=None)
|
||||
|
||||
podman_compose._parse_compose_file()
|
||||
|
||||
actual_compose = {}
|
||||
if podman_compose.services:
|
||||
podman_compose.services["test-service"].pop("_deps")
|
||||
actual_compose = podman_compose.services["test-service"]
|
||||
self.assertEqual(actual_compose, expected)
|
||||
|
||||
@parameterized.expand([
|
||||
(
|
||||
{},
|
||||
{"build": "."},
|
||||
{"build": {"context": cwd, "dockerfile": "Dockerfile"}},
|
||||
),
|
||||
(
|
||||
{"build": "."},
|
||||
{},
|
||||
{"build": {"context": cwd, "dockerfile": "Dockerfile"}},
|
||||
),
|
||||
(
|
||||
{"build": "/workspace/absolute"},
|
||||
{"build": "./relative"},
|
||||
{
|
||||
"build": {
|
||||
"context": os.path.normpath(os.path.join(cwd, "./relative")),
|
||||
"dockerfile": "Dockerfile",
|
||||
}
|
||||
},
|
||||
),
|
||||
(
|
||||
{"build": "./relative"},
|
||||
{"build": "/workspace/absolute"},
|
||||
{"build": {"context": "/workspace/absolute", "dockerfile": "Dockerfile"}},
|
||||
),
|
||||
(
|
||||
{"build": "./relative"},
|
||||
{"build": "/workspace/absolute"},
|
||||
{"build": {"context": "/workspace/absolute", "dockerfile": "Dockerfile"}},
|
||||
),
|
||||
(
|
||||
{"build": {"dockerfile": "test-dockerfile"}},
|
||||
{},
|
||||
{"build": {"context": cwd, "dockerfile": "test-dockerfile"}},
|
||||
),
|
||||
(
|
||||
{},
|
||||
{"build": {"dockerfile": "test-dockerfile"}},
|
||||
{"build": {"context": cwd, "dockerfile": "test-dockerfile"}},
|
||||
),
|
||||
(
|
||||
{},
|
||||
{"build": {"dockerfile": "test-dockerfile"}},
|
||||
{"build": {"context": cwd, "dockerfile": "test-dockerfile"}},
|
||||
),
|
||||
(
|
||||
{"build": {"dockerfile": "test-dockerfile-1"}},
|
||||
{"build": {"dockerfile": "test-dockerfile-2"}},
|
||||
{"build": {"context": cwd, "dockerfile": "test-dockerfile-2"}},
|
||||
),
|
||||
(
|
||||
{"build": "/workspace/absolute"},
|
||||
{"build": {"dockerfile": "test-dockerfile"}},
|
||||
{"build": {"context": "/workspace/absolute", "dockerfile": "test-dockerfile"}},
|
||||
),
|
||||
(
|
||||
{"build": {"dockerfile": "test-dockerfile"}},
|
||||
{"build": "/workspace/absolute"},
|
||||
{"build": {"context": "/workspace/absolute", "dockerfile": "test-dockerfile"}},
|
||||
),
|
||||
(
|
||||
{"build": {"dockerfile": "./test-dockerfile-1"}},
|
||||
{"build": {"dockerfile": "./test-dockerfile-2", "args": ["ENV1=1"]}},
|
||||
{
|
||||
"build": {
|
||||
"context": cwd,
|
||||
"dockerfile": "./test-dockerfile-2",
|
||||
"args": ["ENV1=1"],
|
||||
}
|
||||
},
|
||||
),
|
||||
(
|
||||
{"build": {"dockerfile": "./test-dockerfile-1", "args": ["ENV1=1"]}},
|
||||
{"build": {"dockerfile": "./test-dockerfile-2"}},
|
||||
{
|
||||
"build": {
|
||||
"context": cwd,
|
||||
"dockerfile": "./test-dockerfile-2",
|
||||
"args": ["ENV1=1"],
|
||||
}
|
||||
},
|
||||
),
|
||||
(
|
||||
{"build": {"dockerfile": "./test-dockerfile-1", "args": ["ENV1=1"]}},
|
||||
{"build": {"dockerfile": "./test-dockerfile-2", "args": ["ENV2=2"]}},
|
||||
{
|
||||
"build": {
|
||||
"context": cwd,
|
||||
"dockerfile": "./test-dockerfile-2",
|
||||
"args": ["ENV1=1", "ENV2=2"],
|
||||
}
|
||||
},
|
||||
),
|
||||
])
|
||||
def test_parse_when_multiple_composes(self, input, override, expected):
|
||||
compose_test_1 = {"services": {"test-service": input}}
|
||||
compose_test_2 = {"services": {"test-service": override}}
|
||||
dump_yaml(compose_test_1, "test-compose-1.yaml")
|
||||
dump_yaml(compose_test_2, "test-compose-2.yaml")
|
||||
|
||||
podman_compose = PodmanCompose()
|
||||
set_args(
|
||||
podman_compose,
|
||||
["test-compose-1.yaml", "test-compose-2.yaml"],
|
||||
no_normalize=None,
|
||||
)
|
||||
|
||||
podman_compose._parse_compose_file()
|
||||
|
||||
actual_compose = {}
|
||||
if podman_compose.services:
|
||||
podman_compose.services["test-service"].pop("_deps")
|
||||
actual_compose = podman_compose.services["test-service"]
|
||||
self.assertEqual(actual_compose, expected)
|
||||
|
||||
|
||||
def set_args(podman_compose: PodmanCompose, file_names: list[str], no_normalize: bool) -> None:
|
||||
podman_compose.global_args = argparse.Namespace()
|
||||
podman_compose.global_args.file = file_names
|
||||
podman_compose.global_args.project_name = None
|
||||
podman_compose.global_args.env_file = None
|
||||
podman_compose.global_args.profile = []
|
||||
podman_compose.global_args.in_pod = True
|
||||
podman_compose.global_args.no_normalize = no_normalize
|
||||
|
||||
|
||||
def dump_yaml(compose: dict, name: str) -> None:
|
||||
# Path(Path.cwd()/"subdirectory").mkdir(parents=True, exist_ok=True)
|
||||
with open(name, "w", encoding="utf-8") as outfile:
|
||||
yaml.safe_dump(compose, outfile, default_flow_style=False)
|
||||
|
||||
|
||||
def test_clean_test_yamls() -> None:
|
||||
test_files = ["test-compose-1.yaml", "test-compose-2.yaml", "test-compose.yaml"]
|
||||
for file in test_files:
|
||||
if os.path.exists(file):
|
||||
os.remove(file)
|
@ -1,20 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# pylint: disable=redefined-outer-name
|
||||
import unittest
|
||||
|
||||
from podman_compose import parse_short_mount
|
||||
|
||||
|
||||
class ParseShortMountTests(unittest.TestCase):
|
||||
def test_multi_propagation(self):
|
||||
self.assertEqual(
|
||||
parse_short_mount("/foo/bar:/baz:U,Z", "/"),
|
||||
{
|
||||
"type": "bind",
|
||||
"source": "/foo/bar",
|
||||
"target": "/baz",
|
||||
"bind": {
|
||||
"propagation": "U,Z",
|
||||
},
|
||||
},
|
||||
)
|
@ -3,7 +3,3 @@ universal = 1
|
||||
|
||||
[metadata]
|
||||
version = attr: podman_compose.__version__
|
||||
|
||||
[flake8]
|
||||
# The GitHub editor is 127 chars wide
|
||||
max-line-length=127
|
46
setup.py
46
setup.py
@ -1,49 +1,49 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
import os
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
try:
|
||||
README = open(os.path.join(os.path.dirname(__file__), "README.md"), encoding="utf-8").read()
|
||||
except: # noqa: E722 # pylint: disable=bare-except
|
||||
README = ""
|
||||
readme = open(os.path.join(os.path.dirname(__file__), 'README.md')).read()
|
||||
except:
|
||||
readme = ''
|
||||
|
||||
setup(
|
||||
name="podman-compose",
|
||||
name='podman-compose',
|
||||
description="A script to run docker-compose.yml using podman",
|
||||
long_description=README,
|
||||
long_description_content_type="text/markdown",
|
||||
long_description=readme,
|
||||
long_description_content_type='text/markdown',
|
||||
classifiers=[
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.5",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Intended Audience :: Developers",
|
||||
"Operating System :: OS Independent",
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Topic :: Software Development :: Build Tools",
|
||||
"License :: OSI Approved :: GNU General Public License v2 (GPLv2)",
|
||||
],
|
||||
keywords="podman, podman-compose",
|
||||
author="Muayyad Alsadi",
|
||||
author_email="alsadi@gmail.com",
|
||||
url="https://github.com/containers/podman-compose",
|
||||
py_modules=["podman_compose"],
|
||||
entry_points={"console_scripts": ["podman-compose = podman_compose:main"]},
|
||||
keywords='podman, podman-compose',
|
||||
author='Muayyad Alsadi',
|
||||
author_email='alsadi@gmail.com',
|
||||
url='https://github.com/containers/podman-compose',
|
||||
py_modules=['podman_compose'],
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'podman-compose = podman_compose:main'
|
||||
]
|
||||
},
|
||||
include_package_data=True,
|
||||
license="GPL-2.0-only",
|
||||
license='GPL-2.0-only',
|
||||
install_requires=[
|
||||
"pyyaml",
|
||||
"python-dotenv",
|
||||
'pyyaml',
|
||||
'python-dotenv',
|
||||
],
|
||||
extras_require={"devel": ["ruff", "pre-commit", "coverage", "parameterized"]},
|
||||
# test_suite='tests',
|
||||
# tests_require=[
|
||||
# 'coverage',
|
||||
# 'pytest-cov',
|
||||
# 'pytest',
|
||||
# 'tox',
|
||||
# ]
|
||||
)
|
||||
|
@ -1,33 +1,8 @@
|
||||
-e .
|
||||
coverage==7.4.3
|
||||
parameterized==0.9.0
|
||||
pytest==8.0.2
|
||||
tox==4.13.0
|
||||
ruff==0.3.1
|
||||
pylint==3.1.0
|
||||
# The order of packages is significant, because pip processes them in the order
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
# The packages below are transitive dependencies of the packages above and are included here
|
||||
# to make testing reproducible.
|
||||
# To refresh, create a new virtualenv and do:
|
||||
# pip install -r requirements.txt -r test-requirements.txt
|
||||
# pip freeze > test-requirements.txt
|
||||
# and edit test-requirements.txt to add this comment
|
||||
|
||||
astroid==3.1.0
|
||||
cachetools==5.3.3
|
||||
chardet==5.2.0
|
||||
colorama==0.4.6
|
||||
dill==0.3.8
|
||||
distlib==0.3.8
|
||||
filelock==3.13.1
|
||||
iniconfig==2.0.0
|
||||
isort==5.13.2
|
||||
mccabe==0.7.0
|
||||
packaging==23.2
|
||||
platformdirs==4.2.0
|
||||
pluggy==1.4.0
|
||||
pyproject-api==1.6.1
|
||||
python-dotenv==1.0.1
|
||||
PyYAML==6.0.1
|
||||
tomlkit==0.12.4
|
||||
virtualenv==20.25.1
|
||||
coverage
|
||||
pytest-cov
|
||||
pytest
|
||||
tox
|
||||
|
@ -1,12 +0,0 @@
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
|
||||
def create_base_test_image():
|
||||
subprocess.check_call(
|
||||
['podman', 'build', '-t', 'nopush/podman-compose-test', '.'],
|
||||
cwd=os.path.join(os.path.dirname(__file__), "base_image"),
|
||||
)
|
||||
|
||||
|
||||
create_base_test_image()
|
@ -1,6 +0,0 @@
|
||||
FROM docker.io/library/debian:bookworm-slim
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y \
|
||||
dumb-init \
|
||||
busybox \
|
||||
wget
|
@ -1,22 +0,0 @@
|
||||
# Test podman-compose with build (fail scenario)
|
||||
|
||||
```shell
|
||||
podman-compose build || echo $?
|
||||
```
|
||||
|
||||
expected output would be something like
|
||||
|
||||
```
|
||||
STEP 1/3: FROM busybox
|
||||
STEP 2/3: RUN this_command_does_not_exist
|
||||
/bin/sh: this_command_does_not_exist: not found
|
||||
Error: building at STEP "RUN this_command_does_not_exist": while running runtime: exit status 127
|
||||
|
||||
exit code: 127
|
||||
```
|
||||
|
||||
Expected `podman-compose` exit code:
|
||||
```shell
|
||||
echo $?
|
||||
127
|
||||
```
|
@ -1,3 +0,0 @@
|
||||
FROM busybox
|
||||
RUN this_command_does_not_exist
|
||||
CMD ["sh"]
|
@ -1,5 +0,0 @@
|
||||
version: "3"
|
||||
services:
|
||||
test:
|
||||
build: ./context
|
||||
image: build-fail-img
|
@ -1,9 +0,0 @@
|
||||
FROM busybox
|
||||
|
||||
RUN --mount=type=secret,required=true,id=build_secret \
|
||||
ls -l /run/secrets/ && cat /run/secrets/build_secret
|
||||
|
||||
RUN --mount=type=secret,required=true,id=build_secret,target=/tmp/secret \
|
||||
ls -l /run/secrets/ /tmp/ && cat /tmp/secret
|
||||
|
||||
CMD [ 'echo', 'nothing here' ]
|
@ -1,22 +0,0 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
test:
|
||||
image: test
|
||||
secrets:
|
||||
- run_secret # implicitly mount to /run/secrets/run_secret
|
||||
- source: run_secret
|
||||
target: /tmp/run_secret2 # explicit mount point
|
||||
|
||||
build:
|
||||
context: .
|
||||
secrets:
|
||||
- build_secret # can be mounted in Dockerfile with "RUN --mount=type=secret,id=build_secret"
|
||||
- source: build_secret
|
||||
target: build_secret2 # rename to build_secret2
|
||||
|
||||
secrets:
|
||||
build_secret:
|
||||
file: ./my_secret
|
||||
run_secret:
|
||||
file: ./my_secret
|
@ -1,18 +0,0 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
test:
|
||||
image: test
|
||||
build:
|
||||
context: .
|
||||
secrets:
|
||||
# invalid target argument
|
||||
#
|
||||
# According to https://github.com/compose-spec/compose-spec/blob/master/build.md, target is
|
||||
# supposed to be the "name of a *file* to be mounted in /run/secrets/". Not a path.
|
||||
- source: build_secret
|
||||
target: /build_secret
|
||||
|
||||
secrets:
|
||||
build_secret:
|
||||
file: ./my_secret
|
@ -1 +0,0 @@
|
||||
important-secret-is-important
|
@ -1,4 +1,4 @@
|
||||
|
||||
```
|
||||
podman-compose run --rm sleep /bin/sh -c 'wget -O - http://web:8000/hosts'
|
||||
podman-compose run --rm sleep /bin/sh -c 'wget -O - http://localhost:8000/hosts'
|
||||
```
|
||||
|
@ -1,22 +1,21 @@
|
||||
version: "3.7"
|
||||
services:
|
||||
web:
|
||||
image: nopush/podman-compose-test
|
||||
command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", "/etc/", "-p", "8000"]
|
||||
image: busybox
|
||||
command: ["/bin/busybox", "httpd", "-f", "-h", "/etc/", "-p", "8000"]
|
||||
tmpfs:
|
||||
- /run
|
||||
- /tmp
|
||||
sleep:
|
||||
image: nopush/podman-compose-test
|
||||
command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 3600"]
|
||||
depends_on:
|
||||
- "web"
|
||||
image: busybox
|
||||
command: ["/bin/busybox", "sh", "-c", "sleep 3600"]
|
||||
depends_on: "web"
|
||||
tmpfs:
|
||||
- /run
|
||||
- /tmp
|
||||
sleep2:
|
||||
image: nopush/podman-compose-test
|
||||
command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 3600"]
|
||||
image: busybox
|
||||
command: ["/bin/busybox", "sh", "-c", "sleep 3600"]
|
||||
depends_on:
|
||||
- sleep
|
||||
tmpfs:
|
||||
|
@ -1,9 +0,0 @@
|
||||
running the following commands should always give podman-rocks-123
|
||||
|
||||
```
|
||||
podman-compose -f project/container-compose.yaml --env-file env-files/project-1.env up
|
||||
```
|
||||
|
||||
```
|
||||
podman-compose -f $(pwd)/project/container-compose.yaml --env-file $(pwd)/env-files/project-1.env up
|
||||
```
|
@ -1 +0,0 @@
|
||||
ZZVAR1=podman-rocks-123
|
@ -1,9 +0,0 @@
|
||||
services:
|
||||
app:
|
||||
image: busybox
|
||||
command: ["/bin/busybox", "sh", "-c", "env | grep ZZ"]
|
||||
tmpfs:
|
||||
- /run
|
||||
- /tmp
|
||||
environment:
|
||||
ZZVAR1: $ZZVAR1
|
@ -1,20 +1,20 @@
|
||||
version: "3"
|
||||
services:
|
||||
too_long:
|
||||
image: nopush/podman-compose-test
|
||||
command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 3600; exit 0"]
|
||||
image: busybox
|
||||
command: ["/bin/busybox", "sh", "-c", "sleep 3600; exit 0"]
|
||||
tmpfs:
|
||||
- /run
|
||||
- /tmp
|
||||
sh1:
|
||||
image: nopush/podman-compose-test
|
||||
command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 1; exit 1"]
|
||||
image: busybox
|
||||
command: ["/bin/busybox", "sh", "-c", "sleep 5; exit 1"]
|
||||
tmpfs:
|
||||
- /run
|
||||
- /tmp
|
||||
sh2:
|
||||
image: nopush/podman-compose-test
|
||||
command: ["dumb-init", "/bin/busybox", "sh", "-c", "sleep 1; exit 2"]
|
||||
image: busybox
|
||||
command: ["/bin/busybox", "sh", "-c", "sleep 5; exit 2"]
|
||||
tmpfs:
|
||||
- /run
|
||||
- /tmp
|
||||
|
@ -1,7 +0,0 @@
|
||||
services:
|
||||
webapp_default:
|
||||
|
||||
webapp_special:
|
||||
image: nopush/podman-compose-test
|
||||
volumes:
|
||||
- "/data"
|
@ -1,10 +0,0 @@
|
||||
version: "3"
|
||||
services:
|
||||
web:
|
||||
image: nopush/podman-compose-test
|
||||
extends:
|
||||
file: common-services.yml
|
||||
service: webapp_default
|
||||
environment:
|
||||
- DEBUG=1
|
||||
cpu_shares: 5
|
@ -1,8 +0,0 @@
|
||||
version: "3"
|
||||
services:
|
||||
web:
|
||||
extends:
|
||||
file: sub/docker-compose.yml
|
||||
service: webapp
|
||||
environment:
|
||||
- DEBUG=1
|
@ -1,12 +0,0 @@
|
||||
version: "3"
|
||||
services:
|
||||
webapp:
|
||||
build:
|
||||
context: docker/example
|
||||
dockerfile: Dockerfile
|
||||
image: localhost/subdir_test:me
|
||||
ports:
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
- "/data"
|
||||
|
@ -1 +0,0 @@
|
||||
FROM nopush/podman-compose-test as base
|
@ -1,7 +0,0 @@
|
||||
version: '3.6'
|
||||
|
||||
services:
|
||||
web:
|
||||
image: nopush/podman-compose-test
|
||||
command: ["dumb-init", "/bin/busybox", "httpd", "-f", "-h", ".", "-p", "8003"]
|
||||
|
@ -1,4 +0,0 @@
|
||||
version: '3.6'
|
||||
|
||||
include:
|
||||
- docker-compose.base.yaml
|
@ -4,7 +4,7 @@ services:
|
||||
image: busybox
|
||||
command: busybox httpd -h /var/www/html/ -f -p 8001
|
||||
volumes:
|
||||
- ./1.env:/var/www/html/index.txt:z
|
||||
- ./1.env:/var/www/html/index.txt
|
||||
env_file: ./1.env
|
||||
labels:
|
||||
l1: v1
|
||||
|
@ -1,11 +1,10 @@
|
||||
version: '3'
|
||||
services:
|
||||
web1:
|
||||
image: busybox
|
||||
env_file: ./12.env
|
||||
labels:
|
||||
- l1=v2
|
||||
- l2=v2
|
||||
- l1=v2
|
||||
- l2=v2
|
||||
environment:
|
||||
mykey1: myval2
|
||||
mykey2: myval2
|
||||
@ -14,6 +13,6 @@ services:
|
||||
image: busybox
|
||||
command: busybox httpd -h /var/www/html/ -f -p 8002
|
||||
volumes:
|
||||
- ./2.env:/var/www/html/index.txt:z
|
||||
- ./2.env:/var/www/html/index.txt
|
||||
env_file: ./2.env
|
||||
|
||||
|
@ -1,7 +0,0 @@
|
||||
version: '3'
|
||||
services:
|
||||
web:
|
||||
image: busybox
|
||||
command: httpd -f -p 8123 -h /etc/
|
||||
network_mode: host
|
||||
|
@ -1,16 +0,0 @@
|
||||
---
|
||||
# https://github.com/compose-spec/compose-spec/blob/master/spec.md#priority
|
||||
services:
|
||||
app:
|
||||
image: busybox
|
||||
command: top
|
||||
networks:
|
||||
app_net_1:
|
||||
app_net_2:
|
||||
priority: 1000
|
||||
app_net_3:
|
||||
priority: 100
|
||||
networks:
|
||||
app_net_1:
|
||||
app_net_2:
|
||||
app_net_3:
|
@ -1,21 +0,0 @@
|
||||
version: "3"
|
||||
services:
|
||||
web1:
|
||||
image: busybox
|
||||
hostname: web1
|
||||
command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"]
|
||||
working_dir: /var/www/html
|
||||
ports:
|
||||
- 8001:8001
|
||||
volumes:
|
||||
- ./test1.txt:/var/www/html/index.txt:ro,z
|
||||
web2:
|
||||
image: busybox
|
||||
hostname: web2
|
||||
command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"]
|
||||
working_dir: /var/www/html
|
||||
ports:
|
||||
- 8002:8001
|
||||
volumes:
|
||||
- ./test2.txt:/var/www/html/index.txt:ro,z
|
||||
|
@ -1 +0,0 @@
|
||||
test1
|
@ -1 +0,0 @@
|
||||
test2
|
@ -1,23 +0,0 @@
|
||||
version: "3"
|
||||
networks:
|
||||
mystack:
|
||||
services:
|
||||
web1:
|
||||
image: busybox
|
||||
hostname: web1
|
||||
command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"]
|
||||
working_dir: /var/www/html
|
||||
ports:
|
||||
- 8001:8001
|
||||
volumes:
|
||||
- ./test1.txt:/var/www/html/index.txt:ro,z
|
||||
web2:
|
||||
image: busybox
|
||||
hostname: web2
|
||||
command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"]
|
||||
working_dir: /var/www/html
|
||||
ports:
|
||||
- 8002:8001
|
||||
volumes:
|
||||
- ./test2.txt:/var/www/html/index.txt:ro,z
|
||||
|
@ -1 +0,0 @@
|
||||
test1
|
@ -1 +0,0 @@
|
||||
test2
|
@ -1,45 +0,0 @@
|
||||
version: "3"
|
||||
networks:
|
||||
net1:
|
||||
net2:
|
||||
services:
|
||||
web1:
|
||||
image: busybox
|
||||
#container_name: web1
|
||||
hostname: web1
|
||||
command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"]
|
||||
working_dir: /var/www/html
|
||||
networks:
|
||||
- net1
|
||||
ports:
|
||||
- 8001:8001
|
||||
volumes:
|
||||
- ./test1.txt:/var/www/html/index.txt:ro,z
|
||||
web2:
|
||||
image: busybox
|
||||
#container_name: web2
|
||||
hostname: web2
|
||||
command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"]
|
||||
working_dir: /var/www/html
|
||||
networks:
|
||||
- net1
|
||||
- net2
|
||||
ports:
|
||||
- 8002:8001
|
||||
volumes:
|
||||
- ./test2.txt:/var/www/html/index.txt:ro,z
|
||||
web3:
|
||||
image: busybox
|
||||
command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"]
|
||||
working_dir: /var/www/html
|
||||
networks:
|
||||
net1:
|
||||
aliases:
|
||||
- alias11
|
||||
- alias12
|
||||
net2:
|
||||
aliases:
|
||||
- alias21
|
||||
volumes:
|
||||
- ./test2.txt:/var/www/html/index.txt:ro,z
|
||||
|
@ -1 +0,0 @@
|
||||
test1
|
@ -1 +0,0 @@
|
||||
test2
|
@ -1,61 +0,0 @@
|
||||
version: "3"
|
||||
networks:
|
||||
shared-network:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: "172.19.1.0/24"
|
||||
internal-network:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: "172.19.2.0/24"
|
||||
|
||||
services:
|
||||
web1:
|
||||
image: busybox
|
||||
hostname: web1
|
||||
command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"]
|
||||
working_dir: /var/www/html
|
||||
networks:
|
||||
shared-network:
|
||||
ipv4_address: "172.19.1.10"
|
||||
x-podman.mac_address: "02:01:01:00:01:01"
|
||||
internal-network:
|
||||
ipv4_address: "172.19.2.10"
|
||||
x-podman.mac_address: "02:01:01:00:02:01"
|
||||
volumes:
|
||||
- ./test1.txt:/var/www/html/index.txt:ro,z
|
||||
web2:
|
||||
image: busybox
|
||||
hostname: web2
|
||||
command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"]
|
||||
working_dir: /var/www/html
|
||||
mac_address: "02:01:01:00:02:02"
|
||||
networks:
|
||||
internal-network:
|
||||
ipv4_address: "172.19.2.11"
|
||||
volumes:
|
||||
- ./test2.txt:/var/www/html/index.txt:ro,z
|
||||
|
||||
web3:
|
||||
image: busybox
|
||||
hostname: web2
|
||||
command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"]
|
||||
working_dir: /var/www/html
|
||||
networks:
|
||||
internal-network:
|
||||
volumes:
|
||||
- ./test3.txt:/var/www/html/index.txt:ro,z
|
||||
|
||||
web4:
|
||||
image: busybox
|
||||
hostname: web2
|
||||
command: ["/bin/busybox", "httpd", "-f", "-h", "/var/www/html", "-p", "8001"]
|
||||
working_dir: /var/www/html
|
||||
networks:
|
||||
internal-network:
|
||||
shared-network:
|
||||
ipv4_address: "172.19.1.13"
|
||||
volumes:
|
||||
- ./test4.txt:/var/www/html/index.txt:ro,z
|
@ -1 +0,0 @@
|
||||
test1
|
@ -1 +0,0 @@
|
||||
test2
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user