Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/Publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ on:

jobs:
publish:
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
permissions:
id-token: write # mandatory for PyPI trusted publishing

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version-file: pyproject.toml
architecture: x64
Expand All @@ -25,7 +25,7 @@ jobs:
python -m build --sdist --wheel

- name: Upload to PyPI
uses: pypa/gh-action-pypi-publish@release/v1.8
uses: pypa/gh-action-pypi-publish@release/v1
# dont specify anything for Trusted Publishing
# https://docs.pypi.org/trusted-publishers
# with:
Expand Down
9 changes: 3 additions & 6 deletions .github/workflows/QA.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ on:

jobs:
check-qa:
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version-file: pyproject.toml
architecture: x64
Expand All @@ -24,9 +24,6 @@ jobs:
pip install -U pip
pip install -e .[lint,scripts,test,check,visual]

- name: Check black formatting
run: inv lint-black

- name: Check ruff
run: inv lint-ruff

Expand Down
14 changes: 7 additions & 7 deletions .github/workflows/Tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ jobs:
run-tests:
strategy:
matrix:
os: [ubuntu-22.04]
python: ["3.8", "3.9", "3.10", "3.11"]
os: [ubuntu-24.04]
python: ["3.10", "3.11", "3.12", "3.13", "3.14"]
runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6

- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python }}
architecture: x64
Expand All @@ -32,18 +32,18 @@ jobs:
run: inv coverage --args "-vvv"

- name: Upload coverage report to codecov
if: matrix.python == '3.11'
if: matrix.python == '3.10'
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}

build_python:
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v3

- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version-file: pyproject.toml
architecture: x64
Expand Down
21 changes: 0 additions & 21 deletions .pre-commit-config.yaml

This file was deleted.

6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- Support for OCI image indexes and manifest (#12)

## [1.1.0] - 2024-01-30

### Added
Expand Down
54 changes: 25 additions & 29 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,50 +8,53 @@ authors = [
{ name = "Kiwix", email = "dev@kiwix.org" },
]
keywords = ["oci", "image", "docker", "kiwix"]
requires-python = ">=3.8"
requires-python = ">=3.10"
description = "Export docker image into tar file directly from registry API"
readme = "README.md"
license = {text = "GPL-3.0-or-later"}
classifiers = [
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
]
dependencies = [
"requests>=2,<3",
"pathvalidate==3.2.0",
"types-requests>=2,<3",
"pathvalidate>=3.2.0",
]
dynamic = ["version"]

[project.optional-dependencies]
scripts = [
"invoke==2.2.0",
"invoke==2.2.1",
]
lint = [
"black==24.1.1",
"ruff==0.1.15",
"black==26.1.0",
"ruff==0.15.4",
]
check = [
"pyright==1.1.349",
"pyright==1.1.408",
]
test = [
"pytest==8.0.0",
"coverage==7.4.1",
"pytest==9.0.2",
"coverage==7.13.4",
]
dev = [
"pre-commit==3.6.0",
"debugpy==1.6.7",
"pre-commit==4.5.1",
"debugpy==1.8.20",
"docker-export[scripts]",
"docker-export[lint]",
"docker-export[test]",
"docker-export[check]",
"docker-export[visual]",
]
visual = [
"humanfriendly>=8.0",
"humanfriendly>=10.0",
"types-humanfriendly>=10",
"progressbar2>=4.0"
]

Expand All @@ -71,7 +74,7 @@ exclude = [
]

[[tool.hatch.envs.default.matrix]]
python = ["3.8"]
python = ["3.10"]

[tool.hatch.envs.default]
features = ["dev"]
Expand All @@ -80,7 +83,7 @@ features = ["dev"]
features = ["scripts", "test", "visual"]

[[tool.hatch.envs.test.matrix]]
python = ["3.8", "3.9", "3.10", "3.11"]
python = ["3.10", "3.11", "3.12", "3.13", "3.14"]

[tool.hatch.envs.test.scripts]
run = "inv test --args '{args}'"
Expand All @@ -94,10 +97,8 @@ skip-install = false
features = ["scripts", "lint", "visual"]

[tool.hatch.envs.lint.scripts]
black = "inv lint-black --args '{args}'"
ruff = "inv lint-ruff --args '{args}'"
all = "inv lintall --args '{args}'"
fix-black = "inv fix-black --args '{args}'"
fix-ruff = "inv fix-ruff --args '{args}'"
fixall = "inv fixall --args '{args}'"

Expand All @@ -108,14 +109,13 @@ features = ["scripts", "check", "test", "visual"]
pyright = "inv check-pyright --args '{args}'"
all = "inv checkall --args '{args}'"

[tool.black]
line-length = 88
target-version = ['py38']

[tool.ruff]
target-version = "py38"
target-version = "py310"
line-length = 88
src = ["src"]

[tool.ruff.lint]
select = [
"A", # flake8-builtins
# "ANN", # flake8-annotations
Expand Down Expand Up @@ -194,17 +194,13 @@ unfixable = [
"F401",
]

[tool.ruff.isort]
[tool.ruff.lint.isort]
known-first-party = ["docker_export"]

[tool.ruff.flake8-bugbear]
# add exceptions to B008 for fastapi.
extend-immutable-calls = ["fastapi.Depends", "fastapi.Query"]

[tool.ruff.flake8-tidy-imports]
[tool.ruff.lint.flake8-tidy-imports]
ban-relative-imports = "all"

[tool.ruff.per-file-ignores]
[tool.ruff.lint.per-file-ignores]
# Tests can use magic values, assertions, and relative imports
"tests/**/*" = ["PLR2004", "S101", "TID252"]

Expand Down Expand Up @@ -236,6 +232,6 @@ exclude_lines = [
include = ["src", "tests", "tasks.py"]
exclude = [".env/**", ".venv/**"]
extraPaths = ["src"]
pythonVersion = "3.8"
pythonVersion = "3.10"
typeCheckingMode="strict"
reportImplicitStringConcatenation=false
32 changes: 20 additions & 12 deletions src/docker_export/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
except ImportError:
progressbar = None
try:
import humanfriendly
import humanfriendly # pyright: ignore [reportMissingTypeStubs]
except ImportError:
humanfriendly = None

Expand All @@ -37,6 +37,15 @@
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
logger = logging.getLogger("docker-export")
logging.getLogger("urllib3").setLevel(logging.WARNING)
CT_DOCKER_MANIFEST_LIST = "application/vnd.docker.distribution.manifest.list.v2+json"
CT_DOCKER_MANIFEST = "application/vnd.docker.distribution.manifest.v2+json"
CT_OCI_INDEXES = "application/vnd.oci.image.index.v1+json"
CT_OCI_MANIFEST = "application/vnd.oci.image.manifest.v1+json"
CT_OCI_IMAGE_CONFIG = "application/vnd.oci.image.config.v1+json"
CT_OCI_EMPTY = "application/vnd.oci.image.config.v1+json"
CT_OCI_LAYERS = (
"application/vnd.oci.image.layer.v1.tar,application/vnd.oci.image.layer.v1.tar+gzip"
)


class ImageNotFoundError(Exception): ...
Expand Down Expand Up @@ -128,8 +137,8 @@ def finish(self):
print("")


@dataclass
class Platform:
@dataclass(frozen=True)
class Platform: # noqa: PLW1641
architecture: str
os: str
variant: str
Expand Down Expand Up @@ -260,7 +269,7 @@ def url(self) -> str:
"hub.docker.com" if self.registry == "index.docker.io" else self.registry
)
prefix = "r/" if self.registry == "index.docker.io" else ""
return f"https://{domain}/{prefix}/{self.fullname}"
return f"https://{domain}/{prefix}{'/' if prefix else ''}{self.fullname}"

@classmethod
def parse(
Expand Down Expand Up @@ -384,7 +393,7 @@ def get_manifests(image: Image, auth: RegistryAuth):
f"/manifests/{image.reference}",
headers=dict(
**auth.headers,
**{"Accept": "application/vnd.docker.distribution.manifest.list.v2+json"},
**{"Accept": ", ".join([CT_DOCKER_MANIFEST_LIST, CT_OCI_INDEXES])},
),
timeout=REQUEST_TIMEOUT,
)
Expand Down Expand Up @@ -415,13 +424,12 @@ def get_layers_manifest_for(
f"/manifests/{reference}",
headers=dict(
**auth.headers,
**{"Accept": "application/vnd.docker.distribution.manifest.v2+json"},
**{"Accept": ", ".join([CT_DOCKER_MANIFEST, CT_OCI_MANIFEST])},
),
timeout=REQUEST_TIMEOUT,
)
if resp.status_code != http.HTTPStatus.OK:
raise OSError("HTTP {resp.status_code}: {resp.reason} -- {resp.text}")

raise OSError(f"HTTP {resp.status_code}: {resp.reason} -- {resp.text}")
return resp.json()


Expand All @@ -438,7 +446,7 @@ def get_layers_from_v1_manifest(
)

return {
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"mediaType": CT_DOCKER_MANIFEST,
"schemaVersion": 2,
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
Expand All @@ -447,7 +455,7 @@ def get_layers_from_v1_manifest(
},
"layers": [
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"mediaType": CT_DOCKER_MANIFEST,
"digest": layer["blobSum"],
# "size": None,
"platform": {"architecture": architecture, "os": os},
Expand Down Expand Up @@ -524,7 +532,7 @@ def download_layer_blob(
f"https://{image.registry}/v2/{image.fullname}/blobs/{layer_digest}",
headers=dict(
**auth.headers,
**{"Accept": "application/vnd.docker.distribution.manifest.v2+json"},
**{"Accept": CT_DOCKER_MANIFEST},
),
stream=True,
timeout=REQUEST_TIMEOUT,
Expand All @@ -536,7 +544,7 @@ def download_layer_blob(
layer["urls"][0],
headers=dict(
**auth.headers,
**{"Accept": "application/vnd.docker.distribution.manifest.v2+json"},
**{"Accept": CT_DOCKER_MANIFEST},
),
stream=True,
timeout=REQUEST_TIMEOUT,
Expand Down
Loading