dockerfile-analyzer 0.3.22__tar.gz → 0.3.23__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (20) hide show
  1. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.23}/.github/workflows/CI.yml +67 -1
  2. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.23}/Cargo.lock +11 -11
  3. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.23}/Cargo.toml +1 -1
  4. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.23}/PKG-INFO +1 -1
  5. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.23}/README.md +3 -0
  6. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.23}/requirements-dev.txt +1 -0
  7. dockerfile_analyzer-0.3.23/tests/fixtures/multistage.Dockerfile +38 -0
  8. dockerfile_analyzer-0.3.23/tests/test_analyze_dockerfile.py +129 -0
  9. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.23}/.github/dependabot.yml +0 -0
  10. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.23}/.gitignore +0 -0
  11. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.23}/LICENSE +0 -0
  12. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.23}/dockerfile_analyzer.pyi +0 -0
  13. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.23}/example.py +0 -0
  14. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.23}/py.typed +0 -0
  15. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.23}/pyproject.toml +0 -0
  16. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.23}/src/analyzer.rs +0 -0
  17. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.23}/src/constants.rs +0 -0
  18. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.23}/src/lib.rs +0 -0
  19. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.23}/src/models.rs +0 -0
  20. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.23}/src/parse_utils.rs +0 -0
@@ -19,7 +19,69 @@ permissions:
19
19
  contents: read
20
20
 
21
21
  jobs:
22
+ lint:
23
+ name: lint
24
+ runs-on: ubuntu-latest
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+ - uses: actions/setup-python@v5
28
+ with:
29
+ # Keep in sync with requires-python in pyproject.toml (PyO3 needs a
30
+ # matching libpython for clippy on --all-targets).
31
+ python-version: "3.14"
32
+ - name: Install Rust toolchain
33
+ uses: dtolnay/rust-toolchain@stable
34
+ with:
35
+ components: rustfmt, clippy
36
+ - name: Check formatting
37
+ run: cargo fmt --all -- --check
38
+ - name: Clippy
39
+ run: cargo clippy --all-targets -- -D warnings
40
+
41
+ test:
42
+ name: cargo test
43
+ runs-on: ubuntu-latest
44
+ steps:
45
+ - uses: actions/checkout@v4
46
+ - uses: actions/setup-python@v5
47
+ with:
48
+ # Keep in sync with requires-python in pyproject.toml (PyO3 needs a
49
+ # matching libpython for linking the test binary).
50
+ python-version: "3.14"
51
+ - name: Install Rust toolchain
52
+ uses: dtolnay/rust-toolchain@stable
53
+ - name: Run tests
54
+ run: cargo test
55
+
56
+ python-test:
57
+ name: python test
58
+ runs-on: ubuntu-latest
59
+ steps:
60
+ - uses: actions/checkout@v4
61
+ - uses: actions/setup-python@v5
62
+ with:
63
+ # Keep in sync with requires-python in pyproject.toml.
64
+ python-version: "3.14"
65
+ - name: Install Rust toolchain
66
+ uses: dtolnay/rust-toolchain@stable
67
+ - name: Create virtual environment
68
+ # maturin develop requires an active virtualenv (not system/site-packages).
69
+ run: python -m venv .venv
70
+ - name: Install dev dependencies
71
+ run: |
72
+ source .venv/bin/activate
73
+ pip install -r requirements-dev.txt
74
+ - name: Build and install extension
75
+ run: |
76
+ source .venv/bin/activate
77
+ maturin develop
78
+ - name: Run Python tests
79
+ run: |
80
+ source .venv/bin/activate
81
+ pytest -v
82
+
22
83
  linux:
84
+ needs: [lint, test, python-test]
23
85
  runs-on: ${{ matrix.platform.runner }}
24
86
  strategy:
25
87
  matrix:
@@ -55,6 +117,7 @@ jobs:
55
117
  path: dist
56
118
 
57
119
  musllinux:
120
+ needs: [lint, test, python-test]
58
121
  runs-on: ${{ matrix.platform.runner }}
59
122
  strategy:
60
123
  matrix:
@@ -86,6 +149,7 @@ jobs:
86
149
  path: dist
87
150
 
88
151
  windows:
152
+ needs: [lint, test, python-test]
89
153
  runs-on: ${{ matrix.platform.runner }}
90
154
  strategy:
91
155
  matrix:
@@ -113,6 +177,7 @@ jobs:
113
177
  path: dist
114
178
 
115
179
  macos:
180
+ needs: [lint, test, python-test]
116
181
  runs-on: ${{ matrix.platform.runner }}
117
182
  strategy:
118
183
  matrix:
@@ -139,6 +204,7 @@ jobs:
139
204
  path: dist
140
205
 
141
206
  sdist:
207
+ needs: [lint, test, python-test]
142
208
  runs-on: ubuntu-latest
143
209
  steps:
144
210
  - uses: actions/checkout@v4
@@ -157,7 +223,7 @@ jobs:
157
223
  name: Release
158
224
  runs-on: ubuntu-latest
159
225
  if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }}
160
- needs: [linux, musllinux, windows, macos, sdist]
226
+ needs: [lint, test, python-test, linux, musllinux, windows, macos, sdist]
161
227
  permissions:
162
228
  # Use to sign the release artifacts
163
229
  id-token: write
@@ -14,7 +14,7 @@ dependencies = [
14
14
 
15
15
  [[package]]
16
16
  name = "dockerfile-analyzer"
17
- version = "0.3.22"
17
+ version = "0.3.23"
18
18
  dependencies = [
19
19
  "docker-image",
20
20
  "parse-dockerfile",
@@ -73,9 +73,9 @@ dependencies = [
73
73
 
74
74
  [[package]]
75
75
  name = "pyo3"
76
- version = "0.29.2"
76
+ version = "0.29.3"
77
77
  source = "registry+https://github.com/rust-lang/crates.io-index"
78
- checksum = "4688ddedf473e32662b9b067670129a8afb8c18e351482c70d62ba4a88171e8b"
78
+ checksum = "700d18fa267b73b9b521fd7e13580e2f446916f176cacee1ab63fcc8191f1655"
79
79
  dependencies = [
80
80
  "libc",
81
81
  "once_cell",
@@ -87,18 +87,18 @@ dependencies = [
87
87
 
88
88
  [[package]]
89
89
  name = "pyo3-build-config"
90
- version = "0.29.2"
90
+ version = "0.29.3"
91
91
  source = "registry+https://github.com/rust-lang/crates.io-index"
92
- checksum = "f41027e41b4bd03f6e60f9f417fe24a6341a6bb744edd62b6f709f2a52ea30e9"
92
+ checksum = "7b3fc0c4d08f6bb10e71fe39dfb9e2f59c6eb6854e22ec8092f50c69a4499adb"
93
93
  dependencies = [
94
94
  "target-lexicon",
95
95
  ]
96
96
 
97
97
  [[package]]
98
98
  name = "pyo3-ffi"
99
- version = "0.29.2"
99
+ version = "0.29.3"
100
100
  source = "registry+https://github.com/rust-lang/crates.io-index"
101
- checksum = "e591a95526fead067432c3b3a33fc74770b87b1e04e73671090d9c2055a2b327"
101
+ checksum = "dfc0b8e19df29aad7086cf977bb0c2a2f143e30567eb113e9cf72b62ca698330"
102
102
  dependencies = [
103
103
  "libc",
104
104
  "pyo3-build-config",
@@ -106,9 +106,9 @@ dependencies = [
106
106
 
107
107
  [[package]]
108
108
  name = "pyo3-macros"
109
- version = "0.29.2"
109
+ version = "0.29.3"
110
110
  source = "registry+https://github.com/rust-lang/crates.io-index"
111
- checksum = "73225868fc1cd84eef2c3c230ddb91273bf1de46aeb8a4248da76d32a0924a1c"
111
+ checksum = "6100e8a4b5eba53afaa5ed078364851a0b2499c553a44c31026b929049b49dc6"
112
112
  dependencies = [
113
113
  "proc-macro2",
114
114
  "pyo3-macros-backend",
@@ -118,9 +118,9 @@ dependencies = [
118
118
 
119
119
  [[package]]
120
120
  name = "pyo3-macros-backend"
121
- version = "0.29.2"
121
+ version = "0.29.3"
122
122
  source = "registry+https://github.com/rust-lang/crates.io-index"
123
- checksum = "571575aa3749fa6216757dd47d2a3e7ef360f329a40f0666a9fbd14889024952"
123
+ checksum = "6143877a16e82b5a727b7127ff4cd86858a24a28d745d72f43e6f227c7b1bdb3"
124
124
  dependencies = [
125
125
  "heck",
126
126
  "proc-macro2",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "dockerfile-analyzer"
3
- version = "0.3.22"
3
+ version = "0.3.23"
4
4
  edition = "2024"
5
5
  readme = "README.md"
6
6
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dockerfile-analyzer
3
- Version: 0.3.22
3
+ Version: 0.3.23
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Python :: Implementation :: PyPy
@@ -280,6 +280,9 @@ Analysis(num_stages=4, images=[Image(full="builder", components="ImageComponents
280
280
  python3 -m venv env
281
281
  source env/bin/activate
282
282
  pip install -r requirements-dev.txt
283
+ cargo fmt --all -- --check
284
+ cargo clippy --all-targets -- -D warnings
283
285
  cargo test
284
286
  maturin develop
287
+ pytest
285
288
  ```
@@ -1 +1,2 @@
1
1
  maturin>=1.14.0
2
+ pytest>=8.0
@@ -0,0 +1,38 @@
1
+ FROM docker.abc.com/base-images/python:3.13-debian@sha256:55f1d15ef4c37870e23c03e89ad238940b55c8ede9f13fac4b7d71c7955f1053 AS base
2
+
3
+ LABEL org.opencontainers.image.title="My App" \
4
+ org.opencontainers.image.version="1.0" \
5
+ org.opencontainers.image.authors="john@example.com"
6
+
7
+ ENV PYTHONPATH=/src \
8
+ PYTHONUNBUFFERED=1 \
9
+ REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt \
10
+ PATH="/home/appuser/.local/bin:$PATH"
11
+ WORKDIR /src
12
+ USER root:root
13
+
14
+ RUN apt-get update && \
15
+ apt-get install --no-install-recommends -y postgresql-client curl git && \
16
+ apt-get autoremove -y && \
17
+ apt-get clean && \
18
+ rm -rf /var/lib/apt/lists/*
19
+
20
+ RUN pip install --no-cache-dir --upgrade pip
21
+ COPY --chown=1000:1000 requirements.txt ./
22
+ RUN pip install --no-cache-dir -r requirements.txt
23
+
24
+ FROM base AS test
25
+ COPY --chown=1000:1000 test-requirements.txt ./
26
+ USER 1000:1000
27
+ RUN pip install --user --no-cache-dir -r test-requirements.txt
28
+ COPY ./app ./app
29
+ COPY ./test ./test
30
+
31
+ FROM base
32
+ COPY --chown=1000:1000 ./app ./app
33
+ USER 1000:1000
34
+ ARG GIT_COMMIT
35
+ ENV GIT_COMMIT=$GIT_COMMIT
36
+ EXPOSE 5000
37
+
38
+ CMD ["uvicorn", "--host", "0.0.0.0", "--port", "5000", "app.main:app"]
@@ -0,0 +1,129 @@
1
+ """Python integration tests for the dockerfile_analyzer extension module."""
2
+
3
+ from pathlib import Path
4
+
5
+ import pytest
6
+
7
+ import dockerfile_analyzer as da
8
+
9
+ FIXTURES = Path(__file__).parent / "fixtures"
10
+
11
+
12
+ def _read_fixture(name: str) -> str:
13
+ return (FIXTURES / name).read_text()
14
+
15
+
16
+ def test_analyze_multistage_dockerfile():
17
+ analysis = da.analyze_dockerfile(_read_fixture("multistage.Dockerfile"))
18
+
19
+ assert analysis.num_stages == 3
20
+ assert analysis.stage_names == ["base", "test"]
21
+ assert analysis.copy_from_stages == []
22
+ assert analysis.add_from_stages == []
23
+ assert analysis.exposed_ports == ["5000"]
24
+
25
+ assert analysis.multistage_analysis.is_multistage is True
26
+ assert analysis.multistage_analysis.stages_used_as_base_images == ["base"]
27
+ assert analysis.multistage_analysis.stages_copied_from == []
28
+ assert analysis.multistage_analysis.stages_added_from == []
29
+ assert analysis.multistage_analysis.unused_stages == ["test"]
30
+
31
+ assert analysis.instructions.total_count == 22
32
+ assert analysis.instructions.by_type == {
33
+ "ARG": 1,
34
+ "CMD": 1,
35
+ "COPY": 5,
36
+ "ENV": 2,
37
+ "EXPOSE": 1,
38
+ "FROM": 3,
39
+ "LABEL": 1,
40
+ "RUN": 4,
41
+ "USER": 3,
42
+ "WORKDIR": 1,
43
+ }
44
+
45
+ assert analysis.args == {"GIT_COMMIT": None}
46
+ assert analysis.labels == {
47
+ "org.opencontainers.image.title": "My App",
48
+ "org.opencontainers.image.version": "1.0",
49
+ "org.opencontainers.image.authors": "john@example.com",
50
+ }
51
+ assert analysis.env_vars == {
52
+ "PYTHONPATH": "/src",
53
+ "PYTHONUNBUFFERED": "1",
54
+ "REQUESTS_CA_BUNDLE": "/etc/ssl/certs/ca-certificates.crt",
55
+ "PATH": "/home/appuser/.local/bin:$PATH",
56
+ "GIT_COMMIT": "$GIT_COMMIT",
57
+ }
58
+
59
+ assert len(analysis.images) == 2
60
+ base_stage_image, external_image = analysis.images
61
+
62
+ assert base_stage_image.full == "base"
63
+ assert base_stage_image.components is not None
64
+ assert base_stage_image.components.registry is None
65
+ assert base_stage_image.components.name == "base"
66
+ assert base_stage_image.components.tag is None
67
+ assert base_stage_image.components.digest is None
68
+
69
+ assert (
70
+ external_image.full
71
+ == "docker.abc.com/base-images/python:3.13-debian@sha256:55f1d15ef4c37870e23c03e89ad238940b55c8ede9f13fac4b7d71c7955f1053"
72
+ )
73
+ assert external_image.components is not None
74
+ assert external_image.components.registry == "docker.abc.com"
75
+ assert external_image.components.name == "base-images/python"
76
+ assert external_image.components.tag == "3.13-debian"
77
+ assert (
78
+ external_image.components.digest
79
+ == "sha256:55f1d15ef4c37870e23c03e89ad238940b55c8ede9f13fac4b7d71c7955f1053"
80
+ )
81
+
82
+
83
+ def test_analyze_multistage_to_dict():
84
+ analysis = da.analyze_dockerfile(_read_fixture("multistage.Dockerfile"))
85
+ data = analysis.to_dict()
86
+
87
+ assert data["num_stages"] == 3
88
+ assert data["stage_names"] == ["base", "test"]
89
+ assert data["exposed_ports"] == ["5000"]
90
+ assert data["multistage_analysis"]["is_multistage"] is True
91
+ assert data["multistage_analysis"]["unused_stages"] == ["test"]
92
+ assert data["instructions"]["total_count"] == 22
93
+ assert data["args"]["GIT_COMMIT"] is None
94
+ assert data["env_vars"]["PYTHONPATH"] == "/src"
95
+ assert data["images"][1]["components"]["registry"] == "docker.abc.com"
96
+
97
+
98
+ def test_analyze_single_stage_dockerfile():
99
+ dockerfile = """\
100
+ FROM node:20-alpine
101
+ WORKDIR /app
102
+ COPY package*.json ./
103
+ RUN npm install
104
+ COPY . .
105
+ EXPOSE 3000
106
+ CMD ["npm", "start"]
107
+ """
108
+ analysis = da.analyze_dockerfile(dockerfile)
109
+
110
+ assert analysis.num_stages == 1
111
+ assert analysis.stage_names == []
112
+ assert analysis.multistage_analysis.is_multistage is False
113
+ assert analysis.exposed_ports == ["3000"]
114
+ assert analysis.instructions.by_type["FROM"] == 1
115
+ assert analysis.instructions.by_type["RUN"] == 1
116
+ assert analysis.images[0].full == "node:20-alpine"
117
+ assert analysis.images[0].components is not None
118
+ assert analysis.images[0].components.name == "node"
119
+ assert analysis.images[0].components.tag == "20-alpine"
120
+
121
+
122
+ def test_analyze_invalid_dockerfile_raises():
123
+ with pytest.raises(ValueError, match="unknown instruction"):
124
+ da.analyze_dockerfile("invalid dockerfile content")
125
+
126
+
127
+ def test_analyze_empty_dockerfile_raises():
128
+ with pytest.raises(ValueError):
129
+ da.analyze_dockerfile("")