Skip to content

Commit

Permalink
Version 2.0.0 release: adds --max-layers flag (#308)
Browse files Browse the repository at this point in the history
  • Loading branch information
sobolevn authored Sep 28, 2024
1 parent 510e9a3 commit b08c883
Show file tree
Hide file tree
Showing 16 changed files with 422 additions and 257 deletions.
1 change: 1 addition & 0 deletions .github/workflows/disl.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ jobs:
with: # we lint this image by itself:
image: 'wemake-services/docker-image-size-limit:latest'
size: 405MB
max_layers: 10
10 changes: 8 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ concurrency:
group: ${{ github.head_ref || github.run_id }}
cancel-in-progress: true

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -39,12 +42,15 @@ jobs:
poetry run pip install -U pip
poetry install
- name: Pull test deps
run: |
# We need this specific image for tests:
poetry run docker pull python:3.6.6-alpine
- name: "Run checks for python ${{ matrix.python-version }}"
run: |
poetry run flake8 .
poetry run mypy .
# We need this specific image for tests:
poetry run docker pull python:3.6.6-alpine
poetry run pytest
poetry check
poetry run pip check
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

We follow Semantic Version.


## Version 2.0.0

### Features

- Adds `--max-layers` flag to lint the maximum number of layers


## Version 1.1.0

### Features
Expand Down
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
# This image is also available on Dockerhub:
# https://hub.docker.com/r/wemakeservices/docker-image-size-limit

FROM python:3.9.6-alpine
FROM python:3.12.6-alpine

LABEL maintainer="sobolevn@wemake.services"
LABEL maintainer="mail@sobolevn.me"
LABEL vendor="wemake.services"

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

RUN apk add --no-cache bash docker
RUN pip3 install "docker-image-size-limit==$DISL_VERSION"
Expand Down
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,19 @@ $ disl your-image-name:label 300MiB
your-image-name:label exceeds 300MiB limit by 114.4 MiB
```

Add `--max-layers` flag to also lint the maximum amount of layers possible
in your image:

```bash
# If your image has 7 layers:
$ disl your-image-name:label 300MiB --max-layers=5
your-image-name:label exceeds 5 maximum layers by 2

# If your image has 5 layers:
$ disl your-image-name:label 300MiB --max-layers=5
# ok!
```


## Options

Expand Down Expand Up @@ -62,9 +75,9 @@ We also ship [PEP-561](https://www.python.org/dev/peps/pep-0561/)
compatible type annotations with this library.


## Github Action
## GitHub Action

You can also use this check as a [Gihub Action](https://github.com/marketplace/actions/docker-image-size-limit):
You can also use this check as a [GitHub Action](https://github.com/marketplace/actions/docker-image-size-limit):

```yaml
- uses: wemake-services/docker-image-size-limit@master
Expand Down
7 changes: 6 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# See: https://help.github.com/en/articles/metadata-syntax-for-github-actions

name: 'docker-image-size-limit'
description: 'Runs docker-image-size-limit as a Github Action'
description: 'Runs docker-image-size-limit as a GitHub Action'
branding:
icon: 'check'
color: 'blue'
Expand All @@ -17,6 +17,10 @@ inputs:
size:
description: 'Human readable size as the hard limit'
required: true
max_layers:
description: 'The maximum number of layers in the image'
required: false
default: -1
outputs:
size:
description: 'The output of docker-image-size-limit run'
Expand All @@ -27,3 +31,4 @@ runs:
args:
- ${{ inputs.image }}
- ${{ inputs.size }}
- ${{ inputs.layers }}
71 changes: 61 additions & 10 deletions docker_image_size_limit/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import argparse
import os
import sys
from typing import NoReturn
from typing import List, NoReturn, Tuple

import docker
from docker.models.images import Image
from humanfriendly import format_size, parse_size

from docker_image_size_limit.version import get_version
Expand All @@ -18,22 +19,48 @@ def main() -> NoReturn:
"""Main CLI entrypoint."""
client = docker.from_env()
arguments = _parse_args()
oversize = check_image_size(client, arguments.image, arguments.size)
extra_size, extra_layers = _check_image(
client,
image=arguments.image,
max_size=arguments.max_size,
max_layers=arguments.max_layers,
)

exit_code = 0
if oversize > 0:
if extra_size > 0:
print('{0} exceeds {1} limit by {2}'.format( # noqa: WPS421
arguments.image,
arguments.size,
format_size(oversize, binary=True),
arguments.max_size,
format_size(extra_size, binary=True),
))
exit_code = 1
if extra_layers > 0:
print('{0} exceeds {1} maximum layers by {2}'.format( # noqa: WPS421
arguments.image,
arguments.max_layers,
extra_layers,
))
exit_code = 1
sys.exit(exit_code)


def check_image_size(
def _check_image(
client: docker.DockerClient,
image: str,
max_size: str,
max_layers: int,
) -> Tuple[int, int]:
image_info = client.images.get(image)
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


def check_image_size(
image: Image,
limit: str,
) -> int:
"""
Expand All @@ -42,8 +69,7 @@ def check_image_size(
Compares it to the given size in bytes or in human readable format.
Args:
client: docker client to work with images.
image: image name.
image: image object from docker.
limit: human-readable size limit.
Returns:
Expand All @@ -52,7 +78,7 @@ def check_image_size(
than the actual image. We only care for values ``> 0``.
"""
image_size: int = client.images.get(image).attrs['Size']
image_size: int = image.attrs['Size']

try:
image_limit = int(limit)
Expand All @@ -62,6 +88,25 @@ def check_image_size(
return image_size - image_limit


def check_image_layers(
image: Image,
limit: int,
) -> int:
"""
Checks the number of layers in an image.
Args:
image: image object from docker.
limit: maximum number of layers.
Returns:
Tresshold overflow in number of layers.
"""
layers: List[str] = image.attrs['RootFS']['Layers']
return len(layers) - limit


def _parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description='Keep your docker images small',
Expand All @@ -73,6 +118,12 @@ def _parse_args() -> argparse.Namespace:
'image', type=str, help='Docker image name to be checked',
)
parser.add_argument(
'size', type=str, help='Human-readable size limit: 102 MB, 1GB',
'max_size', type=str, help='Human-readable size limit: 102 MB, 1GB',
)
parser.add_argument(
'--max-layers',
type=int,
help='Maximum number of image layers',
default=-1,
)
return parser.parse_args()
2 changes: 1 addition & 1 deletion docker_image_size_limit/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@

def get_version(distribution_name: str) -> str:
"""Our helper to get version of a package."""
return metadata.version(distribution_name) # type: ignore
return metadata.version(distribution_name)
Loading

0 comments on commit b08c883

Please sign in to comment.