dockerfile-analyzer 0.3.22__tar.gz → 0.3.24__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 (25) hide show
  1. dockerfile_analyzer-0.3.24/.cargo/config.toml +7 -0
  2. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.24}/.github/dependabot.yml +10 -2
  3. dockerfile_analyzer-0.3.24/.github/workflows/CI.yml +265 -0
  4. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.24}/Cargo.lock +13 -55
  5. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.24}/Cargo.toml +1 -2
  6. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.24}/PKG-INFO +1 -2
  7. dockerfile_analyzer-0.3.24/README.md +226 -0
  8. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.24}/dockerfile_analyzer.pyi +14 -15
  9. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.24}/pyproject.toml +0 -1
  10. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.24}/requirements-dev.txt +1 -0
  11. dockerfile_analyzer-0.3.24/src/analyzer.rs +1118 -0
  12. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.24}/src/lib.rs +9 -7
  13. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.24}/src/models.rs +107 -77
  14. dockerfile_analyzer-0.3.24/src/parse_utils.rs +575 -0
  15. dockerfile_analyzer-0.3.24/tests/fixtures/multistage.Dockerfile +38 -0
  16. dockerfile_analyzer-0.3.24/tests/test_analyze_dockerfile.py +197 -0
  17. dockerfile_analyzer-0.3.22/.github/workflows/CI.yml +0 -181
  18. dockerfile_analyzer-0.3.22/README.md +0 -285
  19. dockerfile_analyzer-0.3.22/src/analyzer.rs +0 -1513
  20. dockerfile_analyzer-0.3.22/src/parse_utils.rs +0 -571
  21. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.24}/.gitignore +0 -0
  22. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.24}/LICENSE +0 -0
  23. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.24}/example.py +0 -0
  24. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.24}/py.typed +0 -0
  25. {dockerfile_analyzer-0.3.22 → dockerfile_analyzer-0.3.24}/src/constants.rs +0 -0
@@ -0,0 +1,7 @@
1
+ # PyO3 links test and clippy binaries against the interpreter it discovers.
2
+ # `python3` on this machine is 3.12 without libpython3.12.so on the default
3
+ # linker path, so `cargo test` fails with "unable to find library -lpython3.12".
4
+ # Point Cargo at the project virtualenv. CI sets PYO3_PYTHON itself, which
5
+ # overrides this.
6
+ [env]
7
+ PYO3_PYTHON = { value = "env/bin/python", relative = true }
@@ -5,7 +5,15 @@
5
5
 
6
6
  version: 2
7
7
  updates:
8
- - package-ecosystem: "cargo" # See documentation for possible values
9
- directory: "/" # Location of package manifests
8
+ - package-ecosystem: "cargo"
9
+ directory: "/"
10
+ schedule:
11
+ interval: "weekly"
12
+ - package-ecosystem: "github-actions"
13
+ directory: "/"
14
+ schedule:
15
+ interval: "weekly"
16
+ - package-ecosystem: "pip"
17
+ directory: "/"
10
18
  schedule:
11
19
  interval: "weekly"
@@ -0,0 +1,265 @@
1
+ # Wheel and sdist jobs follow `maturin generate-ci github`.
2
+ # Action refs are pinned to a commit. The comment records the release tag.
3
+ # Lint, cargo test, and python test are project-specific.
4
+ name: CI
5
+
6
+ on:
7
+ push:
8
+ branches:
9
+ - main
10
+ tags:
11
+ - "*"
12
+ pull_request:
13
+ workflow_dispatch:
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ concurrency:
19
+ group: ${{ github.workflow }}-${{ github.ref }}
20
+ cancel-in-progress: true
21
+
22
+ jobs:
23
+ lint:
24
+ name: lint
25
+ runs-on: ubuntu-latest
26
+ steps:
27
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
28
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
29
+ with:
30
+ # Keep in sync with requires-python in pyproject.toml (PyO3 needs a
31
+ # matching libpython for clippy on --all-targets).
32
+ python-version: "3.14"
33
+ - uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 # master
34
+ with:
35
+ # A commit pin does not get the branch-name default of stable.
36
+ toolchain: stable
37
+ components: rustfmt, clippy
38
+ - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
39
+ - name: Check formatting
40
+ run: cargo fmt --all -- --check
41
+ - name: Clippy
42
+ run: cargo clippy --all-targets -- -D warnings
43
+ env:
44
+ # setup-python's interpreter is not always `python3` on PATH.
45
+ PYO3_PYTHON: python
46
+
47
+ test:
48
+ name: cargo test
49
+ runs-on: ubuntu-latest
50
+ steps:
51
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
52
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
53
+ with:
54
+ # Keep in sync with requires-python in pyproject.toml (PyO3 needs a
55
+ # matching libpython for linking the test binary).
56
+ python-version: "3.14"
57
+ - uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 # master
58
+ with:
59
+ # A commit pin does not get the branch-name default of stable.
60
+ toolchain: stable
61
+ - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
62
+ - name: Run tests
63
+ run: cargo test
64
+ env:
65
+ # setup-python's interpreter is not always `python3` on PATH.
66
+ PYO3_PYTHON: python
67
+
68
+ python-test:
69
+ name: python test
70
+ runs-on: ubuntu-latest
71
+ steps:
72
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
73
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
74
+ with:
75
+ # Keep in sync with requires-python in pyproject.toml.
76
+ python-version: "3.14"
77
+ - uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 # master
78
+ with:
79
+ # A commit pin does not get the branch-name default of stable.
80
+ toolchain: stable
81
+ - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
82
+ - name: Install dev dependencies
83
+ run: |
84
+ python -m venv .venv
85
+ source .venv/bin/activate
86
+ python -m pip install -r requirements-dev.txt
87
+ - name: Build and install extension
88
+ run: |
89
+ source .venv/bin/activate
90
+ maturin develop
91
+ - name: Run Python tests
92
+ run: |
93
+ source .venv/bin/activate
94
+ pytest -v
95
+
96
+ linux:
97
+ needs: [lint, test, python-test]
98
+ runs-on: ${{ matrix.platform.runner }}
99
+ strategy:
100
+ fail-fast: false
101
+ matrix:
102
+ platform:
103
+ - runner: ubuntu-22.04
104
+ target: x86_64
105
+ - runner: ubuntu-22.04
106
+ target: x86
107
+ - runner: ubuntu-22.04
108
+ target: aarch64
109
+ - runner: ubuntu-22.04
110
+ target: armv7
111
+ - runner: ubuntu-22.04
112
+ target: s390x
113
+ - runner: ubuntu-22.04
114
+ target: ppc64le
115
+ steps:
116
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
117
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
118
+ with:
119
+ python-version: 3.x
120
+ - name: Build wheels
121
+ uses: PyO3/maturin-action@v1
122
+ with:
123
+ target: ${{ matrix.platform.target }}
124
+ args: --release --out dist --find-interpreter
125
+ sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
126
+ manylinux: auto
127
+ - name: Upload wheels
128
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
129
+ with:
130
+ name: wheels-linux-${{ matrix.platform.target }}
131
+ path: dist
132
+
133
+ musllinux:
134
+ needs: [lint, test, python-test]
135
+ runs-on: ${{ matrix.platform.runner }}
136
+ strategy:
137
+ fail-fast: false
138
+ matrix:
139
+ platform:
140
+ - runner: ubuntu-22.04
141
+ target: x86_64
142
+ - runner: ubuntu-22.04
143
+ target: x86
144
+ - runner: ubuntu-22.04
145
+ target: aarch64
146
+ - runner: ubuntu-22.04
147
+ target: armv7
148
+ steps:
149
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
150
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
151
+ with:
152
+ python-version: 3.x
153
+ - name: Build wheels
154
+ uses: PyO3/maturin-action@v1
155
+ with:
156
+ target: ${{ matrix.platform.target }}
157
+ args: --release --out dist --find-interpreter
158
+ sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
159
+ manylinux: musllinux_1_2
160
+ - name: Upload wheels
161
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
162
+ with:
163
+ name: wheels-musllinux-${{ matrix.platform.target }}
164
+ path: dist
165
+
166
+ windows:
167
+ needs: [lint, test, python-test]
168
+ runs-on: ${{ matrix.platform.runner }}
169
+ strategy:
170
+ fail-fast: false
171
+ matrix:
172
+ platform:
173
+ - runner: windows-latest
174
+ target: x64
175
+ - runner: windows-latest
176
+ target: x86
177
+ steps:
178
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
179
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
180
+ with:
181
+ python-version: 3.x
182
+ architecture: ${{ matrix.platform.target }}
183
+ - name: Build wheels
184
+ uses: PyO3/maturin-action@v1
185
+ with:
186
+ target: ${{ matrix.platform.target }}
187
+ args: --release --out dist --find-interpreter
188
+ sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
189
+ - name: Upload wheels
190
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
191
+ with:
192
+ name: wheels-windows-${{ matrix.platform.target }}
193
+ path: dist
194
+
195
+ macos:
196
+ needs: [lint, test, python-test]
197
+ runs-on: ${{ matrix.platform.runner }}
198
+ strategy:
199
+ fail-fast: false
200
+ matrix:
201
+ platform:
202
+ - runner: macos-15-intel
203
+ target: x86_64
204
+ - runner: macos-14
205
+ target: aarch64
206
+ steps:
207
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
208
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
209
+ with:
210
+ python-version: 3.x
211
+ - name: Build wheels
212
+ uses: PyO3/maturin-action@v1
213
+ with:
214
+ target: ${{ matrix.platform.target }}
215
+ args: --release --out dist --find-interpreter
216
+ sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
217
+ - name: Upload wheels
218
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
219
+ with:
220
+ name: wheels-macos-${{ matrix.platform.target }}
221
+ path: dist
222
+
223
+ sdist:
224
+ needs: [lint, test, python-test]
225
+ runs-on: ubuntu-latest
226
+ steps:
227
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
228
+ - name: Build sdist
229
+ uses: PyO3/maturin-action@v1
230
+ with:
231
+ command: sdist
232
+ args: --out dist
233
+ - name: Upload sdist
234
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
235
+ with:
236
+ name: wheels-sdist
237
+ path: dist
238
+
239
+ release:
240
+ name: Release
241
+ runs-on: ubuntu-latest
242
+ if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }}
243
+ needs: [lint, test, python-test, linux, musllinux, windows, macos, sdist]
244
+ environment:
245
+ name: pypi
246
+ url: https://pypi.org/p/dockerfile-analyzer
247
+ permissions:
248
+ # Sign the release artifacts and mint the PyPI trusted-publishing token.
249
+ id-token: write
250
+ # Upload release artifacts.
251
+ contents: write
252
+ # Generate artifact attestation.
253
+ attestations: write
254
+ steps:
255
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
256
+ - name: Generate artifact attestation
257
+ uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
258
+ with:
259
+ subject-path: "wheels-*/*"
260
+ - name: Install uv
261
+ if: ${{ startsWith(github.ref, 'refs/tags/') }}
262
+ uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
263
+ - name: Publish to PyPI
264
+ if: ${{ startsWith(github.ref, 'refs/tags/') }}
265
+ run: uv publish --trusted-publishing always 'wheels-*/*'
@@ -14,12 +14,11 @@ dependencies = [
14
14
 
15
15
  [[package]]
16
16
  name = "dockerfile-analyzer"
17
- version = "0.3.22"
17
+ version = "0.3.24"
18
18
  dependencies = [
19
19
  "docker-image",
20
20
  "parse-dockerfile",
21
21
  "pyo3",
22
- "serde",
23
22
  "shlex",
24
23
  ]
25
24
 
@@ -73,9 +72,9 @@ dependencies = [
73
72
 
74
73
  [[package]]
75
74
  name = "pyo3"
76
- version = "0.29.2"
75
+ version = "0.29.3"
77
76
  source = "registry+https://github.com/rust-lang/crates.io-index"
78
- checksum = "4688ddedf473e32662b9b067670129a8afb8c18e351482c70d62ba4a88171e8b"
77
+ checksum = "700d18fa267b73b9b521fd7e13580e2f446916f176cacee1ab63fcc8191f1655"
79
78
  dependencies = [
80
79
  "libc",
81
80
  "once_cell",
@@ -87,18 +86,18 @@ dependencies = [
87
86
 
88
87
  [[package]]
89
88
  name = "pyo3-build-config"
90
- version = "0.29.2"
89
+ version = "0.29.3"
91
90
  source = "registry+https://github.com/rust-lang/crates.io-index"
92
- checksum = "f41027e41b4bd03f6e60f9f417fe24a6341a6bb744edd62b6f709f2a52ea30e9"
91
+ checksum = "7b3fc0c4d08f6bb10e71fe39dfb9e2f59c6eb6854e22ec8092f50c69a4499adb"
93
92
  dependencies = [
94
93
  "target-lexicon",
95
94
  ]
96
95
 
97
96
  [[package]]
98
97
  name = "pyo3-ffi"
99
- version = "0.29.2"
98
+ version = "0.29.3"
100
99
  source = "registry+https://github.com/rust-lang/crates.io-index"
101
- checksum = "e591a95526fead067432c3b3a33fc74770b87b1e04e73671090d9c2055a2b327"
100
+ checksum = "dfc0b8e19df29aad7086cf977bb0c2a2f143e30567eb113e9cf72b62ca698330"
102
101
  dependencies = [
103
102
  "libc",
104
103
  "pyo3-build-config",
@@ -106,26 +105,26 @@ dependencies = [
106
105
 
107
106
  [[package]]
108
107
  name = "pyo3-macros"
109
- version = "0.29.2"
108
+ version = "0.29.3"
110
109
  source = "registry+https://github.com/rust-lang/crates.io-index"
111
- checksum = "73225868fc1cd84eef2c3c230ddb91273bf1de46aeb8a4248da76d32a0924a1c"
110
+ checksum = "6100e8a4b5eba53afaa5ed078364851a0b2499c553a44c31026b929049b49dc6"
112
111
  dependencies = [
113
112
  "proc-macro2",
114
113
  "pyo3-macros-backend",
115
114
  "quote",
116
- "syn 2.0.105",
115
+ "syn",
117
116
  ]
118
117
 
119
118
  [[package]]
120
119
  name = "pyo3-macros-backend"
121
- version = "0.29.2"
120
+ version = "0.29.3"
122
121
  source = "registry+https://github.com/rust-lang/crates.io-index"
123
- checksum = "571575aa3749fa6216757dd47d2a3e7ef360f329a40f0666a9fbd14889024952"
122
+ checksum = "6143877a16e82b5a727b7127ff4cd86858a24a28d745d72f43e6f227c7b1bdb3"
124
123
  dependencies = [
125
124
  "heck",
126
125
  "proc-macro2",
127
126
  "quote",
128
- "syn 2.0.105",
127
+ "syn",
129
128
  ]
130
129
 
131
130
  [[package]]
@@ -162,36 +161,6 @@ version = "0.8.5"
162
161
  source = "registry+https://github.com/rust-lang/crates.io-index"
163
162
  checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
164
163
 
165
- [[package]]
166
- name = "serde"
167
- version = "1.0.229"
168
- source = "registry+https://github.com/rust-lang/crates.io-index"
169
- checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
170
- dependencies = [
171
- "serde_core",
172
- "serde_derive",
173
- ]
174
-
175
- [[package]]
176
- name = "serde_core"
177
- version = "1.0.229"
178
- source = "registry+https://github.com/rust-lang/crates.io-index"
179
- checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
180
- dependencies = [
181
- "serde_derive",
182
- ]
183
-
184
- [[package]]
185
- name = "serde_derive"
186
- version = "1.0.229"
187
- source = "registry+https://github.com/rust-lang/crates.io-index"
188
- checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
189
- dependencies = [
190
- "proc-macro2",
191
- "quote",
192
- "syn 3.0.1",
193
- ]
194
-
195
164
  [[package]]
196
165
  name = "shlex"
197
166
  version = "2.0.1"
@@ -215,17 +184,6 @@ dependencies = [
215
184
  "unicode-ident",
216
185
  ]
217
186
 
218
- [[package]]
219
- name = "syn"
220
- version = "3.0.1"
221
- source = "registry+https://github.com/rust-lang/crates.io-index"
222
- checksum = "5edbec4ed188954a10c12c038215f8ce7606b2d5c973cd8dc43e8795065c5f2f"
223
- dependencies = [
224
- "proc-macro2",
225
- "quote",
226
- "unicode-ident",
227
- ]
228
-
229
187
  [[package]]
230
188
  name = "target-lexicon"
231
189
  version = "0.13.4"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "dockerfile-analyzer"
3
- version = "0.3.22"
3
+ version = "0.3.24"
4
4
  edition = "2024"
5
5
  readme = "README.md"
6
6
 
@@ -14,4 +14,3 @@ pyo3 = "0.29.0"
14
14
  docker-image = {version = "0.2.1"}
15
15
  parse-dockerfile = { version = "0.1.7", default-features = false }
16
16
  shlex = "2.0.1"
17
- serde = { version = "1.0.228", features = ["derive"] }
@@ -1,9 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dockerfile-analyzer
3
- Version: 0.3.22
3
+ Version: 0.3.24
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
- Classifier: Programming Language :: Python :: Implementation :: PyPy
7
6
  License-File: LICENSE
8
7
  Summary: Analyzes Dockerfiles and provides comprehensive insights including stage information, base images, multistage build analysis, instruction statistics, environment variables, labels, arguments, and exposed ports.
9
8
  License-Expression: MIT
@@ -0,0 +1,226 @@
1
+ # Dockerfile Analyzer
2
+
3
+ ## Description
4
+
5
+ This Python package analyzes a Dockerfile and returns detailed analysis information. Some of the metadata returned include:
6
+
7
+ - Number of stages, named stages, and the final stage name
8
+ - Base images, including images pulled by `COPY --from` and `ADD --from`
9
+ - Multistage analysis, with numeric `--from` indexes resolved to stages
10
+ - Instructions statistics
11
+ - Environment variables, labels, and arguments
12
+ - Exposed ports
13
+
14
+ Image references that contain `$` are not expanded. Other image references are lowercased. `EXPOSE` values are returned as written, including protocol suffixes and ranges. A later `ARG NAME` without a default keeps an earlier default.
15
+
16
+ The code leverages the highly useful Rust crates `parse-dockerfile` and `docker-image`.
17
+
18
+ ## Example
19
+
20
+ This Dockerfile is short, but it uses the distinctions the analyzer is built to recover: a stage referenced by index, a stage used as a base image, an unused stage, an external `COPY --from` image, a digest-pinned image, an unexpanded variable, and an `ARG` redeclared without a default.
21
+
22
+ ### Sample Input Dockerfile
23
+
24
+ ```Dockerfile
25
+ # syntax=docker/dockerfile:1.7
26
+ ARG GO_VERSION=1.22
27
+ ARG ALPINE_VERSION=3.20
28
+ ARG VERSION=1.0.0
29
+
30
+ # Stage 0. A later COPY refers to this stage by index.
31
+ FROM alpine:${ALPINE_VERSION} AS tools
32
+ RUN apk add --no-cache ca-certificates
33
+
34
+ FROM golang:${GO_VERSION}-alpine AS build
35
+ WORKDIR /src
36
+ COPY go.mod go.sum ./
37
+ RUN go mod download
38
+ COPY . .
39
+ RUN CGO_ENABLED=0 go build -o /out/app ./cmd/app
40
+
41
+ # Uses the build stage as its base image, and is itself an ADD --from source.
42
+ FROM build AS assets
43
+ ADD README.md /docs/README.md
44
+
45
+ # Named, but no later instruction references it.
46
+ FROM alpine:${ALPINE_VERSION} AS debug
47
+ RUN apk add --no-cache busybox
48
+
49
+ # Unnamed, so final_stage is None. Digest is split into components.
50
+ FROM gcr.io/distroless/static-debian12:nonroot@sha256:2c8e1f5d9a3b7e6c4d1f0a8b5e2c9d7f3a6b1c4e8d2f5a9b3c7e1d4f6a8b0c2e
51
+ COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
52
+ COPY --from=build /out/app /app
53
+ ADD --from=assets /docs/README.md /docs/README.md
54
+ COPY --from=ghcr.io/acme/assets:1.4 /share /share
55
+ # Redeclared with no default, so the global default is kept.
56
+ ARG VERSION
57
+ ENV APP_VERSION=$VERSION
58
+ LABEL org.opencontainers.image.title="app" \
59
+ org.opencontainers.image.version="${VERSION}"
60
+ EXPOSE 8080/tcp 9090/udp
61
+ USER nonroot
62
+ ENTRYPOINT ["/app"]
63
+ CMD ["serve"]
64
+ ```
65
+
66
+ ### Sample Python Code
67
+
68
+ ```sh
69
+ pip install dockerfile-analyzer
70
+ ```
71
+
72
+ ```python
73
+ import json
74
+
75
+ import dockerfile_analyzer as da
76
+
77
+ dockerfile = open("Dockerfile").read()
78
+ analysis = da.analyze_dockerfile(dockerfile)
79
+
80
+ # --from=0 resolved to the tools stage. ghcr.io is an image, not a stage.
81
+ print(analysis.copy_from_stages) # ['build', 'tools']
82
+ print(analysis.copy_from_images[0].components.registry) # ghcr.io
83
+
84
+ # debug is named and never referenced. The final stage is unnamed.
85
+ print(analysis.multistage_analysis.unused_stages) # ['debug']
86
+ print(analysis.final_stage) # None
87
+
88
+ # The second ARG VERSION has no default, so 1.0.0 is kept.
89
+ print(analysis.args["VERSION"]) # 1.0.0
90
+
91
+ print(json.dumps(analysis.to_dict(), indent=2))
92
+ ```
93
+
94
+ ### Output
95
+
96
+ ```
97
+ ['build', 'tools']
98
+ ghcr.io
99
+ ['debug']
100
+ None
101
+ 1.0.0
102
+
103
+ ```json
104
+ {
105
+ "num_stages": 5,
106
+ "images": [
107
+ {
108
+ "full": "alpine:${ALPINE_VERSION}",
109
+ "components": null
110
+ },
111
+ {
112
+ "full": "build",
113
+ "components": {
114
+ "registry": null,
115
+ "name": "build",
116
+ "tag": null,
117
+ "digest": null
118
+ }
119
+ },
120
+ {
121
+ "full": "gcr.io/distroless/static-debian12:nonroot@sha256:2c8e1f5d9a3b7e6c4d1f0a8b5e2c9d7f3a6b1c4e8d2f5a9b3c7e1d4f6a8b0c2e",
122
+ "components": {
123
+ "registry": "gcr.io",
124
+ "name": "distroless/static-debian12",
125
+ "tag": "nonroot",
126
+ "digest": "sha256:2c8e1f5d9a3b7e6c4d1f0a8b5e2c9d7f3a6b1c4e8d2f5a9b3c7e1d4f6a8b0c2e"
127
+ }
128
+ },
129
+ {
130
+ "full": "golang:${GO_VERSION}-alpine",
131
+ "components": null
132
+ }
133
+ ],
134
+ "stage_names": [
135
+ "assets",
136
+ "build",
137
+ "debug",
138
+ "tools"
139
+ ],
140
+ "final_stage": null,
141
+ "copy_from_stages": [
142
+ "build",
143
+ "tools"
144
+ ],
145
+ "add_from_stages": [
146
+ "assets"
147
+ ],
148
+ "copy_from_images": [
149
+ {
150
+ "full": "ghcr.io/acme/assets:1.4",
151
+ "components": {
152
+ "registry": "ghcr.io",
153
+ "name": "acme/assets",
154
+ "tag": "1.4",
155
+ "digest": null
156
+ }
157
+ }
158
+ ],
159
+ "add_from_images": [],
160
+ "multistage_analysis": {
161
+ "is_multistage": true,
162
+ "stages_used_as_base_images": [
163
+ "build"
164
+ ],
165
+ "stages_copied_from": [
166
+ "build",
167
+ "tools"
168
+ ],
169
+ "stages_added_from": [
170
+ "assets"
171
+ ],
172
+ "unused_stages": [
173
+ "debug"
174
+ ]
175
+ },
176
+ "exposed_ports": [
177
+ "8080/tcp",
178
+ "9090/udp"
179
+ ],
180
+ "instructions": {
181
+ "total_count": 27,
182
+ "by_type": {
183
+ "ADD": 2,
184
+ "ARG": 4,
185
+ "CMD": 1,
186
+ "COPY": 5,
187
+ "ENTRYPOINT": 1,
188
+ "ENV": 1,
189
+ "EXPOSE": 1,
190
+ "FROM": 5,
191
+ "LABEL": 1,
192
+ "RUN": 4,
193
+ "USER": 1,
194
+ "WORKDIR": 1
195
+ }
196
+ },
197
+ "args": {
198
+ "ALPINE_VERSION": "3.20",
199
+ "GO_VERSION": "1.22",
200
+ "VERSION": "1.0.0"
201
+ },
202
+ "labels": {
203
+ "org.opencontainers.image.title": "app",
204
+ "org.opencontainers.image.version": "${VERSION}"
205
+ },
206
+ "env_vars": {
207
+ "APP_VERSION": "$VERSION"
208
+ }
209
+ }
210
+ ```
211
+
212
+ ## Developing
213
+
214
+ ```sh
215
+ python3 -m venv env
216
+ source env/bin/activate
217
+ pip install -r requirements-dev.txt
218
+ cargo fmt --all -- --check
219
+ cargo clippy --all-targets -- -D warnings
220
+ cargo test
221
+ maturin develop
222
+ pytest
223
+ ```
224
+
225
+ `cargo test` and `cargo clippy --all-targets` link against libpython. `.cargo/config.toml` points PyO3 at `env/bin/python`, which must be a shared Python build (`libpython3.x.so` present). `PYO3_PYTHON` overrides that path when set.
226
+