dockerfile-analyzer 0.3.23__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 (24) hide show
  1. dockerfile_analyzer-0.3.24/.cargo/config.toml +7 -0
  2. {dockerfile_analyzer-0.3.23 → dockerfile_analyzer-0.3.24}/.github/dependabot.yml +10 -2
  3. {dockerfile_analyzer-0.3.23 → dockerfile_analyzer-0.3.24}/.github/workflows/CI.yml +65 -47
  4. {dockerfile_analyzer-0.3.23 → dockerfile_analyzer-0.3.24}/Cargo.lock +3 -45
  5. {dockerfile_analyzer-0.3.23 → dockerfile_analyzer-0.3.24}/Cargo.toml +1 -2
  6. {dockerfile_analyzer-0.3.23 → dockerfile_analyzer-0.3.24}/PKG-INFO +1 -2
  7. dockerfile_analyzer-0.3.24/README.md +226 -0
  8. {dockerfile_analyzer-0.3.23 → dockerfile_analyzer-0.3.24}/dockerfile_analyzer.pyi +14 -15
  9. {dockerfile_analyzer-0.3.23 → dockerfile_analyzer-0.3.24}/pyproject.toml +0 -1
  10. dockerfile_analyzer-0.3.24/src/analyzer.rs +1118 -0
  11. {dockerfile_analyzer-0.3.23 → dockerfile_analyzer-0.3.24}/src/lib.rs +9 -7
  12. {dockerfile_analyzer-0.3.23 → dockerfile_analyzer-0.3.24}/src/models.rs +107 -77
  13. dockerfile_analyzer-0.3.24/src/parse_utils.rs +575 -0
  14. {dockerfile_analyzer-0.3.23 → dockerfile_analyzer-0.3.24}/tests/test_analyze_dockerfile.py +68 -0
  15. dockerfile_analyzer-0.3.23/README.md +0 -288
  16. dockerfile_analyzer-0.3.23/src/analyzer.rs +0 -1513
  17. dockerfile_analyzer-0.3.23/src/parse_utils.rs +0 -571
  18. {dockerfile_analyzer-0.3.23 → dockerfile_analyzer-0.3.24}/.gitignore +0 -0
  19. {dockerfile_analyzer-0.3.23 → dockerfile_analyzer-0.3.24}/LICENSE +0 -0
  20. {dockerfile_analyzer-0.3.23 → dockerfile_analyzer-0.3.24}/example.py +0 -0
  21. {dockerfile_analyzer-0.3.23 → dockerfile_analyzer-0.3.24}/py.typed +0 -0
  22. {dockerfile_analyzer-0.3.23 → dockerfile_analyzer-0.3.24}/requirements-dev.txt +0 -0
  23. {dockerfile_analyzer-0.3.23 → dockerfile_analyzer-0.3.24}/src/constants.rs +0 -0
  24. {dockerfile_analyzer-0.3.23 → dockerfile_analyzer-0.3.24}/tests/fixtures/multistage.Dockerfile +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"
@@ -1,15 +1,12 @@
1
- # This file is autogenerated by maturin v1.9.3
2
- # To update, run
3
- #
4
- # maturin generate-ci github
5
- #
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.
6
4
  name: CI
7
5
 
8
6
  on:
9
7
  push:
10
8
  branches:
11
9
  - main
12
- - master
13
10
  tags:
14
11
  - "*"
15
12
  pull_request:
@@ -18,59 +15,75 @@ on:
18
15
  permissions:
19
16
  contents: read
20
17
 
18
+ concurrency:
19
+ group: ${{ github.workflow }}-${{ github.ref }}
20
+ cancel-in-progress: true
21
+
21
22
  jobs:
22
23
  lint:
23
24
  name: lint
24
25
  runs-on: ubuntu-latest
25
26
  steps:
26
- - uses: actions/checkout@v4
27
- - uses: actions/setup-python@v5
27
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
28
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
28
29
  with:
29
30
  # Keep in sync with requires-python in pyproject.toml (PyO3 needs a
30
31
  # matching libpython for clippy on --all-targets).
31
32
  python-version: "3.14"
32
- - name: Install Rust toolchain
33
- uses: dtolnay/rust-toolchain@stable
33
+ - uses: dtolnay/rust-toolchain@7e38f4b43b4db5c8dd498af069a4f6196df1d067 # master
34
34
  with:
35
+ # A commit pin does not get the branch-name default of stable.
36
+ toolchain: stable
35
37
  components: rustfmt, clippy
38
+ - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
36
39
  - name: Check formatting
37
40
  run: cargo fmt --all -- --check
38
41
  - name: Clippy
39
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
40
46
 
41
47
  test:
42
48
  name: cargo test
43
49
  runs-on: ubuntu-latest
44
50
  steps:
45
- - uses: actions/checkout@v4
46
- - uses: actions/setup-python@v5
51
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
52
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
47
53
  with:
48
54
  # Keep in sync with requires-python in pyproject.toml (PyO3 needs a
49
55
  # matching libpython for linking the test binary).
50
56
  python-version: "3.14"
51
- - name: Install Rust toolchain
52
- uses: dtolnay/rust-toolchain@stable
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
53
62
  - name: Run tests
54
63
  run: cargo test
64
+ env:
65
+ # setup-python's interpreter is not always `python3` on PATH.
66
+ PYO3_PYTHON: python
55
67
 
56
68
  python-test:
57
69
  name: python test
58
70
  runs-on: ubuntu-latest
59
71
  steps:
60
- - uses: actions/checkout@v4
61
- - uses: actions/setup-python@v5
72
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
73
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
62
74
  with:
63
75
  # Keep in sync with requires-python in pyproject.toml.
64
76
  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
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
70
82
  - name: Install dev dependencies
71
83
  run: |
84
+ python -m venv .venv
72
85
  source .venv/bin/activate
73
- pip install -r requirements-dev.txt
86
+ python -m pip install -r requirements-dev.txt
74
87
  - name: Build and install extension
75
88
  run: |
76
89
  source .venv/bin/activate
@@ -84,6 +97,7 @@ jobs:
84
97
  needs: [lint, test, python-test]
85
98
  runs-on: ${{ matrix.platform.runner }}
86
99
  strategy:
100
+ fail-fast: false
87
101
  matrix:
88
102
  platform:
89
103
  - runner: ubuntu-22.04
@@ -99,8 +113,8 @@ jobs:
99
113
  - runner: ubuntu-22.04
100
114
  target: ppc64le
101
115
  steps:
102
- - uses: actions/checkout@v4
103
- - uses: actions/setup-python@v5
116
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
117
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
104
118
  with:
105
119
  python-version: 3.x
106
120
  - name: Build wheels
@@ -111,7 +125,7 @@ jobs:
111
125
  sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
112
126
  manylinux: auto
113
127
  - name: Upload wheels
114
- uses: actions/upload-artifact@v4
128
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
115
129
  with:
116
130
  name: wheels-linux-${{ matrix.platform.target }}
117
131
  path: dist
@@ -120,6 +134,7 @@ jobs:
120
134
  needs: [lint, test, python-test]
121
135
  runs-on: ${{ matrix.platform.runner }}
122
136
  strategy:
137
+ fail-fast: false
123
138
  matrix:
124
139
  platform:
125
140
  - runner: ubuntu-22.04
@@ -131,8 +146,8 @@ jobs:
131
146
  - runner: ubuntu-22.04
132
147
  target: armv7
133
148
  steps:
134
- - uses: actions/checkout@v4
135
- - uses: actions/setup-python@v5
149
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
150
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
136
151
  with:
137
152
  python-version: 3.x
138
153
  - name: Build wheels
@@ -143,7 +158,7 @@ jobs:
143
158
  sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
144
159
  manylinux: musllinux_1_2
145
160
  - name: Upload wheels
146
- uses: actions/upload-artifact@v4
161
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
147
162
  with:
148
163
  name: wheels-musllinux-${{ matrix.platform.target }}
149
164
  path: dist
@@ -152,6 +167,7 @@ jobs:
152
167
  needs: [lint, test, python-test]
153
168
  runs-on: ${{ matrix.platform.runner }}
154
169
  strategy:
170
+ fail-fast: false
155
171
  matrix:
156
172
  platform:
157
173
  - runner: windows-latest
@@ -159,8 +175,8 @@ jobs:
159
175
  - runner: windows-latest
160
176
  target: x86
161
177
  steps:
162
- - uses: actions/checkout@v4
163
- - uses: actions/setup-python@v5
178
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
179
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
164
180
  with:
165
181
  python-version: 3.x
166
182
  architecture: ${{ matrix.platform.target }}
@@ -171,7 +187,7 @@ jobs:
171
187
  args: --release --out dist --find-interpreter
172
188
  sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
173
189
  - name: Upload wheels
174
- uses: actions/upload-artifact@v4
190
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
175
191
  with:
176
192
  name: wheels-windows-${{ matrix.platform.target }}
177
193
  path: dist
@@ -180,6 +196,7 @@ jobs:
180
196
  needs: [lint, test, python-test]
181
197
  runs-on: ${{ matrix.platform.runner }}
182
198
  strategy:
199
+ fail-fast: false
183
200
  matrix:
184
201
  platform:
185
202
  - runner: macos-15-intel
@@ -187,8 +204,8 @@ jobs:
187
204
  - runner: macos-14
188
205
  target: aarch64
189
206
  steps:
190
- - uses: actions/checkout@v4
191
- - uses: actions/setup-python@v5
207
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
208
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
192
209
  with:
193
210
  python-version: 3.x
194
211
  - name: Build wheels
@@ -198,7 +215,7 @@ jobs:
198
215
  args: --release --out dist --find-interpreter
199
216
  sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
200
217
  - name: Upload wheels
201
- uses: actions/upload-artifact@v4
218
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
202
219
  with:
203
220
  name: wheels-macos-${{ matrix.platform.target }}
204
221
  path: dist
@@ -207,14 +224,14 @@ jobs:
207
224
  needs: [lint, test, python-test]
208
225
  runs-on: ubuntu-latest
209
226
  steps:
210
- - uses: actions/checkout@v4
227
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
211
228
  - name: Build sdist
212
229
  uses: PyO3/maturin-action@v1
213
230
  with:
214
231
  command: sdist
215
232
  args: --out dist
216
233
  - name: Upload sdist
217
- uses: actions/upload-artifact@v4
234
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
218
235
  with:
219
236
  name: wheels-sdist
220
237
  path: dist
@@ -224,24 +241,25 @@ jobs:
224
241
  runs-on: ubuntu-latest
225
242
  if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }}
226
243
  needs: [lint, test, python-test, linux, musllinux, windows, macos, sdist]
244
+ environment:
245
+ name: pypi
246
+ url: https://pypi.org/p/dockerfile-analyzer
227
247
  permissions:
228
- # Use to sign the release artifacts
248
+ # Sign the release artifacts and mint the PyPI trusted-publishing token.
229
249
  id-token: write
230
- # Used to upload release artifacts
250
+ # Upload release artifacts.
231
251
  contents: write
232
- # Used to generate artifact attestation
252
+ # Generate artifact attestation.
233
253
  attestations: write
234
254
  steps:
235
- - uses: actions/download-artifact@v4
255
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
236
256
  - name: Generate artifact attestation
237
- uses: actions/attest-build-provenance@v2
257
+ uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
238
258
  with:
239
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
240
263
  - name: Publish to PyPI
241
264
  if: ${{ startsWith(github.ref, 'refs/tags/') }}
242
- uses: PyO3/maturin-action@v1
243
- env:
244
- MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
245
- with:
246
- command: upload
247
- args: --non-interactive --skip-existing wheels-*/*
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.23"
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
 
@@ -113,7 +112,7 @@ 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]]
@@ -125,7 +124,7 @@ 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.23"
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.23
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
+
@@ -7,13 +7,17 @@ def analyze_dockerfile(body: str) -> Analysis:
7
7
  dockerfile_content (str): The content of the Dockerfile to analyze
8
8
 
9
9
  Returns:
10
- Analysis: A comprehensive analysis object containing information about:
11
- - Number of stages and stage names
12
- - Base images used
13
- - Multistage analysis (if applicable)
14
- - Instructions statistics
15
- - Environment variables, labels, and arguments
16
- - Exposed ports
10
+ Analysis: Stages, images, instruction counts, ARG/ENV/LABEL values,
11
+ and exposed ports. ``COPY --from`` and ``ADD --from`` indexes are
12
+ resolved to stages. External ``--from`` images are returned separately
13
+ from stage names.
14
+
15
+ Image references that do not contain a variable are lowercased.
16
+ ``${VAR}`` and ``$VAR`` are not expanded. ``EXPOSE`` values are raw
17
+ tokens, including protocol suffixes and ranges. ``stage_names`` lists
18
+ named stages only; ``final_stage`` is the last stage name, or None
19
+ when it is unnamed. A later ``ARG NAME`` without a value keeps an
20
+ earlier default.
17
21
 
18
22
  Raises:
19
23
  ValueError: If the dockerfile content is empty or invalid
@@ -31,8 +35,11 @@ class Analysis:
31
35
  num_stages: int
32
36
  images: List[Image]
33
37
  stage_names: List[str]
38
+ final_stage: Optional[str]
34
39
  copy_from_stages: List[str]
35
40
  add_from_stages: List[str]
41
+ copy_from_images: List[Image]
42
+ add_from_images: List[Image]
36
43
  multistage_analysis: MultistageAnalysis
37
44
  exposed_ports: List[str]
38
45
  instructions: InstructionStats
@@ -75,11 +82,3 @@ class MultistageAnalysis:
75
82
 
76
83
  def to_dict(self) -> Dict[str, Any]: ...
77
84
  def __repr__(self) -> str: ...
78
-
79
- class KeyValueInstr:
80
- args: Dict[str, Optional[str]]
81
- labels: Dict[str, str]
82
- env_vars: Dict[str, str]
83
-
84
- def to_dict(self) -> Dict[str, Any]: ...
85
- def __repr__(self) -> str: ...