shipit-cli 0.6.1__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 +216 -110
- 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.1.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.1.dist-info/RECORD +0 -19
- {shipit_cli-0.6.1.dist-info → shipit_cli-0.7.0.dist-info}/WHEEL +0 -0
- {shipit_cli-0.6.1.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 []),
|
|
@@ -372,7 +396,28 @@ RUN curl https://mise.run | sh
|
|
|
372
396
|
pre = ""
|
|
373
397
|
self.docker_file_contents += f"RUN {pre}{step.command}\n"
|
|
374
398
|
elif isinstance(step, CopyStep):
|
|
375
|
-
|
|
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"
|
|
376
421
|
elif isinstance(step, EnvStep):
|
|
377
422
|
env_vars = " ".join(
|
|
378
423
|
[f"{key}={value}" for key, value in step.variables.items()]
|
|
@@ -397,9 +442,6 @@ FROM scratch
|
|
|
397
442
|
Shipit
|
|
398
443
|
""")
|
|
399
444
|
|
|
400
|
-
def build_assets(self, assets: Dict[str, str]) -> None:
|
|
401
|
-
raise NotImplementedError
|
|
402
|
-
|
|
403
445
|
def get_path(self) -> Path:
|
|
404
446
|
return Path("/")
|
|
405
447
|
|
|
@@ -409,11 +451,6 @@ Shipit
|
|
|
409
451
|
def get_serve_path(self) -> Path:
|
|
410
452
|
return self.get_path() / "serve"
|
|
411
453
|
|
|
412
|
-
def get_assets_path(self) -> Path:
|
|
413
|
-
path = self.get_path() / "assets"
|
|
414
|
-
self.mkdir(path)
|
|
415
|
-
return path
|
|
416
|
-
|
|
417
454
|
def prepare(self, env: Dict[str, str], prepare: List[PrepareStep]) -> None:
|
|
418
455
|
raise NotImplementedError
|
|
419
456
|
|
|
@@ -433,14 +470,6 @@ Shipit
|
|
|
433
470
|
mode=0o755,
|
|
434
471
|
)
|
|
435
472
|
|
|
436
|
-
def serve_mount(self, name: str) -> str:
|
|
437
|
-
path = self.mkdir(Path("serve") / "mounts" / name)
|
|
438
|
-
return str(path.absolute())
|
|
439
|
-
|
|
440
|
-
def get_asset(self, name: str) -> str:
|
|
441
|
-
asset_path = ASSETS_PATH / name
|
|
442
|
-
return asset_path.read_text()
|
|
443
|
-
|
|
444
473
|
def run_serve_command(self, command: str) -> None:
|
|
445
474
|
path = self.shipit_docker_path / "serve" / "bin" / command
|
|
446
475
|
self.run_command(str(path))
|
|
@@ -518,18 +547,31 @@ class LocalBuilder:
|
|
|
518
547
|
ignore_matches = step.ignore if step.ignore else []
|
|
519
548
|
ignore_matches.append(".shipit")
|
|
520
549
|
ignore_matches.append("Shipit")
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
(build_path / step.target),
|
|
525
|
-
dirs_exist_ok=True,
|
|
526
|
-
ignore=ignore_patterns(*ignore_matches),
|
|
527
|
-
)
|
|
550
|
+
|
|
551
|
+
if step.is_download():
|
|
552
|
+
download_file(step.source, (build_path / step.target))
|
|
528
553
|
else:
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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")
|
|
533
575
|
elif isinstance(step, EnvStep):
|
|
534
576
|
print(f"Setting environment variables: {step}")
|
|
535
577
|
env.update(step.variables)
|
|
@@ -591,17 +633,6 @@ class LocalBuilder:
|
|
|
591
633
|
def get_serve_path(self) -> Path:
|
|
592
634
|
return self.get_path() / "serve"
|
|
593
635
|
|
|
594
|
-
def get_assets_path(self) -> Path:
|
|
595
|
-
path = self.get_path() / "assets"
|
|
596
|
-
self.mkdir(path)
|
|
597
|
-
return path
|
|
598
|
-
|
|
599
|
-
def build_assets(self, assets: Dict[str, str]) -> None:
|
|
600
|
-
assets_path = self.get_assets_path()
|
|
601
|
-
for asset in assets:
|
|
602
|
-
asset_path = assets_path / asset
|
|
603
|
-
self.create_file(asset_path, assets[asset])
|
|
604
|
-
|
|
605
636
|
def build_prepare(self, serve: Serve) -> None:
|
|
606
637
|
self.prepare_bash_script.parent.mkdir(parents=True, exist_ok=True)
|
|
607
638
|
commands: List[str] = []
|
|
@@ -642,6 +673,7 @@ class LocalBuilder:
|
|
|
642
673
|
)
|
|
643
674
|
|
|
644
675
|
def build_serve(self, serve: Serve) -> None:
|
|
676
|
+
# Remember serve configuration for run-time
|
|
645
677
|
console.print("\n[bold]Building serve[/bold]")
|
|
646
678
|
serve_command_path = self.get_serve_path() / "bin"
|
|
647
679
|
serve_command_path.mkdir(parents=True, exist_ok=False)
|
|
@@ -651,10 +683,12 @@ class LocalBuilder:
|
|
|
651
683
|
for command in serve.commands:
|
|
652
684
|
console.print(f"* {command}")
|
|
653
685
|
command_path = serve_command_path / command
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
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)
|
|
658
692
|
manifest_panel = Panel(
|
|
659
693
|
Syntax(
|
|
660
694
|
content.strip(),
|
|
@@ -676,15 +710,6 @@ class LocalBuilder:
|
|
|
676
710
|
command_path = base_path / command
|
|
677
711
|
sh.Command(str(command_path))(_out=write_stdout, _err=write_stderr)
|
|
678
712
|
|
|
679
|
-
def serve_mount(self, name: str) -> str:
|
|
680
|
-
base_path = self.get_serve_path() / "mounts" / name
|
|
681
|
-
base_path.mkdir(parents=True, exist_ok=True)
|
|
682
|
-
return str(base_path.absolute())
|
|
683
|
-
|
|
684
|
-
def get_asset(self, name: str) -> str:
|
|
685
|
-
asset_path = ASSETS_PATH / name
|
|
686
|
-
return asset_path.read_text()
|
|
687
|
-
|
|
688
713
|
|
|
689
714
|
class WasmerBuilder:
|
|
690
715
|
def get_build_mount_path(self, name: str) -> Path:
|
|
@@ -725,8 +750,8 @@ class WasmerBuilder:
|
|
|
725
750
|
},
|
|
726
751
|
"php": {
|
|
727
752
|
"dependencies": {
|
|
728
|
-
"latest": "php/php-32@=8.3.
|
|
729
|
-
"8.3": "php/php-32@=8.3.
|
|
753
|
+
"latest": "php/php-32@=8.3.2102",
|
|
754
|
+
"8.3": "php/php-32@=8.3.2102",
|
|
730
755
|
},
|
|
731
756
|
"scripts": {"php"},
|
|
732
757
|
"aliases": {},
|
|
@@ -782,9 +807,6 @@ class WasmerBuilder:
|
|
|
782
807
|
) -> None:
|
|
783
808
|
return self.inner_builder.build(env, mounts, build)
|
|
784
809
|
|
|
785
|
-
def build_assets(self, assets: Dict[str, str]) -> None:
|
|
786
|
-
return self.inner_builder.build_assets(assets)
|
|
787
|
-
|
|
788
810
|
def get_build_path(self) -> Path:
|
|
789
811
|
return Path("/app")
|
|
790
812
|
|
|
@@ -908,9 +930,6 @@ class WasmerBuilder:
|
|
|
908
930
|
fs = table()
|
|
909
931
|
doc.add("fs", fs)
|
|
910
932
|
inner = cast(Any, self.inner_builder)
|
|
911
|
-
if serve.assets:
|
|
912
|
-
fs.add("/assets", str((inner.get_path() / "assets").absolute()))
|
|
913
|
-
# fs.add("/app", str(inner.get_build_path().absolute()))
|
|
914
933
|
if serve.mounts:
|
|
915
934
|
for mount in serve.mounts:
|
|
916
935
|
fs.add(
|
|
@@ -986,22 +1005,37 @@ class WasmerBuilder:
|
|
|
986
1005
|
# has_postgres = any(service.provider == "postgres" for service in serve.services)
|
|
987
1006
|
# has_redis = any(service.provider == "redis" for service in serve.services)
|
|
988
1007
|
if has_mysql:
|
|
989
|
-
capabilities["database"] = {
|
|
990
|
-
"engine": "mysql"
|
|
991
|
-
}
|
|
1008
|
+
capabilities["database"] = {"engine": "mysql"}
|
|
992
1009
|
yaml_config["capabilities"] = capabilities
|
|
993
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
|
+
|
|
994
1030
|
if "after_deploy" in serve.commands:
|
|
995
1031
|
jobs = yaml_config.get("jobs", [])
|
|
996
|
-
jobs.append(
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
"execute": {
|
|
1001
|
-
"command": "after_deploy"
|
|
1002
|
-
}
|
|
1032
|
+
jobs.append(
|
|
1033
|
+
{
|
|
1034
|
+
"name": "after_deploy",
|
|
1035
|
+
"trigger": "post-deployment",
|
|
1036
|
+
"action": {"execute": {"command": "after_deploy"}},
|
|
1003
1037
|
}
|
|
1004
|
-
|
|
1038
|
+
)
|
|
1005
1039
|
yaml_config["jobs"] = jobs
|
|
1006
1040
|
|
|
1007
1041
|
app_yaml = yaml.dump(yaml_config)
|
|
@@ -1043,12 +1077,6 @@ class WasmerBuilder:
|
|
|
1043
1077
|
],
|
|
1044
1078
|
)
|
|
1045
1079
|
|
|
1046
|
-
def serve_mount(self, name: str) -> str:
|
|
1047
|
-
return self.inner_builder.serve_mount(name)
|
|
1048
|
-
|
|
1049
|
-
def get_asset(self, name: str) -> str:
|
|
1050
|
-
return self.inner_builder.get_asset(name)
|
|
1051
|
-
|
|
1052
1080
|
def run_command(
|
|
1053
1081
|
self, command: str, extra_args: Optional[List[str]] | None = None
|
|
1054
1082
|
) -> Any:
|
|
@@ -1101,6 +1129,7 @@ class Ctx:
|
|
|
1101
1129
|
self.steps: List[Step] = []
|
|
1102
1130
|
self.serves: Dict[str, Serve] = {}
|
|
1103
1131
|
self.mounts: List[Mount] = []
|
|
1132
|
+
self.volumes: List[Volume] = []
|
|
1104
1133
|
self.services: Dict[str, Service] = {}
|
|
1105
1134
|
|
|
1106
1135
|
def add_package(self, package: Package) -> str:
|
|
@@ -1123,6 +1152,8 @@ class Ctx:
|
|
|
1123
1152
|
return self.steps[int(index[len("ref:step:") :])]
|
|
1124
1153
|
elif index.startswith("ref:mount:"):
|
|
1125
1154
|
return self.mounts[int(index[len("ref:mount:") :])]
|
|
1155
|
+
elif index.startswith("ref:volume:"):
|
|
1156
|
+
return self.volumes[int(index[len("ref:volume:") :])]
|
|
1126
1157
|
elif index.startswith("ref:service:"):
|
|
1127
1158
|
return self.services[index[len("ref:service:") :]]
|
|
1128
1159
|
else:
|
|
@@ -1148,14 +1179,13 @@ class Ctx:
|
|
|
1148
1179
|
def getenv(self, name: str) -> Optional[str]:
|
|
1149
1180
|
return self.builder.getenv(name)
|
|
1150
1181
|
|
|
1151
|
-
def get_asset(self, name: str) -> Optional[str]:
|
|
1152
|
-
return self.builder.get_asset(name)
|
|
1153
|
-
|
|
1154
1182
|
def dep(self, name: str, version: Optional[str] = None) -> str:
|
|
1155
1183
|
package = Package(name, version)
|
|
1156
1184
|
return self.add_package(package)
|
|
1157
|
-
|
|
1158
|
-
def service(
|
|
1185
|
+
|
|
1186
|
+
def service(
|
|
1187
|
+
self, name: str, provider: Literal["postgres", "mysql", "redis"]
|
|
1188
|
+
) -> str:
|
|
1159
1189
|
service = Service(name, provider)
|
|
1160
1190
|
return self.add_service(service)
|
|
1161
1191
|
|
|
@@ -1167,10 +1197,10 @@ class Ctx:
|
|
|
1167
1197
|
deps: List[str],
|
|
1168
1198
|
commands: Dict[str, str],
|
|
1169
1199
|
cwd: Optional[str] = None,
|
|
1170
|
-
assets: Optional[Dict[str, str]] = None,
|
|
1171
1200
|
prepare: Optional[List[str]] = None,
|
|
1172
1201
|
workers: Optional[List[str]] = None,
|
|
1173
1202
|
mounts: Optional[List[Mount]] = None,
|
|
1203
|
+
volumes: Optional[List[Volume]] = None,
|
|
1174
1204
|
env: Optional[Dict[str, str]] = None,
|
|
1175
1205
|
services: Optional[List[str]] = None,
|
|
1176
1206
|
) -> str:
|
|
@@ -1188,7 +1218,6 @@ class Ctx:
|
|
|
1188
1218
|
provider=provider,
|
|
1189
1219
|
build=build_refs,
|
|
1190
1220
|
cwd=cwd,
|
|
1191
|
-
assets=assets,
|
|
1192
1221
|
deps=dep_refs,
|
|
1193
1222
|
commands=commands,
|
|
1194
1223
|
prepare=prepare_steps,
|
|
@@ -1196,6 +1225,9 @@ class Ctx:
|
|
|
1196
1225
|
mounts=self.get_refs([mount["ref"] for mount in mounts])
|
|
1197
1226
|
if mounts
|
|
1198
1227
|
else None,
|
|
1228
|
+
volumes=self.get_refs([volume["ref"] for volume in volumes])
|
|
1229
|
+
if volumes
|
|
1230
|
+
else None,
|
|
1199
1231
|
env=env,
|
|
1200
1232
|
services=self.get_refs(services) if services else None,
|
|
1201
1233
|
)
|
|
@@ -1219,14 +1251,15 @@ class Ctx:
|
|
|
1219
1251
|
return self.add_step(step)
|
|
1220
1252
|
|
|
1221
1253
|
def copy(
|
|
1222
|
-
self,
|
|
1254
|
+
self,
|
|
1255
|
+
source: str,
|
|
1256
|
+
target: str,
|
|
1257
|
+
ignore: Optional[List[str]] = None,
|
|
1258
|
+
base: Optional[Literal["source", "assets"]] = None,
|
|
1223
1259
|
) -> Optional[str]:
|
|
1224
|
-
step = CopyStep(source, target, ignore)
|
|
1260
|
+
step = CopyStep(source, target, ignore, base or "source")
|
|
1225
1261
|
return self.add_step(step)
|
|
1226
1262
|
|
|
1227
|
-
def buildpath(self, name: str) -> str:
|
|
1228
|
-
return str((self.builder.get_build_path() / name).absolute())
|
|
1229
|
-
|
|
1230
1263
|
def env(self, **env_vars: str) -> Optional[str]:
|
|
1231
1264
|
step = EnvStep(env_vars)
|
|
1232
1265
|
return self.add_step(step)
|
|
@@ -1246,8 +1279,18 @@ class Ctx:
|
|
|
1246
1279
|
"serve": str(serve_path.absolute()),
|
|
1247
1280
|
}
|
|
1248
1281
|
|
|
1249
|
-
def
|
|
1250
|
-
|
|
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
|
+
}
|
|
1251
1294
|
|
|
1252
1295
|
|
|
1253
1296
|
def print_help() -> None:
|
|
@@ -1260,6 +1303,13 @@ def print_help() -> None:
|
|
|
1260
1303
|
console.print(panel)
|
|
1261
1304
|
|
|
1262
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
|
+
|
|
1263
1313
|
@app.command(name="auto")
|
|
1264
1314
|
def auto(
|
|
1265
1315
|
path: Path = typer.Argument(
|
|
@@ -1319,12 +1369,35 @@ def auto(
|
|
|
1319
1369
|
None,
|
|
1320
1370
|
help="Name of the Wasmer app.",
|
|
1321
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
|
+
),
|
|
1322
1388
|
):
|
|
1323
1389
|
if not path.exists():
|
|
1324
1390
|
raise Exception(f"The path {path} does not exist")
|
|
1325
1391
|
|
|
1326
1392
|
if not (path / "Shipit").exists() or regenerate or regenerate_path is not None:
|
|
1327
|
-
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
|
+
)
|
|
1328
1401
|
|
|
1329
1402
|
build(
|
|
1330
1403
|
path,
|
|
@@ -1364,13 +1437,53 @@ def generate(
|
|
|
1364
1437
|
None,
|
|
1365
1438
|
help="Output path (defaults to the Shipit file in the provided path).",
|
|
1366
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
|
+
),
|
|
1367
1456
|
):
|
|
1368
1457
|
if not path.exists():
|
|
1369
1458
|
raise Exception(f"The path {path} does not exist")
|
|
1370
1459
|
|
|
1371
1460
|
if out is None:
|
|
1372
1461
|
out = path / "Shipit"
|
|
1373
|
-
|
|
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)
|
|
1374
1487
|
out.write_text(content)
|
|
1375
1488
|
console.print(f"[bold]Generated Shipit[/bold] at {out.absolute()}")
|
|
1376
1489
|
|
|
@@ -1529,15 +1642,12 @@ def build(
|
|
|
1529
1642
|
mod.add_callable("serve", ctx.serve)
|
|
1530
1643
|
mod.add_callable("run", ctx.run)
|
|
1531
1644
|
mod.add_callable("mount", ctx.mount)
|
|
1645
|
+
mod.add_callable("volume", ctx.volume)
|
|
1532
1646
|
mod.add_callable("workdir", ctx.workdir)
|
|
1533
1647
|
mod.add_callable("copy", ctx.copy)
|
|
1534
1648
|
mod.add_callable("path", ctx.path)
|
|
1535
|
-
mod.add_callable("buildpath", ctx.buildpath)
|
|
1536
|
-
mod.add_callable("get_asset", ctx.get_asset)
|
|
1537
1649
|
mod.add_callable("env", ctx.env)
|
|
1538
1650
|
mod.add_callable("use", ctx.use)
|
|
1539
|
-
# REMOVE ME
|
|
1540
|
-
mod.add_callable("serve_mount", ctx.serve_mount)
|
|
1541
1651
|
|
|
1542
1652
|
dialect = sl.Dialect.extended()
|
|
1543
1653
|
dialect.enable_f_strings = True
|
|
@@ -1545,9 +1655,7 @@ def build(
|
|
|
1545
1655
|
ast = sl.parse("shipit", source, dialect=dialect)
|
|
1546
1656
|
|
|
1547
1657
|
sl.eval(mod, ast, glb)
|
|
1548
|
-
# assert len(ctx.builds) == 1, "Only one build is allowed for now"
|
|
1549
1658
|
assert len(ctx.serves) <= 1, "Only one serve is allowed for now"
|
|
1550
|
-
# build = ctx.builds[0]
|
|
1551
1659
|
env = {
|
|
1552
1660
|
"PATH": "",
|
|
1553
1661
|
"COLORTERM": os.environ.get("COLORTERM", ""),
|
|
@@ -1561,8 +1669,6 @@ def build(
|
|
|
1561
1669
|
builder.build(env, serve.mounts, serve.build)
|
|
1562
1670
|
if serve.prepare:
|
|
1563
1671
|
builder.build_prepare(serve)
|
|
1564
|
-
if serve.assets:
|
|
1565
|
-
builder.build_assets(serve.assets)
|
|
1566
1672
|
builder.build_serve(serve)
|
|
1567
1673
|
builder.finalize_build(serve)
|
|
1568
1674
|
if serve.prepare and not skip_prepare:
|
|
@@ -1580,7 +1686,7 @@ def main() -> None:
|
|
|
1580
1686
|
app()
|
|
1581
1687
|
except Exception as e:
|
|
1582
1688
|
console.print(f"[bold red]{type(e).__name__}[/bold red]: {e}")
|
|
1583
|
-
|
|
1689
|
+
raise e
|
|
1584
1690
|
|
|
1585
1691
|
|
|
1586
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
|