Skip to content

Commit

Permalink
Add the --current-size and --exit-zero to optionally show current doc…
Browse files Browse the repository at this point in the history
…ker image size and avoid stop the CI even if exceed max_size and max-layers (#319)
  • Loading branch information
luzfcb authored Jan 11, 2025
1 parent 959e325 commit b377924
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 16 deletions.
5 changes: 2 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ['3.9', '3.10', '3.11', '3.12']
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']

steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -52,10 +52,9 @@ jobs:
poetry run flake8 .
poetry run mypy .
poetry run pytest
poetry check
poetry run pip check
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
file: ./coverage.xml
files: ./coverage.xml
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

We follow Semantic Version.

## Version 2.1.0

### Features

- Adds `--current-size` flag to show the current size of the docker image.
- Adds `--exit-zero` flag to force the exit code to be 0 even if there are errors.
- Adds `__main__.py` entrypoint to be able to run it via `python -m docker_image_size_limit`

## Version 2.0.0

Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ LABEL maintainer="mail@sobolevn.me"
LABEL vendor="wemake.services"

# Our own tool:
ENV DISL_VERSION='2.0.0'
ENV DISL_VERSION='2.1.0'

RUN apk add --no-cache bash docker
RUN pip3 install "docker-image-size-limit==$DISL_VERSION"
Expand Down
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,42 @@ $ disl your-image-name:label 300MiB --max-layers=5
# ok!
```

Add `--current-size` flag to show the current size your image:

```bash
$ disl your-image-name:label 300MiB --current-size
your-image-name:label size is 414.4 MiB
your-image-name:label exceeds 300MiB limit by 114.4 MiB
```


Add `--exit-zero` flag to force the exit code to be 0 even if there are errors:

```bash
$ disl your-image-name:label 300MiB --exit-zero
your-image-name:label exceeds 300MiB limit by 114.4 MiB

$ echo $?
0
```

You can combine all flags together:

```bash
$ disl your-image-name:label 300MiB --max-layers=5 --current-size --exit-zero
your-image-name:label size is 414.4 MiB
your-image-name:label exceeds 300MiB limit by 114.4 MiB
your-image-name:label exceeds 5 maximum layers by 2
```

Run `disl` as a module:

```bash
$ python -m docker_image_size_limit your-image-name:label 300MiB
your-image-name:label exceeds 300MiB limit by 114.4 MiB
```



## Options

Expand Down Expand Up @@ -84,6 +120,10 @@ You can also use this check as a [GitHub Action](https://github.com/marketplace/
with:
image: "$YOUR_IMAGE_NAME"
size: "$YOUR_SIZE_LIMIT"
# optional fields:
max_layers: 5
show_current_size: false
exit_zero: false
```
Here's [an example](https://github.com/wemake-services/docker-image-size-limit/actions?query=workflow%3Adisl).
Expand All @@ -105,6 +145,9 @@ Then you can use it like so:
docker run -v /var/run/docker.sock:/var/run/docker.sock --rm \
-e INPUT_IMAGE="$YOUR_IMAGE_NAME" \
-e INPUT_SIZE="$YOUR_SIZE_LIMIT" \
-e INPUT_MAX_LAYERS="$YOUR_MAX_LAYERS" \
-e INPUT_SHOW_CURRENT_SIZE="true" \
-e INPUT_EXIT_ZERO="true" \
wemakeservices/docker-image-size-limit
```

Expand Down
14 changes: 12 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# This is a definition file for a Github Action.
# See: https://help.github.com/en/articles/creating-a-docker-container-action
# See: https://docs.github.com/en/actions/sharing-automations/creating-actions/creating-a-docker-container-action

# We also define metadata here:
# See: https://help.github.com/en/articles/metadata-syntax-for-github-actions
# See: https://docs.github.com/en/actions/sharing-automations/creating-actions/metadata-syntax-for-github-actions

name: 'docker-image-size-limit'
description: 'Runs docker-image-size-limit as a GitHub Action'
Expand All @@ -21,6 +21,14 @@ inputs:
description: 'The maximum number of layers in the image'
required: false
default: -1
show_current_size:
description: 'Show the current size of the image'
required: false
default: 'false'
exit_zero:
description: 'Do not fail the action even if docker image size/layers exceed size and max_layers'
required: false
default: 'false'
outputs:
size:
description: 'The output of docker-image-size-limit run'
Expand All @@ -32,3 +40,5 @@ runs:
- ${{ inputs.image }}
- ${{ inputs.size }}
- ${{ inputs.layers }}
- ${{ inputs.show_current_size }}
- ${{ inputs.exit_zero }}
35 changes: 29 additions & 6 deletions docker_image_size_limit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,21 @@
)


def main() -> NoReturn:
def main(prog_name: str = 'disl') -> NoReturn: # noqa: WPS210
"""Main CLI entrypoint."""
client = docker.from_env()
arguments = _parse_args()
extra_size, extra_layers = _check_image(
arguments = _parse_args(prog_name=prog_name)
extra_size, extra_layers, image_current_size = _check_image(
client,
image=arguments.image,
max_size=arguments.max_size,
max_layers=arguments.max_layers,
)
if arguments.current_size:
print('{0} current size is {1}'.format( # noqa: WPS421
arguments.image,
format_size(image_current_size, binary=True),
))

exit_code = 0
if extra_size > 0:
Expand All @@ -41,6 +46,8 @@ def main() -> NoReturn:
extra_layers,
))
exit_code = 1
if arguments.exit_zero:
exit_code = 0
sys.exit(exit_code)


Expand All @@ -49,14 +56,15 @@ def _check_image(
image: str,
max_size: str,
max_layers: int,
) -> Tuple[int, int]:
) -> Tuple[int, int, int]:
image_info = client.images.get(image)
image_current_size: int = image_info.attrs['Size']
size_overflow = check_image_size(image_info, limit=max_size)
if max_layers > 0:
layers_overflow = check_image_layers(image_info, limit=max_layers)
else:
layers_overflow = 0
return size_overflow, layers_overflow
return size_overflow, layers_overflow, image_current_size


def check_image_size(
Expand Down Expand Up @@ -107,9 +115,10 @@ def check_image_layers(
return len(layers) - limit


def _parse_args() -> argparse.Namespace:
def _parse_args(prog_name: str) -> argparse.Namespace:
parser = argparse.ArgumentParser(
description='Keep your docker images small',
prog=prog_name,
)
parser.add_argument(
'--version', action='version', version=_version,
Expand All @@ -126,4 +135,18 @@ def _parse_args() -> argparse.Namespace:
help='Maximum number of image layers',
default=-1,
)
parser.add_argument(
'--current-size',
action='store_true',
help='Display the current size of the Docker image',
default=False,
dest='current_size',
)
parser.add_argument(
'--exit-zero',
action='store_true',
help='Exit with 0 even if docker image size/layers exceed max_size and max-layers', # noqa: E501
default=False,
dest='exit_zero',
)
return parser.parse_args()
12 changes: 12 additions & 0 deletions docker_image_size_limit/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import sys
from os.path import basename

from docker_image_size_limit import main

if __name__ == '__main__': # pragma: no cover
interpreter_binary_name = basename(sys.executable)
main(
prog_name='{0} -m docker_image_size_limit'.format(
interpreter_binary_name,
),
)
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "docker-image-size-limit"
version = "2.0.0"
version = "2.1.0"
description = ""
license = "MIT"
authors = [
Expand Down
27 changes: 25 additions & 2 deletions scripts/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,19 +1,42 @@
#!/bin/bash

# Default values:
# Passed args from GitHub Actions:
: "${INPUT_IMAGE:=$1}" # Required
: "${INPUT_SIZE:=$2}" # Required
: "${INPUT_MAX_LAYERS:=$3}" # Optional
: "${INPUT_SHOW_CURRENT_SIZE:=$4}" # Optional
: "${INPUT_EXIT_ZERO:=$5}" # Optional

# Default values, needed because `Dockerfile` can be used directly:
# These values must match ones in `action.yml`!
: "${INPUT_MAX_LAYERS:=-1}"
: "${INPUT_SHOW_CURRENT_SIZE:=false}"
: "${INPUT_EXIT_ZERO:=false}"

# Diagnostic output:
echo "Using image: $INPUT_IMAGE"
echo "Size limit: $INPUT_SIZE"
echo "Max layers: $INPUT_MAX_LAYERS"
echo "Show Current Size: $INPUT_SHOW_CURRENT_SIZE"
echo "Exit Zero: $INPUT_EXIT_ZERO"
echo 'disl --version:'
disl --version
echo '================================='
echo

SHOW_CURRENT_SIZE_FLAG=''
if [ "$INPUT_SHOW_CURRENT_SIZE" = 'true' ]; then
SHOW_CURRENT_SIZE_FLAG='--current-size'
fi

EXIT_ZERO_FLAG=''
if [ "$INPUT_EXIT_ZERO" = 'true' ]; then
EXIT_ZERO_FLAG='--exit-zero'
fi


# Runs disl:
output=$(disl "$INPUT_IMAGE" "$INPUT_SIZE" --max-layers="$INPUT_MAX_LAYERS")
output=$(disl "$INPUT_IMAGE" "$INPUT_SIZE" --max-layers="$INPUT_MAX_LAYERS" "$SHOW_CURRENT_SIZE_FLAG" "$EXIT_ZERO_FLAG")
status="$?"

# Sets the output variable for Github Action API:
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ ignore = D100, D104, D401, W504, RST303, RST304, DAR103, DAR203

per-file-ignores =
# There are `assert`s in tests:
tests/*.py: WPS442, S101, S404, S603, S607
tests/*.py: WPS442, S101, S404, S603, S607, WPS226


[isort]
Expand Down
91 changes: 91 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import subprocess
import sys
from os.path import basename


def test_disl_exceeds_limit(image_name: str) -> None:
Expand Down Expand Up @@ -58,3 +60,92 @@ def test_disl_max_layers(image_name: str) -> None:
assert process.returncode == 1
assert 'maximum layers by' in output
assert image_name in output


def test_disl_current_size(image_name: str) -> None:
"""Runs `disl` command with --current-size flag."""
process = subprocess.Popen(
[
'disl',
image_name,
'1 kb',
'--current-size',
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
encoding='utf8',
)
output, _ = process.communicate()

assert process.returncode == 1
assert 'current size is' in output
assert image_name in output


def test_disl_exit_zero(image_name: str) -> None:
"""Runs `disl` command with --exit-zero flag."""
process = subprocess.Popen(
[
'disl',
image_name,
'1 kb',
'--current-size',
'--exit-zero',
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
encoding='utf8',
)
output, _ = process.communicate()

assert process.returncode == 0
assert 'current size is' in output
assert image_name in output


def test_docker_image_size_limit_as_module(image_name: str) -> None:
"""Runs `disl` command with --exit-zero flag."""
interpreter_binary_name = basename(sys.executable)
process = subprocess.Popen(
[
'{0}'.format(interpreter_binary_name),
'-m',
'docker_image_size_limit',
image_name,
'1 kb',
'--current-size',
'--exit-zero',
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
encoding='utf8',
)
output, _ = process.communicate()

assert process.returncode == 0
assert 'current size is' in output
assert image_name in output


def test_docker_image_size_limit_as_module_help_flag(image_name: str) -> None: # noqa: WPS118,E501
"""Runs `disl` command via it python module."""
interpreter_binary_name = basename(sys.executable)
process = subprocess.Popen(
[
'{0}'.format(interpreter_binary_name),
'-m',
'docker_image_size_limit',
'--help',
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
encoding='utf8',
)
output, _ = process.communicate()

assert process.returncode == 0
assert 'docker_image_size_limit' in output

0 comments on commit b377924

Please sign in to comment.