docker-devtools 0.0.1__tar.gz → 0.1.0__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 (172) hide show
  1. docker_devtools-0.1.0/PKG-INFO +414 -0
  2. docker_devtools-0.1.0/README.md +386 -0
  3. docker_devtools-0.1.0/cmd/docker-devtools/context.go +145 -0
  4. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/cmd/docker-devtools/image.go +12 -1
  5. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/cmd/docker-devtools/main.go +14 -3
  6. docker_devtools-0.1.0/cmd/docker-devtools/registry.go +111 -0
  7. docker_devtools-0.1.0/cmd/docker-devtools/registry_test.go +26 -0
  8. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/hatch_build.py +1 -1
  9. docker_devtools-0.1.0/internal/imgref/dockerfile.go +220 -0
  10. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/internal/imgref/imgref.go +4 -0
  11. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/internal/imgref/imgref_test.go +18 -5
  12. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/internal/imgref/scan.go +17 -2
  13. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/internal/imgupdate/tagpolicy.go +38 -0
  14. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/internal/imgupdate/tagpolicy_test.go +43 -0
  15. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/internal/imgupdate/update_test.go +40 -5
  16. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/internal/plugin/plugin.go +1 -1
  17. docker_devtools-0.1.0/internal/registry/netrc.go +124 -0
  18. docker_devtools-0.1.0/internal/registry/netrc_test.go +47 -0
  19. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/internal/registry/registry.go +12 -2
  20. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/scripts/completions.sh +3 -3
  21. docker_devtools-0.1.0/scripts/conformance.sh +86 -0
  22. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/scripts/test-fresh-clone.sh +1 -1
  23. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/src/docker_devtools/__init__.py +2 -2
  24. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/src/docker_devtools/_version.py +2 -2
  25. docker_devtools-0.1.0/testdata/dctx/copysubset/all.golden +17 -0
  26. docker_devtools-0.1.0/testdata/dctx/copysubset/context/.dockerignore +1 -0
  27. docker_devtools-0.1.0/testdata/dctx/copysubset/context/Dockerfile +6 -0
  28. docker_devtools-0.1.0/testdata/dctx/copysubset/context/excluded.txt +1 -0
  29. docker_devtools-0.1.0/testdata/dctx/copysubset/context/heavy/blob.bin +1 -0
  30. docker_devtools-0.1.0/testdata/dctx/copysubset/context/keep.txt +1 -0
  31. docker_devtools-0.1.0/testdata/dctx/copysubset/context/sub/inside.txt +1 -0
  32. docker_devtools-0.1.0/testdata/dctx/copysubset/context/sub/nested/deep.txt +1 -0
  33. docker_devtools-0.1.0/testdata/dctx/copysubset/context/unreferenced.txt +1 -0
  34. docker_devtools-0.1.0/testdata/dctx/copysubset/ignored.golden +7 -0
  35. docker_devtools-0.1.0/testdata/dctx/copysubset/included.golden +11 -0
  36. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/lowercase/all.golden +1 -0
  37. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/lowercase/ignored.golden +1 -0
  38. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/lowercase/included.golden +1 -0
  39. docker_devtools-0.1.0/testdata/dctx/nesteddockerfile/all.golden +13 -0
  40. docker_devtools-0.1.0/testdata/dctx/nesteddockerfile/case.json +1 -0
  41. docker_devtools-0.1.0/testdata/dctx/nesteddockerfile/context/docker/Dockerfile.dev.dockerignore +1 -0
  42. docker_devtools-0.1.0/testdata/dctx/nesteddockerfile/ignored.golden +7 -0
  43. docker_devtools-0.1.0/testdata/dctx/nesteddockerfile/included.golden +12 -0
  44. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/none/all.golden +1 -0
  45. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/none/ignored.golden +1 -0
  46. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/none/included.golden +1 -0
  47. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/order/all.golden +1 -0
  48. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/order/ignored.golden +1 -0
  49. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/order/included.golden +1 -0
  50. docker_devtools-0.1.0/testdata/dctx/outofcontext/all.golden +12 -0
  51. docker_devtools-0.1.0/testdata/dctx/outofcontext/build.Dockerfile.dockerignore +1 -0
  52. docker_devtools-0.1.0/testdata/dctx/outofcontext/case.json +1 -0
  53. docker_devtools-0.1.0/testdata/dctx/outofcontext/context/.dockerignore +1 -0
  54. docker_devtools-0.1.0/testdata/dctx/outofcontext/context/beside-only +1 -0
  55. docker_devtools-0.1.0/testdata/dctx/outofcontext/context/build.Dockerfile.dockerignore +1 -0
  56. docker_devtools-0.1.0/testdata/dctx/outofcontext/context/decoy-only +1 -0
  57. docker_devtools-0.1.0/testdata/dctx/outofcontext/context/keep +1 -0
  58. docker_devtools-0.1.0/testdata/dctx/outofcontext/context/root-only +1 -0
  59. docker_devtools-0.1.0/testdata/dctx/outofcontext/ignored.golden +7 -0
  60. docker_devtools-0.1.0/testdata/dctx/outofcontext/included.golden +11 -0
  61. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/percent/all.golden +1 -0
  62. docker_devtools-0.1.0/testdata/dctx/percent/case.json +1 -0
  63. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/percent/ignored.golden +1 -0
  64. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/percent/included.golden +1 -0
  65. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/perdockerfile/all.golden +1 -0
  66. docker_devtools-0.1.0/testdata/dctx/perdockerfile/case.json +1 -0
  67. docker_devtools-0.1.0/testdata/dctx/perdockerfile/context/.dockerignore +1 -0
  68. docker_devtools-0.1.0/testdata/dctx/perdockerfile/context/only-default +1 -0
  69. docker_devtools-0.1.0/testdata/dctx/perdockerfile/context/only-prj1 +1 -0
  70. docker_devtools-0.1.0/testdata/dctx/perdockerfile/context/other +1 -0
  71. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/perdockerfile/ignored.golden +1 -0
  72. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/perdockerfile/included.golden +1 -0
  73. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/pycache/all.golden +1 -0
  74. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/pycache/ignored.golden +1 -0
  75. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/pycache/included.golden +1 -0
  76. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/reinclude/all.golden +1 -0
  77. docker_devtools-0.1.0/testdata/dctx/reinclude/context/Dockerfile +2 -0
  78. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/reinclude/ignored.golden +1 -0
  79. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/reinclude/included.golden +1 -0
  80. docker_devtools-0.1.0/testdata/dctx/targetstage/all.golden +12 -0
  81. docker_devtools-0.1.0/testdata/dctx/targetstage/case.json +1 -0
  82. docker_devtools-0.1.0/testdata/dctx/targetstage/context/Dockerfile +7 -0
  83. docker_devtools-0.1.0/testdata/dctx/targetstage/context/app/main.go +1 -0
  84. docker_devtools-0.1.0/testdata/dctx/targetstage/context/vendor/big.a +1 -0
  85. docker_devtools-0.1.0/testdata/dctx/targetstage/context/vendor/big.b +1 -0
  86. docker_devtools-0.1.0/testdata/dctx/targetstage/ignored.golden +6 -0
  87. docker_devtools-0.1.0/testdata/dctx/targetstage/included.golden +9 -0
  88. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/trailing/all.golden +1 -0
  89. docker_devtools-0.1.0/testdata/dctx/trailing/context/Dockerfile +2 -0
  90. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/trailing/ignored.golden +1 -0
  91. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/trailing/included.golden +1 -0
  92. docker_devtools-0.1.0/testdata/dctx/unreachedstage/all.golden +12 -0
  93. docker_devtools-0.1.0/testdata/dctx/unreachedstage/context/Dockerfile +7 -0
  94. docker_devtools-0.1.0/testdata/dctx/unreachedstage/context/app/main.go +1 -0
  95. docker_devtools-0.1.0/testdata/dctx/unreachedstage/context/vendor/big.a +1 -0
  96. docker_devtools-0.1.0/testdata/dctx/unreachedstage/context/vendor/big.b +1 -0
  97. docker_devtools-0.1.0/testdata/dctx/unreachedstage/ignored.golden +6 -0
  98. docker_devtools-0.1.0/testdata/dctx/unreachedstage/included.golden +8 -0
  99. docker_devtools-0.1.0/testdata/imageref/argbase/Dockerfile +25 -0
  100. docker_devtools-0.1.0/testdata/imageref/argbase/expected.txt +5 -0
  101. docker_devtools-0.0.1/PKG-INFO +0 -227
  102. docker_devtools-0.0.1/README.md +0 -199
  103. docker_devtools-0.0.1/cmd/docker-devtools/context.go +0 -166
  104. docker_devtools-0.0.1/internal/imgref/dockerfile.go +0 -119
  105. docker_devtools-0.0.1/scripts/conformance.sh +0 -66
  106. docker_devtools-0.0.1/testdata/dctx/percent/case.json +0 -1
  107. docker_devtools-0.0.1/testdata/dctx/perdockerfile/case.json +0 -1
  108. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/.gitignore +0 -0
  109. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/LICENSE +0 -0
  110. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/go.mod +0 -0
  111. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/go.sum +0 -0
  112. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/internal/imgref/compose.go +0 -0
  113. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/internal/imgupdate/update.go +0 -0
  114. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/internal/plugin/plugin_test.go +0 -0
  115. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/internal/rewrite/rewrite.go +0 -0
  116. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/internal/rewrite/rewrite_test.go +0 -0
  117. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/pyproject.toml +0 -0
  118. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/scripts/build-wheels.sh +0 -0
  119. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/scripts/commit-msg-lint.sh +0 -0
  120. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/scripts/prose-lint.sh +0 -0
  121. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/src/docker_devtools/__main__.py +0 -0
  122. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/src/docker_devtools/_find.py +0 -0
  123. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/src/docker_devtools/py.typed +0 -0
  124. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/lowercase/context/dockerfile +0 -0
  125. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/lowercase/context/dockerfile.dockerignore +0 -0
  126. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/lowercase/context/keep +0 -0
  127. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/lowercase/context/secret +0 -0
  128. {docker_devtools-0.0.1/testdata/dctx/perdockerfile → docker_devtools-0.1.0/testdata/dctx/nesteddockerfile}/context/.dockerignore +0 -0
  129. /docker_devtools-0.0.1/testdata/dctx/none/context/Dockerfile → /docker_devtools-0.1.0/testdata/dctx/nesteddockerfile/context/docker/Dockerfile.dev +0 -0
  130. /docker_devtools-0.0.1/testdata/dctx/none/context/a.txt → /docker_devtools-0.1.0/testdata/dctx/nesteddockerfile/context/only-default +0 -0
  131. /docker_devtools-0.0.1/testdata/dctx/none/context/b.txt → /docker_devtools-0.1.0/testdata/dctx/nesteddockerfile/context/only-nested +0 -0
  132. {docker_devtools-0.0.1/testdata/dctx/perdockerfile → docker_devtools-0.1.0/testdata/dctx/nesteddockerfile}/context/other +0 -0
  133. {docker_devtools-0.0.1/testdata/dctx/order → docker_devtools-0.1.0/testdata/dctx/none}/context/Dockerfile +0 -0
  134. /docker_devtools-0.0.1/testdata/dctx/perdockerfile/context/only-prj1 → /docker_devtools-0.1.0/testdata/dctx/none/context/a.txt +0 -0
  135. /docker_devtools-0.0.1/testdata/dctx/perdockerfile/context/only-default → /docker_devtools-0.1.0/testdata/dctx/none/context/b.txt +0 -0
  136. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/order/context/.dockerignore +0 -0
  137. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/order/context/CHANGELOG.md +0 -0
  138. {docker_devtools-0.0.1/testdata/dctx/pycache → docker_devtools-0.1.0/testdata/dctx/order}/context/Dockerfile +0 -0
  139. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/order/context/README.md +0 -0
  140. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/order/context/docs/guide.md +0 -0
  141. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/order/context/main.go +0 -0
  142. /docker_devtools-0.0.1/testdata/dctx/percent/context/we%ird → /docker_devtools-0.1.0/testdata/dctx/outofcontext/build.Dockerfile +0 -0
  143. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/percent/context/nope +0 -0
  144. /docker_devtools-0.0.1/testdata/dctx/perdockerfile/context/Prj1 → /docker_devtools-0.1.0/testdata/dctx/percent/context/we%ird +0 -0
  145. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/percent/context/we%ird.dockerignore +0 -0
  146. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/percent/context/yes +0 -0
  147. /docker_devtools-0.0.1/testdata/dctx/reinclude/context/Dockerfile → /docker_devtools-0.1.0/testdata/dctx/perdockerfile/context/Prj1 +0 -0
  148. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/perdockerfile/context/Prj1.dockerignore +0 -0
  149. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/pycache/context/.dockerignore +0 -0
  150. {docker_devtools-0.0.1/testdata/dctx/trailing → docker_devtools-0.1.0/testdata/dctx/pycache}/context/Dockerfile +0 -0
  151. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/pycache/context/a/b/keep.py +0 -0
  152. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/pycache/context/main.py +0 -0
  153. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/pycache/context/pkg/mod.py +0 -0
  154. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/reinclude/context/.dockerignore +0 -0
  155. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/reinclude/context/app.js +0 -0
  156. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/reinclude/context/node_modules/drop/index.js +0 -0
  157. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/reinclude/context/node_modules/keep/index.js +0 -0
  158. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/trailing/context/.dockerignore +0 -0
  159. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/trailing/context/build/out.bin +0 -0
  160. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/trailing/context/build/sub/deep.bin +0 -0
  161. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/dctx/trailing/context/src.go +0 -0
  162. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/imageref/compose/compose.yaml +0 -0
  163. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/imageref/compose/expected.txt +0 -0
  164. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/imageref/edge/Dockerfile +0 -0
  165. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/imageref/edge/Dockerfile.pinned +0 -0
  166. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/imageref/edge/expected.txt +0 -0
  167. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/imageref/multistage/Dockerfile +0 -0
  168. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/testdata/imageref/multistage/expected.txt +0 -0
  169. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/tests/__init__.py +0 -0
  170. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/tests/api_test.py +0 -0
  171. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/tests/binary_test.py +0 -0
  172. {docker_devtools-0.0.1 → docker_devtools-0.1.0}/tests/conftest.py +0 -0
@@ -0,0 +1,414 @@
1
+ Metadata-Version: 2.5
2
+ Name: docker-devtools
3
+ Version: 0.1.0
4
+ Summary: Work on the Dockerfiles, Compose files and build context in a repository.
5
+ Project-URL: Documentation, https://github.com/FlavioAmurrioCS/docker-devtools#readme
6
+ Project-URL: Issues, https://github.com/FlavioAmurrioCS/docker-devtools/issues
7
+ Project-URL: Source, https://github.com/FlavioAmurrioCS/docker-devtools
8
+ Author-email: Flavio Amurrio <25621374+FlavioAmurrioCS@users.noreply.github.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: build-context,buildkit,docker,docker-compose,dockerfile,dockerignore,pre-commit
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Go
16
+ Classifier: Programming Language :: Python
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Programming Language :: Python :: Implementation :: CPython
23
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
24
+ Classifier: Topic :: Software Development :: Build Tools
25
+ Classifier: Topic :: Utilities
26
+ Requires-Python: >=3.10
27
+ Description-Content-Type: text/markdown
28
+
29
+ # docker-devtools
30
+
31
+ Work on the Docker files in a repository: the build context a Dockerfile would
32
+ send, and the image references it and your Compose files point at.
33
+
34
+ ```console
35
+ $ docker-devtools image-refs ls
36
+ Dockerfile:1 python:3.11-slim
37
+ compose.yaml:3 nginx:1.25-alpine
38
+
39
+ $ docker-devtools image-refs update --tag-policy same-pattern --dry-run
40
+ Dockerfile:1 python:3.11-slim -> python:3.14-slim (tag 3.11-slim -> 3.14-slim)
41
+ compose.yaml:3 nginx:1.25-alpine -> nginx:1.31-alpine (tag 1.25-alpine -> 1.31-alpine)
42
+ ```
43
+
44
+ ## Why another one
45
+
46
+ Renovate and Dependabot already update image references, and they do it well.
47
+ They run as bots against a repository and open pull requests. This one runs on
48
+ your machine and edits the files in place. It is fast enough for a pre-commit
49
+ hook, so a stale base image gets caught before it is ever committed.
50
+
51
+ Where the semantics are Docker's, this defers to Docker's own code:
52
+
53
+ | Step | Package |
54
+ | --- | --- |
55
+ | Parse Dockerfiles | `moby/buildkit/frontend/dockerfile/parser` and `instructions` |
56
+ | Parse image references | `google/go-containerregistry/pkg/name` |
57
+ | Talk to registries | `google/go-containerregistry/pkg/v1/remote` |
58
+ | Match .dockerignore rules | `moby/patternmatcher` |
59
+ | Walk a build context | `tonistiigi/fsutil`, the package BuildKit sends contexts with |
60
+
61
+ None of the `.dockerignore` semantics are reimplemented here, and CI checks
62
+ that rather than asserting it: for every fixture, `scripts/conformance.sh`
63
+ builds `FROM scratch` with `COPY . /`, exports the image as a tarball, and
64
+ diffs the tar members against what `build-context ls` reports.
65
+
66
+ ## Install
67
+
68
+ ```console
69
+ $ uvx docker-devtools image-refs ls # no install
70
+ $ pipx run docker-devtools image-refs ls # no install
71
+ $ uv tool install docker-devtools
72
+ $ pip install docker-devtools
73
+ $ mise use ubi:FlavioAmurrioCS/docker-devtools
74
+ ```
75
+
76
+ Prebuilt binaries are attached to each
77
+ [release](https://github.com/FlavioAmurrioCS/docker-devtools/releases). With a
78
+ Go toolchain:
79
+
80
+ ```console
81
+ $ go install github.com/FlavioAmurrioCS/docker-devtools/cmd/docker-devtools@latest
82
+ ```
83
+
84
+ Reading and updating files doesn't require a Docker installation or a running
85
+ daemon.
86
+ Registry lookups authenticate with the same `~/.docker/config.json` the docker
87
+ CLI uses.
88
+
89
+ The build context is what BuildKit would send, because `docker build` forwards to
90
+ buildx by default. The legacy builder never learned `<dockerfile>.dockerignore`
91
+ at all. Its `ReadDockerignore` opens only `<context>/.dockerignore`, so a
92
+ `DOCKER_BUILDKIT=0` build legitimately disagrees with this listing whenever a
93
+ per-Dockerfile ignore file is in play.
94
+
95
+ ## As a pre-commit hook
96
+
97
+ ```yaml
98
+ repos:
99
+ - repo: https://github.com/FlavioAmurrioCS/docker-devtools
100
+ rev: v0.0.1
101
+ hooks:
102
+ - id: docker-image-check
103
+ ```
104
+
105
+ The hooks are scoped to `Dockerfile*`, `Containerfile*` and
106
+ `(docker-)?compose*.ya?ml` already:
107
+
108
+ | Hook | What it does |
109
+ | --- | --- |
110
+ | `docker-image-check` | Fails when a tag could move. Writes nothing. |
111
+ | `docker-image-update` | Moves tags in place, under `same-pattern`. |
112
+ | `docker-image-pin` | Appends or refreshes the `@sha256` digest. |
113
+
114
+ Each one reaches the registry, so a hook is only as fast as the tag listing it
115
+ asks for. `docker-image-check` is the one to reach for first: it reports without
116
+ touching the tree.
117
+
118
+ pre-commit installs a `repo:` from source, and building this one compiles the Go
119
+ binary, so the machine running the hook needs a Go toolchain. Somewhere that
120
+ cannot have one, install the published wheel instead and point a `local` hook at
121
+ the `docker-devtools` it puts on PATH.
122
+
123
+ ## Usage
124
+
125
+ ```text
126
+ docker-devtools build-context ls [PATH] list the files Docker would send
127
+ docker-devtools image-refs ls [PATH...] list every image reference, with file and line
128
+ docker-devtools image-refs update [PATH...] rewrite references in place
129
+ docker-devtools registry tags REF list a repository's tags, newest last
130
+ docker-devtools install-docker-plugin register as "docker devtools"
131
+ docker-devtools version print the version (also --version)
132
+ ```
133
+
134
+ Each group's `ls` is also the default, so `image-refs Dockerfile` and
135
+ `build-context .` work without it. The cost is that a mistyped verb reads as a
136
+ path: `image-refs updte` reports `stat updte: no such file or directory`.
137
+
138
+ `ls` is also spelled `list`. The groups are named away from `context` and
139
+ `image` on purpose: `docker context ls` lists CLI endpoints and `docker image ls`
140
+ lists local images, and neither is anything like what these do.
141
+
142
+ `PATH` is the build context directory for `build-context ls`, and files or
143
+ directories to scan for `image-refs`. Every command takes `--json`, which is the
144
+ same document the Python API parses.
145
+
146
+ `build-context ls` lists directories in their own right, the way `docker build`
147
+ sends them, so a listing piped through `-0` into `xargs` gets both. It also
148
+ takes:
149
+
150
+ ```text
151
+ -f, --file PATH Dockerfile to derive <path>.dockerignore from
152
+ --ignored list what the ignore file excluded instead
153
+ --all list everything, prefixed + for sent and - for excluded
154
+ --size prefix each path with its size in bytes
155
+ --why append the ignore-file rule that decided each path
156
+ --target STAGE build as if --target were given, changing what is reached
157
+ --whole-context list everything the ignore rules permit
158
+ --summary print totals to stderr after the listing
159
+ -0, --zero separate paths with NUL
160
+ ```
161
+
162
+ ### What actually gets sent
163
+
164
+ BuildKit transfers only the paths the Dockerfile names. Every `COPY` and `ADD`
165
+ source becomes a follow path on the build context, so a Dockerfile that copies
166
+ one file transfers one file, however large the directory around it:
167
+
168
+ ```console
169
+ $ docker-devtools build-context ls --summary
170
+ taplo.toml
171
+ transferred: 1 file, 1.7 KiB
172
+ permitted: 24626 files, 458.5 MiB
173
+ ```
174
+
175
+ The gap between those two lines is the point. `permitted` is what the ignore
176
+ rules allow through, which is also what gets sent when something copies the
177
+ context whole: `COPY . /` switches the filter off. `--whole-context` lists that
178
+ set, and `--target` picks the stage, since a stage the build never reaches never
179
+ reads its sources.
180
+
181
+ ### Which Dockerfile, and which .dockerignore
182
+
183
+ A context needs a Dockerfile. With no `-f`, `Dockerfile` is looked for and then
184
+ the lowercase `dockerfile`, which is the whole candidate set BuildKit uses;
185
+ there is no `Containerfile` fallback. When neither is there the command fails,
186
+ because `docker build` would too, and a listing of a build that cannot run
187
+ describes nothing.
188
+
189
+ A context with no `.dockerignore` at all says so, since that is the reason
190
+ `.git` and a virtualenv turn up in the listing:
191
+
192
+ ```console
193
+ $ docker-devtools build-context ls
194
+ warning: no .dockerignore in .; every file is sent
195
+ ```
196
+
197
+ `-f` takes a path, resolved from your working directory rather than from the
198
+ context. That is the rule `docker build -f` follows, and the Dockerfile may sit
199
+ outside the context entirely. The ignore file is the one **beside the
200
+ Dockerfile**, `<path>.dockerignore`, falling back to `<context>/.dockerignore`.
201
+ The first wins outright. They never merge.
202
+
203
+ ```console
204
+ $ docker-devtools build-context ls -f docker/build.Dockerfile ./app
205
+ # reads docker/build.Dockerfile.dockerignore, else app/.dockerignore
206
+ ```
207
+
208
+ `--why` says which rule decided each path, which is usually the question:
209
+
210
+ ```console
211
+ $ docker-devtools build-context ls --all --why
212
+ + app.js
213
+ - node_modules/drop/index.js <- .dockerignore:1 node_modules
214
+ + node_modules/keep/index.js <- .dockerignore:2 !node_modules/keep
215
+ ```
216
+
217
+ ### Updating image references
218
+
219
+ What changes is split by how much judgement it needs.
220
+
221
+ `--pin-digest` resolves the current tag to a digest and appends it, turning
222
+ `nginx:1.29` into `nginx:1.29@sha256:…`. It doesn't decide anything about versions, so it is
223
+ reversible and safe to run anywhere.
224
+
225
+ `--tag-policy` moves the tag. The default, `same-pattern`, moves only the last
226
+ component and keeps the suffix, so how specific your tag is decides how far it
227
+ may move:
228
+
229
+ | Current tag | same-pattern | minor | patch | latest |
230
+ | --- | --- | --- | --- | --- |
231
+ | `3.12-slim` | `3.13-slim` | `3.13-slim` | `3.12.7-slim` | `4.0-slim` |
232
+ | `3.12.1-slim` | `3.12.7-slim` | `3.13.0-slim` | `3.12.7-slim` | `4.0-slim` |
233
+ | `latest` | no change | no change | no change | no change |
234
+
235
+ Only `same-pattern` keeps the shape of a tag. The other three compare version
236
+ components, and a component the current tag omits counts as zero, so `patch` can
237
+ turn `3.12-slim` into `3.12.7-slim`: a tag that pinned a minor line now pins a
238
+ patch.
239
+
240
+ No policy ever changes the suffix: `-alpine` and `-slim` are different images,
241
+ and swapping them would change your base distribution without saying so. Tags
242
+ with no version, such as `latest` or `bookworm`, are never moved, because there
243
+ is no ordering to move along.
244
+
245
+ Add `--dry-run` to see the plan without writing, and `--fail-on-diff` to exit
246
+ non-zero when anything would change, which is what makes it useful in CI.
247
+
248
+ `--fail-on-diff` reports on the plan, not on the writing, so on its own it
249
+ still rewrites the files and then exits non-zero. Pair it with `--dry-run` for a
250
+ check that leaves the tree alone, which is what the `docker-image-check` hook
251
+ does.
252
+
253
+ ### Base images behind an ARG
254
+
255
+ A Dockerfile that opens `ARG BASE_IMAGE=debian:13-slim` and then
256
+ `FROM "${BASE_IMAGE}"` still has a real base image, and it is updatable. The
257
+ `FROM` is expanded through the ARG defaults with BuildKit's own lexer, the same
258
+ way `docker build` does it, and the reference is reported on the **ARG** line,
259
+ because that is the only text an update can rewrite:
260
+
261
+ ```console
262
+ $ docker-devtools image-refs ls --unresolved
263
+ Dockerfile:1 debian:13-slim
264
+ Dockerfile:5 "${BASE_IMAGE}" (resolved from ARG BASE_IMAGE on line 1)
265
+ ```
266
+
267
+ This holds only when the ARG default is the whole reference, spelled out on its
268
+ own line. `ARG VERSION=12` with `FROM debian:${VERSION}-slim` stays unresolved:
269
+ the image is `debian:12-slim`, which is written nowhere, and rewriting would
270
+ mean splicing a bare tag into the middle of a line.
271
+
272
+ ### What it will not touch
273
+
274
+ Some references cannot be resolved to an image, and those are reported rather
275
+ than guessed at. Pass `--unresolved` to `image-refs ls` to see them:
276
+
277
+ - `FROM builder`, where `builder` is an earlier stage
278
+ - `COPY --from=0`, which indexes a stage
279
+ - `FROM $BASE` where the ARG has no default, or supplies only part of the
280
+ reference
281
+ - `FROM scratch`, which is the empty base rather than a registry image
282
+ - Compose values built from variables, such as `${REGISTRY}/app:latest`
283
+
284
+ A listing says how many it withheld, so a file whose every reference is one of
285
+ these does not simply vanish from the output.
286
+
287
+ ### Editing in place
288
+
289
+ An update splices the new reference into the exact byte range the parser
290
+ reported. It never re-encodes the file, so comments, quoting style, anchors and
291
+ whitespace all survive:
292
+
293
+ ```yaml
294
+ image: "nginx:1.29-alpine" # keep this comment and the quotes
295
+ ```
296
+
297
+ becomes
298
+
299
+ ```yaml
300
+ image: "nginx:1.31-alpine" # keep this comment and the quotes
301
+ ```
302
+
303
+ If a byte range no longer holds the text the parse said it held, the update
304
+ fails instead of writing. A rewrite that has drifted from the parse is a bug,
305
+ and corrupting the file would hide it.
306
+
307
+ ## Listing tags
308
+
309
+ ```console
310
+ $ docker-devtools registry tags python:3.12-slim
311
+ 3.11-slim
312
+ * 3.12-slim
313
+ 3.13-slim
314
+ 3.14-slim
315
+ ```
316
+
317
+ Given a tag, the listing keeps only tags sharing its suffix and marks the one
318
+ you named, so it answers what that reference could move to. `-alpine` and
319
+ `-slim` stay apart for the same reason no policy crosses between them. Pass
320
+ `--all` for everything, and `--json` to script against.
321
+
322
+ The ordering is computed here, not taken from the registry. The OCI
323
+ distribution spec requires the tags endpoint to return
324
+ "lexical (i.e. case-insensitive alphanumeric order)" and carries no timestamps,
325
+ which is the order that puts `3.10` before `3.9`. There is no portable way to
326
+ sort by publication date: reading one costs three requests per tag and is
327
+ meaningless for reproducible builds, which set it to the epoch. `--sort lexical`
328
+ hands the registry's own order back.
329
+
330
+ Credentials come from `~/.docker/config.json`, including the `credsStore` and
331
+ `credHelpers` entries that shell out to `docker-credential-*`, and from
332
+ `$DOCKER_CONFIG`, `$REGISTRY_AUTH_FILE` and Podman's `containers/auth.json`.
333
+ Behind all of those, `~/.netrc` is consulted, or `$NETRC` when it is set.
334
+
335
+ ## Shell completion
336
+
337
+ The binary emits a [usage](https://usage.jdx.dev) spec describing its own
338
+ command tree, and the `usage` CLI turns that into completions for bash, zsh,
339
+ fish, powershell and nushell:
340
+
341
+ ```console
342
+ $ mise use usage
343
+ $ usage g completion zsh docker-devtools --usage-cmd 'docker-devtools --usage-spec' --install
344
+ ```
345
+
346
+ The generated scripts call back to `usage` at completion time, so it has to stay
347
+ on your PATH. `mise run completions` regenerates all five, plus a markdown
348
+ reference, into `build/`.
349
+
350
+ ## As a Docker CLI plugin
351
+
352
+ ```console
353
+ $ docker-devtools install-docker-plugin
354
+ $ docker devtools image-refs ls
355
+ ```
356
+
357
+ This symlinks the binary into `~/.docker/cli-plugins/`, so upgrading the binary
358
+ upgrades the plugin. Windows gets a copy instead, having no dependable
359
+ unprivileged symlink. `DOCKER_CONFIG` moves the directory, and `--system`
360
+ installs for every user.
361
+
362
+ The subcommand is `devtools` because Docker validates plugin names against
363
+ `^[a-z][a-z0-9]*$` and refuses to load anything else. Python wheels cannot do
364
+ this step at install time: they have no post-install hook, and
365
+ `~/.docker/cli-plugins/` sits outside every Python install path.
366
+
367
+ ## Python API
368
+
369
+ The wheel bundles the binary and a typed wrapper.
370
+
371
+ ```python
372
+ from docker_devtools import image_ls
373
+ from docker_devtools import image_update
374
+
375
+ for ref in image_ls(".").resolved():
376
+ print(f"{ref.path}:{ref.line}", ref.repository, ref.tag)
377
+
378
+ report = image_update(".", pin_digest=True, dry_run=True)
379
+ for change in report.changes:
380
+ print(change.old, "->", change.new, f"({change.reason})")
381
+ ```
382
+
383
+ `image_update` defaults to `dry_run=True`, so calling it by accident cannot
384
+ rewrite a repository. The CLI defaults the other way, as a CLI should: `image
385
+ update` writes unless you pass `--dry-run`.
386
+
387
+ The wrapper shells out to the bundled binary, which the wheel installs onto
388
+ PATH. Where that directory isn't on PATH, `python -m docker_devtools` runs it
389
+ anyway, and `DOCKER_DEVTOOLS_BINARY` points at a specific build.
390
+
391
+ ## Development
392
+
393
+ `mise.toml` defines the tools and the tasks.
394
+
395
+ ```console
396
+ $ mise run build # compile into ./build
397
+ $ mise run test # go test + pytest
398
+ $ mise run lint # pre-commit across the repo
399
+ $ mise run prose # vale-ai-tells across all markdown
400
+ $ mise run conformance # diff context listing against real docker build
401
+ $ mise run completions # regenerate completions and docs
402
+ $ mise run wheels # every platform wheel into ./dist
403
+ $ mise run test-clone # verify a fresh clone in a container
404
+ ```
405
+
406
+ Registry behaviour is tested against `go-containerregistry`'s in-process
407
+ registry, so the suite doesn't touch the network or carry recorded fixtures.
408
+
409
+ ## License
410
+
411
+ MIT. See [LICENSE](LICENSE).
412
+
413
+ `src/docker_devtools/_find.py` adapts the binary-discovery search order from
414
+ [uv](https://github.com/astral-sh/uv), which is MIT OR Apache-2.0.