docker-devkit 0.1.7__py3-none-any.whl → 0.1.9__py3-none-any.whl

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.
@@ -5,6 +5,7 @@ import os
5
5
  import platform
6
6
  import re
7
7
  from pathlib import Path
8
+ from subprocess import CalledProcessError
8
9
  from typing import Annotated, Any, Literal
9
10
 
10
11
  import typer
@@ -15,6 +16,7 @@ from pydantic_settings import BaseSettings
15
16
 
16
17
  from .context_sha import compute_service_shas
17
18
  from .image_refs import bake_base_image_refs, compose_service_refs, resolve_remote_digest
19
+ from .modes import parse_env_file
18
20
 
19
21
 
20
22
  class Settings(BaseSettings):
@@ -35,70 +37,6 @@ Mode = Literal["local", "ci"]
35
37
  app = typer.Typer(add_completion=False, pretty_exceptions_show_locals=False)
36
38
 
37
39
 
38
- def _load_lock_file(path: Path):
39
- return {
40
- (p := line.split("=", 1))[0].strip(): p[1].strip()
41
- for line in path.read_text(encoding="utf-8").splitlines()
42
- if "=" in line and not line.startswith("#")
43
- }
44
-
45
-
46
- def _write_lock_file(path: Path, variables: dict[str, str]):
47
- path.write_text(
48
- "# Generated by lock.py\n" + "\n".join(f"{k}={v}" for k, v in sorted(variables.items())) + "\n",
49
- encoding="utf-8",
50
- )
51
-
52
-
53
- # Vibe code - Gemini 3
54
- def _check_gc_limits(min_gb: int = 60):
55
- candidates = [Path("/etc/docker/daemon.json"), Path(os.path.expanduser("~/.docker/daemon.json"))]
56
- if settings.wsl_distro_name is not None:
57
- try:
58
- win_home = bash_output('wslpath $(cmd.exe /c "echo %UserProfile%" 2>/dev/null)').strip()
59
- candidates.append(Path(win_home) / ".docker" / "daemon.json")
60
- except Exception:
61
- pass
62
-
63
- config = next((p for p in candidates if p.exists() and os.access(p, os.R_OK)), None)
64
- if not config:
65
- print(f" [WARN] No readable daemon.json found. Ensure defaultKeepStorage > {min_gb}GB.")
66
- return
67
-
68
- data = json.loads(config.read_text())
69
- raw = data.get("builder", {}).get("gc", {}).get("defaultKeepStorage")
70
- if not raw:
71
- sample = json.dumps({"builder": {"gc": {"defaultKeepStorage": f"{min_gb}GB"}}}, indent=2)
72
- raise RuntimeError(
73
- f"Missing 'builder.gc.defaultKeepStorage' in {config}; Docker's default is too low for GPU builds "
74
- f"(need >= {min_gb}GB). Add the following (merging into any existing keys) and restart Docker:\n\n{sample}"
75
- )
76
-
77
- m = re.match(r"^(\d+(?:\.\d+)?)\s*([TGMK]i?B)?$", str(raw), re.IGNORECASE)
78
- if not m:
79
- raise RuntimeError(f"Could not parse 'defaultKeepStorage' value: {raw}")
80
-
81
- mult = {"T": 1024, "G": 1, "M": 1 / 1024, "K": 1 / 1024**2, "B": 1 / 1024**3}
82
- unit = (m.group(2) or "B")[0].upper()
83
- val = float(m.group(1)) * mult.get(unit, 1 / 1024**3)
84
-
85
- if val < min_gb:
86
- raise RuntimeError(
87
- f"UNSAFE GC LIMIT: {val:.1f}GB in {config} (Required: {min_gb}GB). RESTART DOCKER AFTER FIXING."
88
- )
89
-
90
- print(f" [OK] Docker GC Limit verified: {val:.1f}GB")
91
-
92
-
93
- def _load_compose(path: Path) -> dict[str, Any]:
94
- data: dict[str, Any] = yaml.safe_load(path.read_text(encoding="utf-8"))
95
- for include in data.pop("include", []):
96
- include_path = path.parent / (include if isinstance(include, str) else include["path"])
97
- included: dict[str, Any] = yaml.safe_load(include_path.read_text(encoding="utf-8"))
98
- data.setdefault("services", {}).update(included.get("services", {}))
99
- return data
100
-
101
-
102
40
  @app.command()
103
41
  def build(
104
42
  upgrade: bool = typer.Option(False, "--upgrade", "-u", help="Re-resolve and rewrite base digests."),
@@ -153,8 +91,12 @@ def run_build(
153
91
 
154
92
  # Read bake, compose, and lock files
155
93
  bake_data: dict[str, Any] = yaml.safe_load(bake_file.read_text(encoding="utf-8"))
156
- compose_data: dict[str, Any] = _load_compose(COMPOSE_FILE)
157
- lock_data = _load_lock_file(LOCK_FILE) if LOCK_FILE.exists() else {}
94
+ compose_data: dict[str, Any] = yaml.safe_load(COMPOSE_FILE.read_text(encoding="utf-8"))
95
+ for include in compose_data.pop("include", []):
96
+ include_path = COMPOSE_FILE.parent / (include if isinstance(include, str) else include["path"])
97
+ included: dict[str, Any] = yaml.safe_load(include_path.read_text(encoding="utf-8"))
98
+ compose_data.setdefault("services", {}).update(included.get("services", {}))
99
+ lock_data = parse_env_file(LOCK_FILE) if LOCK_FILE.exists() else {}
158
100
 
159
101
  # TOOD: Create separate commands for ci and local modes so typer can do this validation instead of us
160
102
  if mode == "ci" and gpu == "auto":
@@ -185,18 +127,20 @@ def run_build(
185
127
  for occurrence in compose_service_refs(compose_data)
186
128
  }
187
129
 
188
- def _needs_resolve(image: str, ref: str) -> bool:
189
- if upgrade or image not in lock_data:
190
- return True
191
- # Re-resolve if the image name in compose.yml changed from what's in the lock file
192
- # (e.g. postgres:16-alpine → postgis/postgis:16-3.4-alpine)
193
- return not lock_data[image].startswith(ref + "@")
194
-
195
- for image, ref in {image: ref for image, ref in third_party_images.items() if _needs_resolve(image, ref)}.items():
130
+ # Re-resolve when explicitly requested, when unseen, or when the image name in compose.yml
131
+ # changed from what's in the lock file (e.g. postgres:16-alpine → postgis/postgres:16-3.4-alpine)
132
+ for image, ref in {
133
+ image: ref
134
+ for image, ref in third_party_images.items()
135
+ if upgrade or image not in lock_data or not lock_data[image].startswith(ref + "@")
136
+ }.items():
196
137
  lock_data[image] = f"{ref}@{'' if '$' in ref else resolve_remote_digest(ref)}"
197
138
 
198
139
  # Update main lock file
199
- _write_lock_file(LOCK_FILE, lock_data)
140
+ LOCK_FILE.write_text(
141
+ "# Generated by lock.py\n" + "\n".join(f"{key}={value}" for key, value in sorted(lock_data.items())) + "\n",
142
+ encoding="utf-8",
143
+ )
200
144
 
201
145
  if lock_only:
202
146
  return
@@ -272,6 +216,46 @@ def run_build(
272
216
  raise RuntimeError("Baked images do not match target images; something went wrong during the bake.")
273
217
 
274
218
 
219
+ # Vibe code - Gemini 3
220
+ def _check_gc_limits(min_gb: int = 60):
221
+ candidates = [Path("/etc/docker/daemon.json"), Path(os.path.expanduser("~/.docker/daemon.json"))]
222
+ if settings.wsl_distro_name is not None:
223
+ try:
224
+ win_home = bash_output('wslpath $(cmd.exe /c "echo %UserProfile%" 2>/dev/null)').strip()
225
+ candidates.append(Path(win_home) / ".docker" / "daemon.json")
226
+ except CalledProcessError:
227
+ pass
228
+
229
+ config = next((p for p in candidates if p.exists() and os.access(p, os.R_OK)), None)
230
+ if not config:
231
+ print(f" [WARN] No readable daemon.json found. Ensure defaultKeepStorage > {min_gb}GB.")
232
+ return
233
+
234
+ data = json.loads(config.read_text())
235
+ raw = data.get("builder", {}).get("gc", {}).get("defaultKeepStorage")
236
+ if not raw:
237
+ sample = json.dumps({"builder": {"gc": {"defaultKeepStorage": f"{min_gb}GB"}}}, indent=2)
238
+ raise RuntimeError(
239
+ f"Missing 'builder.gc.defaultKeepStorage' in {config}; Docker's default is too low for GPU builds "
240
+ f"(need >= {min_gb}GB). Add the following (merging into any existing keys) and restart Docker:\n\n{sample}"
241
+ )
242
+
243
+ m = re.match(r"^(\d+(?:\.\d+)?)\s*([TGMK]i?B)?$", str(raw), re.IGNORECASE)
244
+ if not m:
245
+ raise RuntimeError(f"Could not parse 'defaultKeepStorage' value: {raw}")
246
+
247
+ mult = {"T": 1024, "G": 1, "M": 1 / 1024, "K": 1 / 1024**2, "B": 1 / 1024**3}
248
+ unit = (m.group(2) or "B")[0].upper()
249
+ val = float(m.group(1)) * mult.get(unit, 1 / 1024**3)
250
+
251
+ if val < min_gb:
252
+ raise RuntimeError(
253
+ f"UNSAFE GC LIMIT: {val:.1f}GB in {config} (Required: {min_gb}GB). RESTART DOCKER AFTER FIXING."
254
+ )
255
+
256
+ print(f" [OK] Docker GC Limit verified: {val:.1f}GB")
257
+
258
+
275
259
  # x-cross-compile-targets: services declared here are excluded from the default
276
260
  # target list because they need special (usually per-arch) treatment; an operator
277
261
  # can still opt them in explicitly via --targets. The field is a top-level bake key.
@@ -1,7 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import os
4
- import subprocess
5
3
  from pathlib import Path, PurePosixPath
6
4
  from tempfile import TemporaryDirectory
7
5
 
@@ -9,33 +7,21 @@ from typing import Any
9
7
 
10
8
  import pathspec
11
9
  import yaml
10
+ from bashrun.bash import bash, bash_output
12
11
 
13
12
 
14
- def _env_var_name(dockerfile_dir: str) -> str:
15
- return PurePosixPath(dockerfile_dir).name.upper().replace("-", "_") + "_SHA"
16
-
13
+ def compute_service_shas(repo_root: Path, bake_file: Path) -> dict[str, str]:
14
+ repo_root = repo_root.resolve()
17
15
 
18
- def _extract_service_dirs(bake_data: dict[str, Any]) -> set[str]:
16
+ bake_data: dict[str, Any] = yaml.safe_load(bake_file.read_text(encoding="utf-8"))
19
17
  service_dirs: set[str] = set()
20
- services: dict[str, Any] = bake_data["services"]
21
- for config in services.values():
18
+ for config in bake_data["services"].values():
22
19
  build: dict[str, Any] = config.get("build", {})
23
20
  if not build.get("tags"):
24
21
  continue
25
- dockerfile: str = build.get("dockerfile", "")
26
- service_dirs.add(str(PurePosixPath(dockerfile).parent))
27
- return service_dirs
28
-
29
-
30
- def compute_service_shas(repo_root: Path, bake_file: Path) -> dict[str, str]:
31
- repo_root = repo_root.resolve()
32
-
33
- bake_data: dict[str, Any] = yaml.safe_load(bake_file.read_text(encoding="utf-8"))
34
- service_dirs = _extract_service_dirs(bake_data)
22
+ service_dirs.add(str(PurePosixPath(build.get("dockerfile", "")).parent))
35
23
 
36
- tree_entries = subprocess.run(
37
- ["git", "ls-tree", "-r", "HEAD"], cwd=str(repo_root), capture_output=True, text=True, check=True
38
- ).stdout.splitlines()
24
+ tree_entries = bash_output("git ls-tree -r HEAD", cwd=repo_root).splitlines()
39
25
 
40
26
  dockerignore = repo_root / ".dockerignore"
41
27
  spec = pathspec.PathSpec.from_lines("gitignore", dockerignore.read_text().splitlines())
@@ -67,20 +53,10 @@ def compute_service_shas(repo_root: Path, bake_file: Path) -> dict[str, str]:
67
53
  index_input = "\n".join(f"{mode} {obj_hash}\t{path}" for mode, obj_hash, path in entries) + "\n"
68
54
 
69
55
  with TemporaryDirectory() as tmpdir:
70
- env = {**os.environ, "GIT_INDEX_FILE": str(Path(tmpdir) / "index")}
71
- subprocess.run(
72
- ["git", "update-index", "--index-info"],
73
- cwd=str(repo_root),
74
- env=env,
75
- input=index_input.encode(),
76
- capture_output=True,
77
- check=True,
78
- )
79
- tree_hash = subprocess.run(
80
- ["git", "write-tree"], cwd=str(repo_root), env=env, capture_output=True, text=True, check=True
81
- ).stdout.strip()
56
+ index_env = {"GIT_INDEX_FILE": str(Path(tmpdir) / "index")}
57
+ bash("git update-index --index-info", cwd=repo_root, env=index_env, stdin_text=index_input)
58
+ tree_hash = bash_output("git write-tree", cwd=repo_root, env=index_env).strip()
82
59
 
83
- var_name = _env_var_name(service_dir)
84
- result[var_name] = f"tree-{tree_hash}"
60
+ result[PurePosixPath(service_dir).name.upper().replace("-", "_") + "_SHA"] = f"tree-{tree_hash}"
85
61
 
86
62
  return result
docker_devkit/down.py CHANGED
@@ -13,11 +13,6 @@ ENV_FILE = Path(".env")
13
13
  LOCK_FILE = Path(".env.lock")
14
14
  BAKE_FILE = Path("compose.bake.yml")
15
15
 
16
-
17
- def _resolve_service_shas() -> None:
18
- os.environ.update(compute_service_shas(Path.cwd(), BAKE_FILE))
19
-
20
-
21
16
  app = typer.Typer(add_completion=False)
22
17
 
23
18
 
@@ -53,7 +48,7 @@ def down(
53
48
  resolve_auth_mode(ENV_FILE)
54
49
 
55
50
  if BAKE_FILE.exists():
56
- _resolve_service_shas()
51
+ os.environ.update(compute_service_shas(Path.cwd(), BAKE_FILE))
57
52
 
58
53
  if native:
59
54
  compose_files = (
@@ -76,6 +76,17 @@ class VersionCoupling:
76
76
  sites: tuple[VersionSite, ...]
77
77
 
78
78
 
79
+ def unpinned_references(root: Path) -> list[str]:
80
+ return [
81
+ occurrence.reference
82
+ for occurrence in collect_repo_references(root)
83
+ if "/" in (reference := strip_build_args(occurrence.reference))
84
+ and not BUILD_ARG_PATTERN.search(occurrence.reference)
85
+ and ":" not in (tail := reference[reference.rfind("/") + 1 :])
86
+ and "@" not in tail
87
+ ]
88
+
89
+
79
90
  def collect_repo_references(
80
91
  root: Path,
81
92
  compose_glob: str = "compose*.yml",
@@ -121,47 +132,37 @@ def collect_repo_references(
121
132
  )
122
133
 
123
134
 
124
- def compose_service_refs(document: object) -> list[ImageReference]:
135
+ def version_coupling_violations(root: Path, couplings: list[VersionCoupling]) -> list[str]:
125
136
  return [
126
- ImageReference(service_name, service.image_ref)
127
- for service_name, service in ComposeDocument.model_validate(document).services.items()
128
- if service.image_ref is not None
137
+ f"{coupling.name}: {site.description} at {path}: expected {expected}, found {found}"
138
+ for coupling in couplings
139
+ for expected in [re.sub(r"^[^0-9]+", "", _pyproject_value(root, coupling.pyproject_key)).split(",")[0].strip()]
140
+ for site in coupling.sites
141
+ for path in sorted(root.glob(site.glob)) or [Path(site.glob)]
142
+ for found in [_site_version(path, site)]
143
+ if found != expected
129
144
  ]
130
145
 
131
146
 
132
- def bake_base_image_refs(document: object) -> list[ImageReference]:
133
- return list(starmap(ImageReference, BakeDocument.model_validate(document).base_images.items()))
134
-
135
-
136
- def _load_yaml_document(path: Path) -> object:
137
- return yaml.safe_load(path.read_text(encoding="utf-8"))
138
-
139
-
140
- def strip_build_args(reference: str) -> str:
141
- return BUILD_ARG_PATTERN.sub("", reference).strip()
147
+ def resolve_remote_digest(reference: str) -> str:
148
+ print(f"Resolving digest for: {reference}")
149
+ output = bash_output(f"docker buildx imagetools inspect {shlex.quote(reference)}")
150
+ match = DIGEST_PATTERN.search(output)
151
+ if match is None:
152
+ raise RuntimeError(f"Could not parse digest for image reference: {reference}")
153
+ return match.group(1)
142
154
 
143
155
 
144
- def unpinned_references(root: Path) -> list[str]:
156
+ def compose_service_refs(document: object) -> list[ImageReference]:
145
157
  return [
146
- occurrence.reference
147
- for occurrence in collect_repo_references(root)
148
- if "/" in (reference := strip_build_args(occurrence.reference))
149
- and not BUILD_ARG_PATTERN.search(occurrence.reference)
150
- and ":" not in (tail := reference[reference.rfind("/") + 1 :])
151
- and "@" not in tail
158
+ ImageReference(service_name, service.image_ref)
159
+ for service_name, service in ComposeDocument.model_validate(document).services.items()
160
+ if service.image_ref is not None
152
161
  ]
153
162
 
154
163
 
155
- def version_coupling_violations(root: Path, couplings: list[VersionCoupling]) -> list[str]:
156
- return [
157
- f"{coupling.name}: {site.description} at {path}: expected {expected}, found {found}"
158
- for coupling in couplings
159
- for expected in [_declared_version(_pyproject_value(root, coupling.pyproject_key))]
160
- for site in coupling.sites
161
- for path in _site_paths(root, site)
162
- for found in [_site_version(path, site)]
163
- if found != expected
164
- ]
164
+ def strip_build_args(reference: str) -> str:
165
+ return BUILD_ARG_PATTERN.sub("", reference).strip()
165
166
 
166
167
 
167
168
  def _pyproject_value(root: Path, dotted_key: str) -> str:
@@ -175,33 +176,27 @@ def _pyproject_value(root: Path, dotted_key: str) -> str:
175
176
  return value
176
177
 
177
178
 
178
- def _declared_version(specifier: str) -> str:
179
- return re.sub(r"^[^0-9]+", "", specifier).split(",")[0].strip()
180
-
181
-
182
- def _site_paths(root: Path, site: VersionSite) -> list[Path]:
183
- return sorted(root.glob(site.glob)) or [Path(site.glob)]
184
-
185
-
186
179
  def _site_version(path: Path, site: VersionSite) -> str:
187
180
  if not path.exists():
188
181
  return MISSING_VERSION
189
- haystack = _bake_base_image(path, site.base_image) if site.base_image else path.read_text(encoding="utf-8")
182
+ if site.base_image:
183
+ haystack = next(
184
+ (
185
+ reference.reference
186
+ for reference in bake_base_image_refs(_load_yaml_document(path))
187
+ if reference.name == site.base_image
188
+ ),
189
+ "",
190
+ )
191
+ else:
192
+ haystack = path.read_text(encoding="utf-8")
190
193
  match = re.search(site.pattern, haystack)
191
194
  return match.group(1) if match else MISSING_VERSION
192
195
 
193
196
 
194
- def _bake_base_image(path: Path, key: str) -> str:
195
- return next(
196
- (reference.reference for reference in bake_base_image_refs(_load_yaml_document(path)) if reference.name == key),
197
- "",
198
- )
197
+ def bake_base_image_refs(document: object) -> list[ImageReference]:
198
+ return list(starmap(ImageReference, BakeDocument.model_validate(document).base_images.items()))
199
199
 
200
200
 
201
- def resolve_remote_digest(reference: str) -> str:
202
- print(f"Resolving digest for: {reference}")
203
- output = bash_output(f"docker buildx imagetools inspect {shlex.quote(reference)}")
204
- match = DIGEST_PATTERN.search(output)
205
- if match is None:
206
- raise RuntimeError(f"Could not parse digest for image reference: {reference}")
207
- return match.group(1)
201
+ def _load_yaml_document(path: Path) -> object:
202
+ return yaml.safe_load(path.read_text(encoding="utf-8"))
docker_devkit/modes.py CHANGED
@@ -6,17 +6,6 @@ VALID_AUTH_MODES = ("keycloak", "disabled")
6
6
  DEFAULT_AUTH_MODE = "keycloak"
7
7
 
8
8
 
9
- def parse_env_file(path: Path) -> dict[str, str]:
10
- result: dict[str, str] = {}
11
- for raw_line in path.read_text().splitlines():
12
- line = raw_line.strip()
13
- if not line or line.startswith("#") or "=" not in line:
14
- continue
15
- key, _, value = line.partition("=")
16
- result[key.strip()] = value.strip().strip("'\"")
17
- return result
18
-
19
-
20
9
  def resolve_auth_mode(env_file: Path) -> str:
21
10
  file_values = parse_env_file(env_file)
22
11
  public_url = environ.get("PUBLIC_URL") or file_values.get("PUBLIC_URL")
@@ -37,3 +26,14 @@ def resolve_auth_mode(env_file: Path) -> str:
37
26
  )
38
27
 
39
28
  return auth_mode
29
+
30
+
31
+ def parse_env_file(path: Path) -> dict[str, str]:
32
+ result: dict[str, str] = {}
33
+ for raw_line in path.read_text().splitlines():
34
+ line = raw_line.strip()
35
+ if not line or line.startswith("#") or "=" not in line:
36
+ continue
37
+ key, _, value = line.partition("=")
38
+ result[key.strip()] = value.strip().strip("'\"")
39
+ return result
docker_devkit/up.py CHANGED
@@ -14,11 +14,6 @@ ENV_FILE = Path(".env")
14
14
  LOCK_FILE = Path(".env.lock")
15
15
  BAKE_FILE = Path("compose.bake.yml")
16
16
 
17
-
18
- def _resolve_service_shas() -> None:
19
- os.environ.update(compute_service_shas(Path.cwd(), BAKE_FILE))
20
-
21
-
22
17
  app = typer.Typer(add_completion=False)
23
18
 
24
19
 
@@ -77,7 +72,7 @@ def up(
77
72
  run_build(gpu=gpu)
78
73
 
79
74
  if BAKE_FILE.exists():
80
- _resolve_service_shas()
75
+ os.environ.update(compute_service_shas(Path.cwd(), BAKE_FILE))
81
76
 
82
77
  profile_flag = "--profile keycloak " if auth_mode == "keycloak" else ""
83
78
  if native:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: docker-devkit
3
- Version: 0.1.7
3
+ Version: 0.1.9
4
4
  Summary: Docker-stack lifecycle commands (up/down/build) with a native/consumer split
5
5
  License-File: LICENSE
6
6
  License-File: NOTICE
@@ -0,0 +1,15 @@
1
+ docker_devkit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ docker_devkit/build_docker.py,sha256=FzCpT8e7kg6dVcg81vO7X_nKp65IUtLiRTV2mPGXWEY,11552
3
+ docker_devkit/context_sha.py,sha256=wThaKFXF9jJE09zlyzpN2xU5S0LusIZ7pVi_MYFFfAU,2416
4
+ docker_devkit/detect_gpu.py,sha256=C4e3vixvSSJS6xQiMVWp2IdWSAgMr551KrBTuIrkTk4,456
5
+ docker_devkit/down.py,sha256=lK5Bk6rTZp-XMbz_o1ves65tBXSAT9Wo5s_owZbXknQ,2700
6
+ docker_devkit/image_refs.py,sha256=6y2lqAxBz0RkP2ZIdVsIB7fnD8HKdZFaFyQ7QMUzCdk,6269
7
+ docker_devkit/modes.py,sha256=Lwhblzu5Kn7qhfy_QMqE28XuflSIJtCRBYNX7Iq8Qfo,1510
8
+ docker_devkit/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ docker_devkit/up.py,sha256=dcd1p0JjzVD0JBL0InIkWamkYIeeHK2EQAoiSIfNJh4,4117
10
+ docker_devkit-0.1.9.dist-info/METADATA,sha256=OS9ikxjAa28_CHuSmuFNXz3c8iGjDlulBs3hukxpug0,399
11
+ docker_devkit-0.1.9.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
12
+ docker_devkit-0.1.9.dist-info/entry_points.txt,sha256=0AJlYxdEuQ7w21s1y-07MJ4AU_aJBUfkVWrwrxCqaMI,113
13
+ docker_devkit-0.1.9.dist-info/licenses/LICENSE,sha256=RFhQPdSOiMTguUX7JSoIuTxA7HVzCbj_p8WU36HjUQQ,10947
14
+ docker_devkit-0.1.9.dist-info/licenses/NOTICE,sha256=jvglZza3GCdN7v4_TLuXUqX9C_uABlwAKUkcIeKwk_8,26
15
+ docker_devkit-0.1.9.dist-info/RECORD,,
@@ -1,15 +0,0 @@
1
- docker_devkit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- docker_devkit/build_docker.py,sha256=NmAw8GXQ8xcakKWRepjtArKts738MRMTohzSIlyU2GI,11940
3
- docker_devkit/context_sha.py,sha256=KWwJtIKfgKkogYoPL7_KEcKX8XG3i-P6_ReIOkB5wE4,3085
4
- docker_devkit/detect_gpu.py,sha256=C4e3vixvSSJS6xQiMVWp2IdWSAgMr551KrBTuIrkTk4,456
5
- docker_devkit/down.py,sha256=dHqMqvsufCqwBD__tO2DEpyD9mS2T9ReyuqIcqUQ38c,2768
6
- docker_devkit/image_refs.py,sha256=CZ5ibvs2l01gqh-GwaLUYhiZ_cz2_bjBk9PlplGVlts,6441
7
- docker_devkit/modes.py,sha256=-6mNyvu3G97PzJ2vsfnXr5LwQhjvhlDfd11u6rkhN6M,1510
8
- docker_devkit/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- docker_devkit/up.py,sha256=HQH5WB0kZTST6wJHS1jjzOWAvT2bK0u_MozRieU-iJU,4185
10
- docker_devkit-0.1.7.dist-info/METADATA,sha256=voRGgZJzvzMRVfCDesxHtIqmd3BXQUfH0KPtiYZBXdQ,399
11
- docker_devkit-0.1.7.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
12
- docker_devkit-0.1.7.dist-info/entry_points.txt,sha256=0AJlYxdEuQ7w21s1y-07MJ4AU_aJBUfkVWrwrxCqaMI,113
13
- docker_devkit-0.1.7.dist-info/licenses/LICENSE,sha256=RFhQPdSOiMTguUX7JSoIuTxA7HVzCbj_p8WU36HjUQQ,10947
14
- docker_devkit-0.1.7.dist-info/licenses/NOTICE,sha256=jvglZza3GCdN7v4_TLuXUqX9C_uABlwAKUkcIeKwk_8,26
15
- docker_devkit-0.1.7.dist-info/RECORD,,