shipit-cli 0.6.0__py3-none-any.whl → 0.7.0__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.
- shipit/assets/wordpress/install.sh +33 -0
- shipit/assets/wordpress/wp-config.php +133 -0
- shipit/cli.py +233 -116
- shipit/env.py +30 -0
- shipit/generator.py +27 -16
- shipit/procfile.py +106 -0
- shipit/providers/base.py +19 -3
- shipit/providers/gatsby.py +18 -6
- shipit/providers/hugo.py +6 -2
- shipit/providers/laravel.py +17 -6
- shipit/providers/mkdocs.py +16 -7
- shipit/providers/node_static.py +18 -6
- shipit/providers/php.py +39 -10
- shipit/providers/python.py +54 -14
- shipit/providers/registry.py +2 -0
- shipit/providers/staticfile.py +18 -6
- shipit/providers/wordpress.py +88 -0
- shipit/version.py +2 -2
- {shipit_cli-0.6.0.dist-info → shipit_cli-0.7.0.dist-info}/METADATA +2 -1
- shipit_cli-0.7.0.dist-info/RECORD +24 -0
- shipit_cli-0.6.0.dist-info/RECORD +0 -19
- {shipit_cli-0.6.0.dist-info → shipit_cli-0.7.0.dist-info}/WHEEL +0 -0
- {shipit_cli-0.6.0.dist-info → shipit_cli-0.7.0.dist-info}/entry_points.txt +0 -0
shipit/cli.py
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
import requests
|
|
2
3
|
import os
|
|
3
4
|
import shlex
|
|
4
5
|
import shutil
|
|
5
6
|
import sys
|
|
6
7
|
import json
|
|
7
8
|
import yaml
|
|
9
|
+
import base64
|
|
8
10
|
from dataclasses import dataclass
|
|
9
11
|
from pathlib import Path
|
|
10
12
|
from typing import (
|
|
@@ -32,6 +34,8 @@ from rich.syntax import Syntax
|
|
|
32
34
|
|
|
33
35
|
from shipit.version import version as shipit_version
|
|
34
36
|
from shipit.generator import generate_shipit
|
|
37
|
+
from shipit.providers.base import CustomCommands
|
|
38
|
+
from shipit.procfile import Procfile
|
|
35
39
|
|
|
36
40
|
|
|
37
41
|
console = Console()
|
|
@@ -49,10 +53,18 @@ class Mount:
|
|
|
49
53
|
serve_path: Path
|
|
50
54
|
|
|
51
55
|
|
|
56
|
+
@dataclass
|
|
57
|
+
class Volume:
|
|
58
|
+
name: str
|
|
59
|
+
serve_path: Path
|
|
60
|
+
|
|
61
|
+
|
|
52
62
|
@dataclass
|
|
53
63
|
class Service:
|
|
54
64
|
name: str
|
|
55
|
-
provider: Literal[
|
|
65
|
+
provider: Literal[
|
|
66
|
+
"postgres", "mysql", "redis"
|
|
67
|
+
] # Right now we only support postgres and mysql
|
|
56
68
|
|
|
57
69
|
|
|
58
70
|
@dataclass
|
|
@@ -63,10 +75,10 @@ class Serve:
|
|
|
63
75
|
deps: List["Package"]
|
|
64
76
|
commands: Dict[str, str]
|
|
65
77
|
cwd: Optional[str] = None
|
|
66
|
-
assets: Optional[Dict[str, str]] = None
|
|
67
78
|
prepare: Optional[List["PrepareStep"]] = None
|
|
68
79
|
workers: Optional[List[str]] = None
|
|
69
80
|
mounts: Optional[List[Mount]] = None
|
|
81
|
+
volumes: Optional[List[Volume]] = None
|
|
70
82
|
env: Optional[Dict[str, str]] = None
|
|
71
83
|
services: Optional[List[Service]] = None
|
|
72
84
|
|
|
@@ -98,6 +110,11 @@ class CopyStep:
|
|
|
98
110
|
source: str
|
|
99
111
|
target: str
|
|
100
112
|
ignore: Optional[List[str]] = None
|
|
113
|
+
# We can copy from the app source or from the shipit assets folder
|
|
114
|
+
base: Literal["source", "assets"] = "source"
|
|
115
|
+
|
|
116
|
+
def is_download(self) -> bool:
|
|
117
|
+
return self.source.startswith("http://") or self.source.startswith("https://")
|
|
101
118
|
|
|
102
119
|
|
|
103
120
|
@dataclass
|
|
@@ -147,7 +164,6 @@ class Builder(Protocol):
|
|
|
147
164
|
def build(
|
|
148
165
|
self, env: Dict[str, str], mounts: List[Mount], steps: List[Step]
|
|
149
166
|
) -> None: ...
|
|
150
|
-
def build_assets(self, assets: Dict[str, str]) -> None: ...
|
|
151
167
|
def build_prepare(self, serve: Serve) -> None: ...
|
|
152
168
|
def build_serve(self, serve: Serve) -> None: ...
|
|
153
169
|
def finalize_build(self, serve: Serve) -> None: ...
|
|
@@ -157,8 +173,6 @@ class Builder(Protocol):
|
|
|
157
173
|
def run_command(
|
|
158
174
|
self, command: str, extra_args: Optional[List[str]] | None = None
|
|
159
175
|
) -> Any: ...
|
|
160
|
-
def serve_mount(self, name: str) -> str: ...
|
|
161
|
-
def get_asset(self, name: str) -> str: ...
|
|
162
176
|
def get_build_mount_path(self, name: str) -> Path: ...
|
|
163
177
|
def get_serve_mount_path(self, name: str) -> Path: ...
|
|
164
178
|
|
|
@@ -258,13 +272,23 @@ class DockerBuilder:
|
|
|
258
272
|
|
|
259
273
|
def run_command(self, command: str, extra_args: Optional[List[str]] = None) -> Any:
|
|
260
274
|
image_name = self.docker_name_path.read_text()
|
|
261
|
-
|
|
262
|
-
"docker"
|
|
263
|
-
)(
|
|
275
|
+
docker_args: List[str] = [
|
|
264
276
|
"run",
|
|
265
277
|
"-p",
|
|
266
278
|
"80:80",
|
|
267
279
|
"--rm",
|
|
280
|
+
]
|
|
281
|
+
# Attach volumes if present
|
|
282
|
+
# if serve.volumes:
|
|
283
|
+
# for vol in serve.volumes:
|
|
284
|
+
# docker_args += [
|
|
285
|
+
# "--mount",
|
|
286
|
+
# f"type=volume,source={vol.name},target={str(vol.serve_path)}",
|
|
287
|
+
# ]
|
|
288
|
+
return sh.Command(
|
|
289
|
+
"docker"
|
|
290
|
+
)(
|
|
291
|
+
*docker_args,
|
|
268
292
|
image_name,
|
|
269
293
|
command,
|
|
270
294
|
*(extra_args or []),
|
|
@@ -317,10 +341,10 @@ RUN chmod {oct(mode)[2:]} {path.absolute()}
|
|
|
317
341
|
return
|
|
318
342
|
if dependency.version:
|
|
319
343
|
self.docker_file_contents += (
|
|
320
|
-
f"RUN
|
|
344
|
+
f"RUN mise use --global {dependency.name}@{dependency.version}\n"
|
|
321
345
|
)
|
|
322
346
|
else:
|
|
323
|
-
self.docker_file_contents += f"RUN
|
|
347
|
+
self.docker_file_contents += f"RUN mise use --global {dependency.name}\n"
|
|
324
348
|
|
|
325
349
|
def build(
|
|
326
350
|
self, env: Dict[str, str], mounts: List[Mount], steps: List[Step]
|
|
@@ -328,15 +352,26 @@ RUN chmod {oct(mode)[2:]} {path.absolute()}
|
|
|
328
352
|
base_path = self.docker_path
|
|
329
353
|
shutil.rmtree(base_path, ignore_errors=True)
|
|
330
354
|
base_path.mkdir(parents=True, exist_ok=True)
|
|
331
|
-
self.docker_file_contents = "FROM debian:
|
|
355
|
+
self.docker_file_contents = "FROM debian:trixie-slim AS build\n"
|
|
356
|
+
|
|
332
357
|
self.docker_file_contents += """
|
|
333
358
|
RUN apt-get update \\
|
|
334
|
-
&& apt-get -y --no-install-recommends install
|
|
359
|
+
&& apt-get -y --no-install-recommends install \\
|
|
360
|
+
build-essential gcc make \\
|
|
361
|
+
dpkg-dev pkg-config \\
|
|
362
|
+
libmariadb-dev libmariadb-dev-compat libpq-dev \\
|
|
363
|
+
sudo curl ca-certificates \\
|
|
335
364
|
&& rm -rf /var/lib/apt/lists/*
|
|
336
365
|
|
|
337
366
|
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
|
338
|
-
|
|
339
|
-
|
|
367
|
+
ENV MISE_DATA_DIR="/mise"
|
|
368
|
+
ENV MISE_CONFIG_DIR="/mise"
|
|
369
|
+
ENV MISE_CACHE_DIR="/mise/cache"
|
|
370
|
+
ENV MISE_INSTALL_PATH="/usr/local/bin/mise"
|
|
371
|
+
ENV PATH="/mise/shims:$PATH"
|
|
372
|
+
# ENV MISE_VERSION="..."
|
|
373
|
+
|
|
374
|
+
RUN curl https://mise.run | sh
|
|
340
375
|
"""
|
|
341
376
|
# docker_file_contents += "RUN curl https://mise.run | sh\n"
|
|
342
377
|
# self.docker_file_contents += """
|
|
@@ -361,7 +396,28 @@ RUN curl https://pkgx.sh | sh
|
|
|
361
396
|
pre = ""
|
|
362
397
|
self.docker_file_contents += f"RUN {pre}{step.command}\n"
|
|
363
398
|
elif isinstance(step, CopyStep):
|
|
364
|
-
|
|
399
|
+
if step.is_download():
|
|
400
|
+
self.docker_file_contents += (
|
|
401
|
+
"ADD " + step.source + " " + step.target + "\n"
|
|
402
|
+
)
|
|
403
|
+
elif step.base == "assets":
|
|
404
|
+
# Detect if the asset exists and is a file
|
|
405
|
+
if (ASSETS_PATH / step.source).is_file():
|
|
406
|
+
# Read the file content and write it to the target file
|
|
407
|
+
content_base64 = base64.b64encode(
|
|
408
|
+
(ASSETS_PATH / step.source).read_bytes()
|
|
409
|
+
)
|
|
410
|
+
self.docker_file_contents += (
|
|
411
|
+
f"RUN echo '{content_base64}' | base64 -d > {step.target}\n"
|
|
412
|
+
)
|
|
413
|
+
elif (ASSETS_PATH / step.source).is_dir():
|
|
414
|
+
raise Exception(
|
|
415
|
+
f"Asset {step.source} is a directory, shipit doesn't currently support coppying assets directories inside Docker"
|
|
416
|
+
)
|
|
417
|
+
else:
|
|
418
|
+
raise Exception(f"Asset {step.source} does not exist")
|
|
419
|
+
else:
|
|
420
|
+
self.docker_file_contents += f"COPY {step.source} {step.target}\n"
|
|
365
421
|
elif isinstance(step, EnvStep):
|
|
366
422
|
env_vars = " ".join(
|
|
367
423
|
[f"{key}={value}" for key, value in step.variables.items()]
|
|
@@ -386,9 +442,6 @@ FROM scratch
|
|
|
386
442
|
Shipit
|
|
387
443
|
""")
|
|
388
444
|
|
|
389
|
-
def build_assets(self, assets: Dict[str, str]) -> None:
|
|
390
|
-
raise NotImplementedError
|
|
391
|
-
|
|
392
445
|
def get_path(self) -> Path:
|
|
393
446
|
return Path("/")
|
|
394
447
|
|
|
@@ -398,11 +451,6 @@ Shipit
|
|
|
398
451
|
def get_serve_path(self) -> Path:
|
|
399
452
|
return self.get_path() / "serve"
|
|
400
453
|
|
|
401
|
-
def get_assets_path(self) -> Path:
|
|
402
|
-
path = self.get_path() / "assets"
|
|
403
|
-
self.mkdir(path)
|
|
404
|
-
return path
|
|
405
|
-
|
|
406
454
|
def prepare(self, env: Dict[str, str], prepare: List[PrepareStep]) -> None:
|
|
407
455
|
raise NotImplementedError
|
|
408
456
|
|
|
@@ -422,14 +470,6 @@ Shipit
|
|
|
422
470
|
mode=0o755,
|
|
423
471
|
)
|
|
424
472
|
|
|
425
|
-
def serve_mount(self, name: str) -> str:
|
|
426
|
-
path = self.mkdir(Path("serve") / "mounts" / name)
|
|
427
|
-
return str(path.absolute())
|
|
428
|
-
|
|
429
|
-
def get_asset(self, name: str) -> str:
|
|
430
|
-
asset_path = ASSETS_PATH / name
|
|
431
|
-
return asset_path.read_text()
|
|
432
|
-
|
|
433
473
|
def run_serve_command(self, command: str) -> None:
|
|
434
474
|
path = self.shipit_docker_path / "serve" / "bin" / command
|
|
435
475
|
self.run_command(str(path))
|
|
@@ -507,18 +547,31 @@ class LocalBuilder:
|
|
|
507
547
|
ignore_matches = step.ignore if step.ignore else []
|
|
508
548
|
ignore_matches.append(".shipit")
|
|
509
549
|
ignore_matches.append("Shipit")
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
(build_path / step.target),
|
|
514
|
-
dirs_exist_ok=True,
|
|
515
|
-
ignore=ignore_patterns(*ignore_matches),
|
|
516
|
-
)
|
|
550
|
+
|
|
551
|
+
if step.is_download():
|
|
552
|
+
download_file(step.source, (build_path / step.target))
|
|
517
553
|
else:
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
554
|
+
if step.base == "source":
|
|
555
|
+
base = self.src_dir
|
|
556
|
+
elif step.base == "assets":
|
|
557
|
+
base = ASSETS_PATH
|
|
558
|
+
else:
|
|
559
|
+
raise Exception(f"Unknown base: {step.base}")
|
|
560
|
+
|
|
561
|
+
if (base / step.source).is_dir():
|
|
562
|
+
copytree(
|
|
563
|
+
(base / step.source),
|
|
564
|
+
(build_path / step.target),
|
|
565
|
+
dirs_exist_ok=True,
|
|
566
|
+
ignore=ignore_patterns(*ignore_matches),
|
|
567
|
+
)
|
|
568
|
+
elif (base / step.source).is_file():
|
|
569
|
+
copy(
|
|
570
|
+
(base / step.source),
|
|
571
|
+
(build_path / step.target),
|
|
572
|
+
)
|
|
573
|
+
else:
|
|
574
|
+
raise Exception(f"Source {step.source} is not a file or directory")
|
|
522
575
|
elif isinstance(step, EnvStep):
|
|
523
576
|
print(f"Setting environment variables: {step}")
|
|
524
577
|
env.update(step.variables)
|
|
@@ -580,17 +633,6 @@ class LocalBuilder:
|
|
|
580
633
|
def get_serve_path(self) -> Path:
|
|
581
634
|
return self.get_path() / "serve"
|
|
582
635
|
|
|
583
|
-
def get_assets_path(self) -> Path:
|
|
584
|
-
path = self.get_path() / "assets"
|
|
585
|
-
self.mkdir(path)
|
|
586
|
-
return path
|
|
587
|
-
|
|
588
|
-
def build_assets(self, assets: Dict[str, str]) -> None:
|
|
589
|
-
assets_path = self.get_assets_path()
|
|
590
|
-
for asset in assets:
|
|
591
|
-
asset_path = assets_path / asset
|
|
592
|
-
self.create_file(asset_path, assets[asset])
|
|
593
|
-
|
|
594
636
|
def build_prepare(self, serve: Serve) -> None:
|
|
595
637
|
self.prepare_bash_script.parent.mkdir(parents=True, exist_ok=True)
|
|
596
638
|
commands: List[str] = []
|
|
@@ -631,6 +673,7 @@ class LocalBuilder:
|
|
|
631
673
|
)
|
|
632
674
|
|
|
633
675
|
def build_serve(self, serve: Serve) -> None:
|
|
676
|
+
# Remember serve configuration for run-time
|
|
634
677
|
console.print("\n[bold]Building serve[/bold]")
|
|
635
678
|
serve_command_path = self.get_serve_path() / "bin"
|
|
636
679
|
serve_command_path.mkdir(parents=True, exist_ok=False)
|
|
@@ -640,10 +683,12 @@ class LocalBuilder:
|
|
|
640
683
|
for command in serve.commands:
|
|
641
684
|
console.print(f"* {command}")
|
|
642
685
|
command_path = serve_command_path / command
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
686
|
+
env_vars = ""
|
|
687
|
+
if serve.env:
|
|
688
|
+
env_vars = " ".join([f"{k}={v}" for k, v in serve.env.items()])
|
|
689
|
+
|
|
690
|
+
content = f"#!/bin/bash\ncd {serve.cwd}\nPATH={path_text}:$PATH {env_vars} {serve.commands[command]}"
|
|
691
|
+
command_path.write_text(content)
|
|
647
692
|
manifest_panel = Panel(
|
|
648
693
|
Syntax(
|
|
649
694
|
content.strip(),
|
|
@@ -665,15 +710,6 @@ class LocalBuilder:
|
|
|
665
710
|
command_path = base_path / command
|
|
666
711
|
sh.Command(str(command_path))(_out=write_stdout, _err=write_stderr)
|
|
667
712
|
|
|
668
|
-
def serve_mount(self, name: str) -> str:
|
|
669
|
-
base_path = self.get_serve_path() / "mounts" / name
|
|
670
|
-
base_path.mkdir(parents=True, exist_ok=True)
|
|
671
|
-
return str(base_path.absolute())
|
|
672
|
-
|
|
673
|
-
def get_asset(self, name: str) -> str:
|
|
674
|
-
asset_path = ASSETS_PATH / name
|
|
675
|
-
return asset_path.read_text()
|
|
676
|
-
|
|
677
713
|
|
|
678
714
|
class WasmerBuilder:
|
|
679
715
|
def get_build_mount_path(self, name: str) -> Path:
|
|
@@ -714,8 +750,8 @@ class WasmerBuilder:
|
|
|
714
750
|
},
|
|
715
751
|
"php": {
|
|
716
752
|
"dependencies": {
|
|
717
|
-
"latest": "php/php-32@=8.3.
|
|
718
|
-
"8.3": "php/php-32@=8.3.
|
|
753
|
+
"latest": "php/php-32@=8.3.2102",
|
|
754
|
+
"8.3": "php/php-32@=8.3.2102",
|
|
719
755
|
},
|
|
720
756
|
"scripts": {"php"},
|
|
721
757
|
"aliases": {},
|
|
@@ -771,9 +807,6 @@ class WasmerBuilder:
|
|
|
771
807
|
) -> None:
|
|
772
808
|
return self.inner_builder.build(env, mounts, build)
|
|
773
809
|
|
|
774
|
-
def build_assets(self, assets: Dict[str, str]) -> None:
|
|
775
|
-
return self.inner_builder.build_assets(assets)
|
|
776
|
-
|
|
777
810
|
def get_build_path(self) -> Path:
|
|
778
811
|
return Path("/app")
|
|
779
812
|
|
|
@@ -897,9 +930,6 @@ class WasmerBuilder:
|
|
|
897
930
|
fs = table()
|
|
898
931
|
doc.add("fs", fs)
|
|
899
932
|
inner = cast(Any, self.inner_builder)
|
|
900
|
-
if serve.assets:
|
|
901
|
-
fs.add("/assets", str((inner.get_path() / "assets").absolute()))
|
|
902
|
-
# fs.add("/app", str(inner.get_build_path().absolute()))
|
|
903
933
|
if serve.mounts:
|
|
904
934
|
for mount in serve.mounts:
|
|
905
935
|
fs.add(
|
|
@@ -975,22 +1005,37 @@ class WasmerBuilder:
|
|
|
975
1005
|
# has_postgres = any(service.provider == "postgres" for service in serve.services)
|
|
976
1006
|
# has_redis = any(service.provider == "redis" for service in serve.services)
|
|
977
1007
|
if has_mysql:
|
|
978
|
-
capabilities["database"] = {
|
|
979
|
-
"engine": "mysql"
|
|
980
|
-
}
|
|
1008
|
+
capabilities["database"] = {"engine": "mysql"}
|
|
981
1009
|
yaml_config["capabilities"] = capabilities
|
|
982
1010
|
|
|
1011
|
+
# Attach declared volumes to the app manifest (serve-time mounts)
|
|
1012
|
+
if serve.volumes:
|
|
1013
|
+
volumes_yaml = yaml_config.get("volumes", [])
|
|
1014
|
+
for vol in serve.volumes:
|
|
1015
|
+
volumes_yaml.append(
|
|
1016
|
+
{
|
|
1017
|
+
"name": vol.name,
|
|
1018
|
+
"mount": str(vol.serve_path),
|
|
1019
|
+
}
|
|
1020
|
+
)
|
|
1021
|
+
yaml_config["volumes"] = volumes_yaml
|
|
1022
|
+
|
|
1023
|
+
# If it has a php dependency, set the scaling mode to single_concurrency
|
|
1024
|
+
has_php = any(dep.name == "php" for dep in serve.deps)
|
|
1025
|
+
if has_php:
|
|
1026
|
+
scaling = yaml_config.get("scaling", {})
|
|
1027
|
+
scaling["mode"] = "single_concurrency"
|
|
1028
|
+
yaml_config["scaling"] = scaling
|
|
1029
|
+
|
|
983
1030
|
if "after_deploy" in serve.commands:
|
|
984
1031
|
jobs = yaml_config.get("jobs", [])
|
|
985
|
-
jobs.append(
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
"execute": {
|
|
990
|
-
"command": "after_deploy"
|
|
991
|
-
}
|
|
1032
|
+
jobs.append(
|
|
1033
|
+
{
|
|
1034
|
+
"name": "after_deploy",
|
|
1035
|
+
"trigger": "post-deployment",
|
|
1036
|
+
"action": {"execute": {"command": "after_deploy"}},
|
|
992
1037
|
}
|
|
993
|
-
|
|
1038
|
+
)
|
|
994
1039
|
yaml_config["jobs"] = jobs
|
|
995
1040
|
|
|
996
1041
|
app_yaml = yaml.dump(yaml_config)
|
|
@@ -1032,12 +1077,6 @@ class WasmerBuilder:
|
|
|
1032
1077
|
],
|
|
1033
1078
|
)
|
|
1034
1079
|
|
|
1035
|
-
def serve_mount(self, name: str) -> str:
|
|
1036
|
-
return self.inner_builder.serve_mount(name)
|
|
1037
|
-
|
|
1038
|
-
def get_asset(self, name: str) -> str:
|
|
1039
|
-
return self.inner_builder.get_asset(name)
|
|
1040
|
-
|
|
1041
1080
|
def run_command(
|
|
1042
1081
|
self, command: str, extra_args: Optional[List[str]] | None = None
|
|
1043
1082
|
) -> Any:
|
|
@@ -1090,6 +1129,7 @@ class Ctx:
|
|
|
1090
1129
|
self.steps: List[Step] = []
|
|
1091
1130
|
self.serves: Dict[str, Serve] = {}
|
|
1092
1131
|
self.mounts: List[Mount] = []
|
|
1132
|
+
self.volumes: List[Volume] = []
|
|
1093
1133
|
self.services: Dict[str, Service] = {}
|
|
1094
1134
|
|
|
1095
1135
|
def add_package(self, package: Package) -> str:
|
|
@@ -1112,6 +1152,8 @@ class Ctx:
|
|
|
1112
1152
|
return self.steps[int(index[len("ref:step:") :])]
|
|
1113
1153
|
elif index.startswith("ref:mount:"):
|
|
1114
1154
|
return self.mounts[int(index[len("ref:mount:") :])]
|
|
1155
|
+
elif index.startswith("ref:volume:"):
|
|
1156
|
+
return self.volumes[int(index[len("ref:volume:") :])]
|
|
1115
1157
|
elif index.startswith("ref:service:"):
|
|
1116
1158
|
return self.services[index[len("ref:service:") :]]
|
|
1117
1159
|
else:
|
|
@@ -1137,14 +1179,13 @@ class Ctx:
|
|
|
1137
1179
|
def getenv(self, name: str) -> Optional[str]:
|
|
1138
1180
|
return self.builder.getenv(name)
|
|
1139
1181
|
|
|
1140
|
-
def get_asset(self, name: str) -> Optional[str]:
|
|
1141
|
-
return self.builder.get_asset(name)
|
|
1142
|
-
|
|
1143
1182
|
def dep(self, name: str, version: Optional[str] = None) -> str:
|
|
1144
1183
|
package = Package(name, version)
|
|
1145
1184
|
return self.add_package(package)
|
|
1146
|
-
|
|
1147
|
-
def service(
|
|
1185
|
+
|
|
1186
|
+
def service(
|
|
1187
|
+
self, name: str, provider: Literal["postgres", "mysql", "redis"]
|
|
1188
|
+
) -> str:
|
|
1148
1189
|
service = Service(name, provider)
|
|
1149
1190
|
return self.add_service(service)
|
|
1150
1191
|
|
|
@@ -1156,10 +1197,10 @@ class Ctx:
|
|
|
1156
1197
|
deps: List[str],
|
|
1157
1198
|
commands: Dict[str, str],
|
|
1158
1199
|
cwd: Optional[str] = None,
|
|
1159
|
-
assets: Optional[Dict[str, str]] = None,
|
|
1160
1200
|
prepare: Optional[List[str]] = None,
|
|
1161
1201
|
workers: Optional[List[str]] = None,
|
|
1162
1202
|
mounts: Optional[List[Mount]] = None,
|
|
1203
|
+
volumes: Optional[List[Volume]] = None,
|
|
1163
1204
|
env: Optional[Dict[str, str]] = None,
|
|
1164
1205
|
services: Optional[List[str]] = None,
|
|
1165
1206
|
) -> str:
|
|
@@ -1177,7 +1218,6 @@ class Ctx:
|
|
|
1177
1218
|
provider=provider,
|
|
1178
1219
|
build=build_refs,
|
|
1179
1220
|
cwd=cwd,
|
|
1180
|
-
assets=assets,
|
|
1181
1221
|
deps=dep_refs,
|
|
1182
1222
|
commands=commands,
|
|
1183
1223
|
prepare=prepare_steps,
|
|
@@ -1185,6 +1225,9 @@ class Ctx:
|
|
|
1185
1225
|
mounts=self.get_refs([mount["ref"] for mount in mounts])
|
|
1186
1226
|
if mounts
|
|
1187
1227
|
else None,
|
|
1228
|
+
volumes=self.get_refs([volume["ref"] for volume in volumes])
|
|
1229
|
+
if volumes
|
|
1230
|
+
else None,
|
|
1188
1231
|
env=env,
|
|
1189
1232
|
services=self.get_refs(services) if services else None,
|
|
1190
1233
|
)
|
|
@@ -1208,14 +1251,15 @@ class Ctx:
|
|
|
1208
1251
|
return self.add_step(step)
|
|
1209
1252
|
|
|
1210
1253
|
def copy(
|
|
1211
|
-
self,
|
|
1254
|
+
self,
|
|
1255
|
+
source: str,
|
|
1256
|
+
target: str,
|
|
1257
|
+
ignore: Optional[List[str]] = None,
|
|
1258
|
+
base: Optional[Literal["source", "assets"]] = None,
|
|
1212
1259
|
) -> Optional[str]:
|
|
1213
|
-
step = CopyStep(source, target, ignore)
|
|
1260
|
+
step = CopyStep(source, target, ignore, base or "source")
|
|
1214
1261
|
return self.add_step(step)
|
|
1215
1262
|
|
|
1216
|
-
def buildpath(self, name: str) -> str:
|
|
1217
|
-
return str((self.builder.get_build_path() / name).absolute())
|
|
1218
|
-
|
|
1219
1263
|
def env(self, **env_vars: str) -> Optional[str]:
|
|
1220
1264
|
step = EnvStep(env_vars)
|
|
1221
1265
|
return self.add_step(step)
|
|
@@ -1235,8 +1279,18 @@ class Ctx:
|
|
|
1235
1279
|
"serve": str(serve_path.absolute()),
|
|
1236
1280
|
}
|
|
1237
1281
|
|
|
1238
|
-
def
|
|
1239
|
-
|
|
1282
|
+
def add_volume(self, volume: Volume) -> Optional[str]:
|
|
1283
|
+
self.volumes.append(volume)
|
|
1284
|
+
return f"ref:volume:{len(self.volumes) - 1}"
|
|
1285
|
+
|
|
1286
|
+
def volume(self, name: str, serve: str) -> Optional[str]:
|
|
1287
|
+
volume = Volume(name=name, serve_path=Path(serve))
|
|
1288
|
+
ref = self.add_volume(volume)
|
|
1289
|
+
return {
|
|
1290
|
+
"ref": ref,
|
|
1291
|
+
"name": name,
|
|
1292
|
+
"serve": str(volume.serve_path),
|
|
1293
|
+
}
|
|
1240
1294
|
|
|
1241
1295
|
|
|
1242
1296
|
def print_help() -> None:
|
|
@@ -1249,6 +1303,13 @@ def print_help() -> None:
|
|
|
1249
1303
|
console.print(panel)
|
|
1250
1304
|
|
|
1251
1305
|
|
|
1306
|
+
def download_file(url: str, path: Path) -> None:
|
|
1307
|
+
response = requests.get(url)
|
|
1308
|
+
response.raise_for_status()
|
|
1309
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
1310
|
+
path.write_bytes(response.content)
|
|
1311
|
+
|
|
1312
|
+
|
|
1252
1313
|
@app.command(name="auto")
|
|
1253
1314
|
def auto(
|
|
1254
1315
|
path: Path = typer.Argument(
|
|
@@ -1308,12 +1369,35 @@ def auto(
|
|
|
1308
1369
|
None,
|
|
1309
1370
|
help="Name of the Wasmer app.",
|
|
1310
1371
|
),
|
|
1372
|
+
use_procfile: bool = typer.Option(
|
|
1373
|
+
True,
|
|
1374
|
+
help="Use the Procfile to generate the default custom commands (install, build, start, after_deploy).",
|
|
1375
|
+
),
|
|
1376
|
+
install_command: Optional[str] = typer.Option(
|
|
1377
|
+
None,
|
|
1378
|
+
help="The install command to use (overwrites the default)",
|
|
1379
|
+
),
|
|
1380
|
+
build_command: Optional[str] = typer.Option(
|
|
1381
|
+
None,
|
|
1382
|
+
help="The build command to use (overwrites the default)",
|
|
1383
|
+
),
|
|
1384
|
+
start_command: Optional[str] = typer.Option(
|
|
1385
|
+
None,
|
|
1386
|
+
help="The start command to use (overwrites the default)",
|
|
1387
|
+
),
|
|
1311
1388
|
):
|
|
1312
1389
|
if not path.exists():
|
|
1313
1390
|
raise Exception(f"The path {path} does not exist")
|
|
1314
1391
|
|
|
1315
1392
|
if not (path / "Shipit").exists() or regenerate or regenerate_path is not None:
|
|
1316
|
-
generate(
|
|
1393
|
+
generate(
|
|
1394
|
+
path,
|
|
1395
|
+
out=regenerate_path,
|
|
1396
|
+
use_procfile=use_procfile,
|
|
1397
|
+
install_command=install_command,
|
|
1398
|
+
build_command=build_command,
|
|
1399
|
+
start_command=start_command,
|
|
1400
|
+
)
|
|
1317
1401
|
|
|
1318
1402
|
build(
|
|
1319
1403
|
path,
|
|
@@ -1353,13 +1437,53 @@ def generate(
|
|
|
1353
1437
|
None,
|
|
1354
1438
|
help="Output path (defaults to the Shipit file in the provided path).",
|
|
1355
1439
|
),
|
|
1440
|
+
use_procfile: bool = typer.Option(
|
|
1441
|
+
True,
|
|
1442
|
+
help="Use the Procfile to generate the default custom commands (install, build, start, after_deploy).",
|
|
1443
|
+
),
|
|
1444
|
+
install_command: Optional[str] = typer.Option(
|
|
1445
|
+
None,
|
|
1446
|
+
help="The install command to use (overwrites the default)",
|
|
1447
|
+
),
|
|
1448
|
+
build_command: Optional[str] = typer.Option(
|
|
1449
|
+
None,
|
|
1450
|
+
help="The build command to use (overwrites the default)",
|
|
1451
|
+
),
|
|
1452
|
+
start_command: Optional[str] = typer.Option(
|
|
1453
|
+
None,
|
|
1454
|
+
help="The start command to use (overwrites the default)",
|
|
1455
|
+
),
|
|
1356
1456
|
):
|
|
1357
1457
|
if not path.exists():
|
|
1358
1458
|
raise Exception(f"The path {path} does not exist")
|
|
1359
1459
|
|
|
1360
1460
|
if out is None:
|
|
1361
1461
|
out = path / "Shipit"
|
|
1362
|
-
|
|
1462
|
+
custom_commands = CustomCommands()
|
|
1463
|
+
# if (path / "Dockerfile").exists():
|
|
1464
|
+
# # We get the start command from the Dockerfile
|
|
1465
|
+
# with open(path / "Dockerfile", "r") as f:
|
|
1466
|
+
# cmd = None
|
|
1467
|
+
# for line in f:
|
|
1468
|
+
# if line.startswith("CMD "):
|
|
1469
|
+
# cmd = line[4:].strip()
|
|
1470
|
+
# cmd = json.loads(cmd)
|
|
1471
|
+
# # We get the last command
|
|
1472
|
+
# if cmd:
|
|
1473
|
+
# if isinstance(cmd, list):
|
|
1474
|
+
# cmd = " ".join(cmd)
|
|
1475
|
+
# custom_commands.start = cmd
|
|
1476
|
+
if use_procfile:
|
|
1477
|
+
if (path / "Procfile").exists():
|
|
1478
|
+
procfile = Procfile.loads((path / "Procfile").read_text())
|
|
1479
|
+
custom_commands.start = procfile.get_start_command()
|
|
1480
|
+
if start_command:
|
|
1481
|
+
custom_commands.start = start_command
|
|
1482
|
+
if install_command:
|
|
1483
|
+
custom_commands.install = install_command
|
|
1484
|
+
if build_command:
|
|
1485
|
+
custom_commands.build = build_command
|
|
1486
|
+
content = generate_shipit(path, custom_commands)
|
|
1363
1487
|
out.write_text(content)
|
|
1364
1488
|
console.print(f"[bold]Generated Shipit[/bold] at {out.absolute()}")
|
|
1365
1489
|
|
|
@@ -1518,15 +1642,12 @@ def build(
|
|
|
1518
1642
|
mod.add_callable("serve", ctx.serve)
|
|
1519
1643
|
mod.add_callable("run", ctx.run)
|
|
1520
1644
|
mod.add_callable("mount", ctx.mount)
|
|
1645
|
+
mod.add_callable("volume", ctx.volume)
|
|
1521
1646
|
mod.add_callable("workdir", ctx.workdir)
|
|
1522
1647
|
mod.add_callable("copy", ctx.copy)
|
|
1523
1648
|
mod.add_callable("path", ctx.path)
|
|
1524
|
-
mod.add_callable("buildpath", ctx.buildpath)
|
|
1525
|
-
mod.add_callable("get_asset", ctx.get_asset)
|
|
1526
1649
|
mod.add_callable("env", ctx.env)
|
|
1527
1650
|
mod.add_callable("use", ctx.use)
|
|
1528
|
-
# REMOVE ME
|
|
1529
|
-
mod.add_callable("serve_mount", ctx.serve_mount)
|
|
1530
1651
|
|
|
1531
1652
|
dialect = sl.Dialect.extended()
|
|
1532
1653
|
dialect.enable_f_strings = True
|
|
@@ -1534,9 +1655,7 @@ def build(
|
|
|
1534
1655
|
ast = sl.parse("shipit", source, dialect=dialect)
|
|
1535
1656
|
|
|
1536
1657
|
sl.eval(mod, ast, glb)
|
|
1537
|
-
# assert len(ctx.builds) == 1, "Only one build is allowed for now"
|
|
1538
1658
|
assert len(ctx.serves) <= 1, "Only one serve is allowed for now"
|
|
1539
|
-
# build = ctx.builds[0]
|
|
1540
1659
|
env = {
|
|
1541
1660
|
"PATH": "",
|
|
1542
1661
|
"COLORTERM": os.environ.get("COLORTERM", ""),
|
|
@@ -1550,8 +1669,6 @@ def build(
|
|
|
1550
1669
|
builder.build(env, serve.mounts, serve.build)
|
|
1551
1670
|
if serve.prepare:
|
|
1552
1671
|
builder.build_prepare(serve)
|
|
1553
|
-
if serve.assets:
|
|
1554
|
-
builder.build_assets(serve.assets)
|
|
1555
1672
|
builder.build_serve(serve)
|
|
1556
1673
|
builder.finalize_build(serve)
|
|
1557
1674
|
if serve.prepare and not skip_prepare:
|
|
@@ -1569,7 +1686,7 @@ def main() -> None:
|
|
|
1569
1686
|
app()
|
|
1570
1687
|
except Exception as e:
|
|
1571
1688
|
console.print(f"[bold red]{type(e).__name__}[/bold red]: {e}")
|
|
1572
|
-
|
|
1689
|
+
raise e
|
|
1573
1690
|
|
|
1574
1691
|
|
|
1575
1692
|
if __name__ == "__main__":
|
shipit/env.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import shlex
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
def parse(content):
|
|
5
|
+
"""
|
|
6
|
+
Parse the content of a .env file (a line-delimited KEY=value format) into a
|
|
7
|
+
dictionary mapping keys to values.
|
|
8
|
+
"""
|
|
9
|
+
values = {}
|
|
10
|
+
for line in content.splitlines():
|
|
11
|
+
lexer = shlex.shlex(line, posix=True)
|
|
12
|
+
tokens = list(lexer)
|
|
13
|
+
|
|
14
|
+
# parses the assignment statement
|
|
15
|
+
if len(tokens) < 3:
|
|
16
|
+
continue
|
|
17
|
+
|
|
18
|
+
name, op = tokens[:2]
|
|
19
|
+
value = ''.join(tokens[2:])
|
|
20
|
+
|
|
21
|
+
if op != '=':
|
|
22
|
+
continue
|
|
23
|
+
if not re.match(r'[A-Za-z_][A-Za-z_0-9]*', name):
|
|
24
|
+
continue
|
|
25
|
+
|
|
26
|
+
value = value.replace(r'\n', '\n')
|
|
27
|
+
value = value.replace(r'\t', '\t')
|
|
28
|
+
values[name] = value
|
|
29
|
+
|
|
30
|
+
return values
|