shipit-cli 0.6.1__py3-none-any.whl → 0.7.1__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 +243 -116
- 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.1.dist-info}/METADATA +3 -1
- shipit_cli-0.7.1.dist-info/RECORD +23 -0
- shipit_cli-0.6.1.dist-info/RECORD +0 -19
- {shipit_cli-0.6.1.dist-info → shipit_cli-0.7.1.dist-info}/WHEEL +0 -0
- {shipit_cli-0.6.1.dist-info → shipit_cli-0.7.1.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,9 @@ 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
|
|
39
|
+
from dotenv import dotenv_values
|
|
35
40
|
|
|
36
41
|
|
|
37
42
|
console = Console()
|
|
@@ -49,10 +54,18 @@ class Mount:
|
|
|
49
54
|
serve_path: Path
|
|
50
55
|
|
|
51
56
|
|
|
57
|
+
@dataclass
|
|
58
|
+
class Volume:
|
|
59
|
+
name: str
|
|
60
|
+
serve_path: Path
|
|
61
|
+
|
|
62
|
+
|
|
52
63
|
@dataclass
|
|
53
64
|
class Service:
|
|
54
65
|
name: str
|
|
55
|
-
provider: Literal[
|
|
66
|
+
provider: Literal[
|
|
67
|
+
"postgres", "mysql", "redis"
|
|
68
|
+
] # Right now we only support postgres and mysql
|
|
56
69
|
|
|
57
70
|
|
|
58
71
|
@dataclass
|
|
@@ -63,10 +76,10 @@ class Serve:
|
|
|
63
76
|
deps: List["Package"]
|
|
64
77
|
commands: Dict[str, str]
|
|
65
78
|
cwd: Optional[str] = None
|
|
66
|
-
assets: Optional[Dict[str, str]] = None
|
|
67
79
|
prepare: Optional[List["PrepareStep"]] = None
|
|
68
80
|
workers: Optional[List[str]] = None
|
|
69
81
|
mounts: Optional[List[Mount]] = None
|
|
82
|
+
volumes: Optional[List[Volume]] = None
|
|
70
83
|
env: Optional[Dict[str, str]] = None
|
|
71
84
|
services: Optional[List[Service]] = None
|
|
72
85
|
|
|
@@ -98,6 +111,11 @@ class CopyStep:
|
|
|
98
111
|
source: str
|
|
99
112
|
target: str
|
|
100
113
|
ignore: Optional[List[str]] = None
|
|
114
|
+
# We can copy from the app source or from the shipit assets folder
|
|
115
|
+
base: Literal["source", "assets"] = "source"
|
|
116
|
+
|
|
117
|
+
def is_download(self) -> bool:
|
|
118
|
+
return self.source.startswith("http://") or self.source.startswith("https://")
|
|
101
119
|
|
|
102
120
|
|
|
103
121
|
@dataclass
|
|
@@ -147,7 +165,6 @@ class Builder(Protocol):
|
|
|
147
165
|
def build(
|
|
148
166
|
self, env: Dict[str, str], mounts: List[Mount], steps: List[Step]
|
|
149
167
|
) -> None: ...
|
|
150
|
-
def build_assets(self, assets: Dict[str, str]) -> None: ...
|
|
151
168
|
def build_prepare(self, serve: Serve) -> None: ...
|
|
152
169
|
def build_serve(self, serve: Serve) -> None: ...
|
|
153
170
|
def finalize_build(self, serve: Serve) -> None: ...
|
|
@@ -157,8 +174,6 @@ class Builder(Protocol):
|
|
|
157
174
|
def run_command(
|
|
158
175
|
self, command: str, extra_args: Optional[List[str]] | None = None
|
|
159
176
|
) -> Any: ...
|
|
160
|
-
def serve_mount(self, name: str) -> str: ...
|
|
161
|
-
def get_asset(self, name: str) -> str: ...
|
|
162
177
|
def get_build_mount_path(self, name: str) -> Path: ...
|
|
163
178
|
def get_serve_mount_path(self, name: str) -> Path: ...
|
|
164
179
|
|
|
@@ -258,13 +273,23 @@ class DockerBuilder:
|
|
|
258
273
|
|
|
259
274
|
def run_command(self, command: str, extra_args: Optional[List[str]] = None) -> Any:
|
|
260
275
|
image_name = self.docker_name_path.read_text()
|
|
261
|
-
|
|
262
|
-
"docker"
|
|
263
|
-
)(
|
|
276
|
+
docker_args: List[str] = [
|
|
264
277
|
"run",
|
|
265
278
|
"-p",
|
|
266
279
|
"80:80",
|
|
267
280
|
"--rm",
|
|
281
|
+
]
|
|
282
|
+
# Attach volumes if present
|
|
283
|
+
# if serve.volumes:
|
|
284
|
+
# for vol in serve.volumes:
|
|
285
|
+
# docker_args += [
|
|
286
|
+
# "--mount",
|
|
287
|
+
# f"type=volume,source={vol.name},target={str(vol.serve_path)}",
|
|
288
|
+
# ]
|
|
289
|
+
return sh.Command(
|
|
290
|
+
"docker"
|
|
291
|
+
)(
|
|
292
|
+
*docker_args,
|
|
268
293
|
image_name,
|
|
269
294
|
command,
|
|
270
295
|
*(extra_args or []),
|
|
@@ -372,7 +397,28 @@ RUN curl https://mise.run | sh
|
|
|
372
397
|
pre = ""
|
|
373
398
|
self.docker_file_contents += f"RUN {pre}{step.command}\n"
|
|
374
399
|
elif isinstance(step, CopyStep):
|
|
375
|
-
|
|
400
|
+
if step.is_download():
|
|
401
|
+
self.docker_file_contents += (
|
|
402
|
+
"ADD " + step.source + " " + step.target + "\n"
|
|
403
|
+
)
|
|
404
|
+
elif step.base == "assets":
|
|
405
|
+
# Detect if the asset exists and is a file
|
|
406
|
+
if (ASSETS_PATH / step.source).is_file():
|
|
407
|
+
# Read the file content and write it to the target file
|
|
408
|
+
content_base64 = base64.b64encode(
|
|
409
|
+
(ASSETS_PATH / step.source).read_bytes()
|
|
410
|
+
)
|
|
411
|
+
self.docker_file_contents += (
|
|
412
|
+
f"RUN echo '{content_base64}' | base64 -d > {step.target}\n"
|
|
413
|
+
)
|
|
414
|
+
elif (ASSETS_PATH / step.source).is_dir():
|
|
415
|
+
raise Exception(
|
|
416
|
+
f"Asset {step.source} is a directory, shipit doesn't currently support coppying assets directories inside Docker"
|
|
417
|
+
)
|
|
418
|
+
else:
|
|
419
|
+
raise Exception(f"Asset {step.source} does not exist")
|
|
420
|
+
else:
|
|
421
|
+
self.docker_file_contents += f"COPY {step.source} {step.target}\n"
|
|
376
422
|
elif isinstance(step, EnvStep):
|
|
377
423
|
env_vars = " ".join(
|
|
378
424
|
[f"{key}={value}" for key, value in step.variables.items()]
|
|
@@ -397,9 +443,6 @@ FROM scratch
|
|
|
397
443
|
Shipit
|
|
398
444
|
""")
|
|
399
445
|
|
|
400
|
-
def build_assets(self, assets: Dict[str, str]) -> None:
|
|
401
|
-
raise NotImplementedError
|
|
402
|
-
|
|
403
446
|
def get_path(self) -> Path:
|
|
404
447
|
return Path("/")
|
|
405
448
|
|
|
@@ -409,11 +452,6 @@ Shipit
|
|
|
409
452
|
def get_serve_path(self) -> Path:
|
|
410
453
|
return self.get_path() / "serve"
|
|
411
454
|
|
|
412
|
-
def get_assets_path(self) -> Path:
|
|
413
|
-
path = self.get_path() / "assets"
|
|
414
|
-
self.mkdir(path)
|
|
415
|
-
return path
|
|
416
|
-
|
|
417
455
|
def prepare(self, env: Dict[str, str], prepare: List[PrepareStep]) -> None:
|
|
418
456
|
raise NotImplementedError
|
|
419
457
|
|
|
@@ -433,14 +471,6 @@ Shipit
|
|
|
433
471
|
mode=0o755,
|
|
434
472
|
)
|
|
435
473
|
|
|
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
474
|
def run_serve_command(self, command: str) -> None:
|
|
445
475
|
path = self.shipit_docker_path / "serve" / "bin" / command
|
|
446
476
|
self.run_command(str(path))
|
|
@@ -518,18 +548,31 @@ class LocalBuilder:
|
|
|
518
548
|
ignore_matches = step.ignore if step.ignore else []
|
|
519
549
|
ignore_matches.append(".shipit")
|
|
520
550
|
ignore_matches.append("Shipit")
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
(build_path / step.target),
|
|
525
|
-
dirs_exist_ok=True,
|
|
526
|
-
ignore=ignore_patterns(*ignore_matches),
|
|
527
|
-
)
|
|
551
|
+
|
|
552
|
+
if step.is_download():
|
|
553
|
+
download_file(step.source, (build_path / step.target))
|
|
528
554
|
else:
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
555
|
+
if step.base == "source":
|
|
556
|
+
base = self.src_dir
|
|
557
|
+
elif step.base == "assets":
|
|
558
|
+
base = ASSETS_PATH
|
|
559
|
+
else:
|
|
560
|
+
raise Exception(f"Unknown base: {step.base}")
|
|
561
|
+
|
|
562
|
+
if (base / step.source).is_dir():
|
|
563
|
+
copytree(
|
|
564
|
+
(base / step.source),
|
|
565
|
+
(build_path / step.target),
|
|
566
|
+
dirs_exist_ok=True,
|
|
567
|
+
ignore=ignore_patterns(*ignore_matches),
|
|
568
|
+
)
|
|
569
|
+
elif (base / step.source).is_file():
|
|
570
|
+
copy(
|
|
571
|
+
(base / step.source),
|
|
572
|
+
(build_path / step.target),
|
|
573
|
+
)
|
|
574
|
+
else:
|
|
575
|
+
raise Exception(f"Source {step.source} is not a file or directory")
|
|
533
576
|
elif isinstance(step, EnvStep):
|
|
534
577
|
print(f"Setting environment variables: {step}")
|
|
535
578
|
env.update(step.variables)
|
|
@@ -591,17 +634,6 @@ class LocalBuilder:
|
|
|
591
634
|
def get_serve_path(self) -> Path:
|
|
592
635
|
return self.get_path() / "serve"
|
|
593
636
|
|
|
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
637
|
def build_prepare(self, serve: Serve) -> None:
|
|
606
638
|
self.prepare_bash_script.parent.mkdir(parents=True, exist_ok=True)
|
|
607
639
|
commands: List[str] = []
|
|
@@ -642,6 +674,7 @@ class LocalBuilder:
|
|
|
642
674
|
)
|
|
643
675
|
|
|
644
676
|
def build_serve(self, serve: Serve) -> None:
|
|
677
|
+
# Remember serve configuration for run-time
|
|
645
678
|
console.print("\n[bold]Building serve[/bold]")
|
|
646
679
|
serve_command_path = self.get_serve_path() / "bin"
|
|
647
680
|
serve_command_path.mkdir(parents=True, exist_ok=False)
|
|
@@ -651,10 +684,12 @@ class LocalBuilder:
|
|
|
651
684
|
for command in serve.commands:
|
|
652
685
|
console.print(f"* {command}")
|
|
653
686
|
command_path = serve_command_path / command
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
687
|
+
env_vars = ""
|
|
688
|
+
if serve.env:
|
|
689
|
+
env_vars = " ".join([f"{k}={v}" for k, v in serve.env.items()])
|
|
690
|
+
|
|
691
|
+
content = f"#!/bin/bash\ncd {serve.cwd}\nPATH={path_text}:$PATH {env_vars} {serve.commands[command]}"
|
|
692
|
+
command_path.write_text(content)
|
|
658
693
|
manifest_panel = Panel(
|
|
659
694
|
Syntax(
|
|
660
695
|
content.strip(),
|
|
@@ -676,15 +711,6 @@ class LocalBuilder:
|
|
|
676
711
|
command_path = base_path / command
|
|
677
712
|
sh.Command(str(command_path))(_out=write_stdout, _err=write_stderr)
|
|
678
713
|
|
|
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
714
|
|
|
689
715
|
class WasmerBuilder:
|
|
690
716
|
def get_build_mount_path(self, name: str) -> Path:
|
|
@@ -725,8 +751,8 @@ class WasmerBuilder:
|
|
|
725
751
|
},
|
|
726
752
|
"php": {
|
|
727
753
|
"dependencies": {
|
|
728
|
-
"latest": "php/php-32@=8.3.
|
|
729
|
-
"8.3": "php/php-32@=8.3.
|
|
754
|
+
"latest": "php/php-32@=8.3.2102",
|
|
755
|
+
"8.3": "php/php-32@=8.3.2102",
|
|
730
756
|
},
|
|
731
757
|
"scripts": {"php"},
|
|
732
758
|
"aliases": {},
|
|
@@ -782,9 +808,6 @@ class WasmerBuilder:
|
|
|
782
808
|
) -> None:
|
|
783
809
|
return self.inner_builder.build(env, mounts, build)
|
|
784
810
|
|
|
785
|
-
def build_assets(self, assets: Dict[str, str]) -> None:
|
|
786
|
-
return self.inner_builder.build_assets(assets)
|
|
787
|
-
|
|
788
811
|
def get_build_path(self) -> Path:
|
|
789
812
|
return Path("/app")
|
|
790
813
|
|
|
@@ -855,10 +878,10 @@ class WasmerBuilder:
|
|
|
855
878
|
)
|
|
856
879
|
|
|
857
880
|
def build_serve(self, serve: Serve) -> None:
|
|
858
|
-
from tomlkit import comment, document, nl, table, aot, string
|
|
881
|
+
from tomlkit import comment, document, nl, table, aot, string, array
|
|
859
882
|
|
|
860
883
|
doc = document()
|
|
861
|
-
doc.add(comment(f"
|
|
884
|
+
doc.add(comment(f"Wasmer manifest generated with Shipit v{shipit_version}"))
|
|
862
885
|
package = table()
|
|
863
886
|
doc.add("package", package)
|
|
864
887
|
package.add("entrypoint", "start")
|
|
@@ -908,9 +931,6 @@ class WasmerBuilder:
|
|
|
908
931
|
fs = table()
|
|
909
932
|
doc.add("fs", fs)
|
|
910
933
|
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
934
|
if serve.mounts:
|
|
915
935
|
for mount in serve.mounts:
|
|
916
936
|
fs.add(
|
|
@@ -934,14 +954,14 @@ class WasmerBuilder:
|
|
|
934
954
|
wasi_args = table()
|
|
935
955
|
if serve.cwd:
|
|
936
956
|
wasi_args.add("cwd", serve.cwd)
|
|
937
|
-
wasi_args.add("main-args", parts[1:])
|
|
957
|
+
wasi_args.add("main-args", array(parts[1:]).multiline(True))
|
|
938
958
|
env = program_binary.get("env") or {}
|
|
939
959
|
if serve.env:
|
|
940
960
|
env.update(serve.env)
|
|
941
961
|
if env:
|
|
962
|
+
arr = array([f"{k}={v}" for k, v in env.items()]).multiline(True)
|
|
942
963
|
wasi_args.add(
|
|
943
|
-
"env",
|
|
944
|
-
[f"{k}={v}" for k, v in env.items()],
|
|
964
|
+
"env", arr
|
|
945
965
|
)
|
|
946
966
|
title = string("annotations.wasi", literal=False)
|
|
947
967
|
command.add(title, wasi_args)
|
|
@@ -986,25 +1006,40 @@ class WasmerBuilder:
|
|
|
986
1006
|
# has_postgres = any(service.provider == "postgres" for service in serve.services)
|
|
987
1007
|
# has_redis = any(service.provider == "redis" for service in serve.services)
|
|
988
1008
|
if has_mysql:
|
|
989
|
-
capabilities["database"] = {
|
|
990
|
-
"engine": "mysql"
|
|
991
|
-
}
|
|
1009
|
+
capabilities["database"] = {"engine": "mysql"}
|
|
992
1010
|
yaml_config["capabilities"] = capabilities
|
|
993
1011
|
|
|
1012
|
+
# Attach declared volumes to the app manifest (serve-time mounts)
|
|
1013
|
+
if serve.volumes:
|
|
1014
|
+
volumes_yaml = yaml_config.get("volumes", [])
|
|
1015
|
+
for vol in serve.volumes:
|
|
1016
|
+
volumes_yaml.append(
|
|
1017
|
+
{
|
|
1018
|
+
"name": vol.name,
|
|
1019
|
+
"mount": str(vol.serve_path),
|
|
1020
|
+
}
|
|
1021
|
+
)
|
|
1022
|
+
yaml_config["volumes"] = volumes_yaml
|
|
1023
|
+
|
|
1024
|
+
# If it has a php dependency, set the scaling mode to single_concurrency
|
|
1025
|
+
has_php = any(dep.name == "php" for dep in serve.deps)
|
|
1026
|
+
if has_php:
|
|
1027
|
+
scaling = yaml_config.get("scaling", {})
|
|
1028
|
+
scaling["mode"] = "single_concurrency"
|
|
1029
|
+
yaml_config["scaling"] = scaling
|
|
1030
|
+
|
|
994
1031
|
if "after_deploy" in serve.commands:
|
|
995
1032
|
jobs = yaml_config.get("jobs", [])
|
|
996
|
-
jobs.append(
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
"execute": {
|
|
1001
|
-
"command": "after_deploy"
|
|
1002
|
-
}
|
|
1033
|
+
jobs.append(
|
|
1034
|
+
{
|
|
1035
|
+
"name": "after_deploy",
|
|
1036
|
+
"trigger": "post-deployment",
|
|
1037
|
+
"action": {"execute": {"command": "after_deploy"}},
|
|
1003
1038
|
}
|
|
1004
|
-
|
|
1039
|
+
)
|
|
1005
1040
|
yaml_config["jobs"] = jobs
|
|
1006
1041
|
|
|
1007
|
-
app_yaml = yaml.dump(yaml_config)
|
|
1042
|
+
app_yaml = yaml.dump(yaml_config,)
|
|
1008
1043
|
|
|
1009
1044
|
console.print(f"\n[bold]Created app.yaml manifest ✅[/bold]")
|
|
1010
1045
|
app_yaml_panel = Panel(
|
|
@@ -1043,12 +1078,6 @@ class WasmerBuilder:
|
|
|
1043
1078
|
],
|
|
1044
1079
|
)
|
|
1045
1080
|
|
|
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
1081
|
def run_command(
|
|
1053
1082
|
self, command: str, extra_args: Optional[List[str]] | None = None
|
|
1054
1083
|
) -> Any:
|
|
@@ -1101,6 +1130,7 @@ class Ctx:
|
|
|
1101
1130
|
self.steps: List[Step] = []
|
|
1102
1131
|
self.serves: Dict[str, Serve] = {}
|
|
1103
1132
|
self.mounts: List[Mount] = []
|
|
1133
|
+
self.volumes: List[Volume] = []
|
|
1104
1134
|
self.services: Dict[str, Service] = {}
|
|
1105
1135
|
|
|
1106
1136
|
def add_package(self, package: Package) -> str:
|
|
@@ -1123,6 +1153,8 @@ class Ctx:
|
|
|
1123
1153
|
return self.steps[int(index[len("ref:step:") :])]
|
|
1124
1154
|
elif index.startswith("ref:mount:"):
|
|
1125
1155
|
return self.mounts[int(index[len("ref:mount:") :])]
|
|
1156
|
+
elif index.startswith("ref:volume:"):
|
|
1157
|
+
return self.volumes[int(index[len("ref:volume:") :])]
|
|
1126
1158
|
elif index.startswith("ref:service:"):
|
|
1127
1159
|
return self.services[index[len("ref:service:") :]]
|
|
1128
1160
|
else:
|
|
@@ -1148,14 +1180,13 @@ class Ctx:
|
|
|
1148
1180
|
def getenv(self, name: str) -> Optional[str]:
|
|
1149
1181
|
return self.builder.getenv(name)
|
|
1150
1182
|
|
|
1151
|
-
def get_asset(self, name: str) -> Optional[str]:
|
|
1152
|
-
return self.builder.get_asset(name)
|
|
1153
|
-
|
|
1154
1183
|
def dep(self, name: str, version: Optional[str] = None) -> str:
|
|
1155
1184
|
package = Package(name, version)
|
|
1156
1185
|
return self.add_package(package)
|
|
1157
|
-
|
|
1158
|
-
def service(
|
|
1186
|
+
|
|
1187
|
+
def service(
|
|
1188
|
+
self, name: str, provider: Literal["postgres", "mysql", "redis"]
|
|
1189
|
+
) -> str:
|
|
1159
1190
|
service = Service(name, provider)
|
|
1160
1191
|
return self.add_service(service)
|
|
1161
1192
|
|
|
@@ -1167,10 +1198,10 @@ class Ctx:
|
|
|
1167
1198
|
deps: List[str],
|
|
1168
1199
|
commands: Dict[str, str],
|
|
1169
1200
|
cwd: Optional[str] = None,
|
|
1170
|
-
assets: Optional[Dict[str, str]] = None,
|
|
1171
1201
|
prepare: Optional[List[str]] = None,
|
|
1172
1202
|
workers: Optional[List[str]] = None,
|
|
1173
1203
|
mounts: Optional[List[Mount]] = None,
|
|
1204
|
+
volumes: Optional[List[Volume]] = None,
|
|
1174
1205
|
env: Optional[Dict[str, str]] = None,
|
|
1175
1206
|
services: Optional[List[str]] = None,
|
|
1176
1207
|
) -> str:
|
|
@@ -1188,7 +1219,6 @@ class Ctx:
|
|
|
1188
1219
|
provider=provider,
|
|
1189
1220
|
build=build_refs,
|
|
1190
1221
|
cwd=cwd,
|
|
1191
|
-
assets=assets,
|
|
1192
1222
|
deps=dep_refs,
|
|
1193
1223
|
commands=commands,
|
|
1194
1224
|
prepare=prepare_steps,
|
|
@@ -1196,6 +1226,9 @@ class Ctx:
|
|
|
1196
1226
|
mounts=self.get_refs([mount["ref"] for mount in mounts])
|
|
1197
1227
|
if mounts
|
|
1198
1228
|
else None,
|
|
1229
|
+
volumes=self.get_refs([volume["ref"] for volume in volumes])
|
|
1230
|
+
if volumes
|
|
1231
|
+
else None,
|
|
1199
1232
|
env=env,
|
|
1200
1233
|
services=self.get_refs(services) if services else None,
|
|
1201
1234
|
)
|
|
@@ -1219,14 +1252,15 @@ class Ctx:
|
|
|
1219
1252
|
return self.add_step(step)
|
|
1220
1253
|
|
|
1221
1254
|
def copy(
|
|
1222
|
-
self,
|
|
1255
|
+
self,
|
|
1256
|
+
source: str,
|
|
1257
|
+
target: str,
|
|
1258
|
+
ignore: Optional[List[str]] = None,
|
|
1259
|
+
base: Optional[Literal["source", "assets"]] = None,
|
|
1223
1260
|
) -> Optional[str]:
|
|
1224
|
-
step = CopyStep(source, target, ignore)
|
|
1261
|
+
step = CopyStep(source, target, ignore, base or "source")
|
|
1225
1262
|
return self.add_step(step)
|
|
1226
1263
|
|
|
1227
|
-
def buildpath(self, name: str) -> str:
|
|
1228
|
-
return str((self.builder.get_build_path() / name).absolute())
|
|
1229
|
-
|
|
1230
1264
|
def env(self, **env_vars: str) -> Optional[str]:
|
|
1231
1265
|
step = EnvStep(env_vars)
|
|
1232
1266
|
return self.add_step(step)
|
|
@@ -1246,8 +1280,18 @@ class Ctx:
|
|
|
1246
1280
|
"serve": str(serve_path.absolute()),
|
|
1247
1281
|
}
|
|
1248
1282
|
|
|
1249
|
-
def
|
|
1250
|
-
|
|
1283
|
+
def add_volume(self, volume: Volume) -> Optional[str]:
|
|
1284
|
+
self.volumes.append(volume)
|
|
1285
|
+
return f"ref:volume:{len(self.volumes) - 1}"
|
|
1286
|
+
|
|
1287
|
+
def volume(self, name: str, serve: str) -> Optional[str]:
|
|
1288
|
+
volume = Volume(name=name, serve_path=Path(serve))
|
|
1289
|
+
ref = self.add_volume(volume)
|
|
1290
|
+
return {
|
|
1291
|
+
"ref": ref,
|
|
1292
|
+
"name": name,
|
|
1293
|
+
"serve": str(volume.serve_path),
|
|
1294
|
+
}
|
|
1251
1295
|
|
|
1252
1296
|
|
|
1253
1297
|
def print_help() -> None:
|
|
@@ -1260,6 +1304,13 @@ def print_help() -> None:
|
|
|
1260
1304
|
console.print(panel)
|
|
1261
1305
|
|
|
1262
1306
|
|
|
1307
|
+
def download_file(url: str, path: Path) -> None:
|
|
1308
|
+
response = requests.get(url)
|
|
1309
|
+
response.raise_for_status()
|
|
1310
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
1311
|
+
path.write_bytes(response.content)
|
|
1312
|
+
|
|
1313
|
+
|
|
1263
1314
|
@app.command(name="auto")
|
|
1264
1315
|
def auto(
|
|
1265
1316
|
path: Path = typer.Argument(
|
|
@@ -1319,12 +1370,39 @@ def auto(
|
|
|
1319
1370
|
None,
|
|
1320
1371
|
help="Name of the Wasmer app.",
|
|
1321
1372
|
),
|
|
1373
|
+
use_procfile: bool = typer.Option(
|
|
1374
|
+
True,
|
|
1375
|
+
help="Use the Procfile to generate the default custom commands (install, build, start, after_deploy).",
|
|
1376
|
+
),
|
|
1377
|
+
install_command: Optional[str] = typer.Option(
|
|
1378
|
+
None,
|
|
1379
|
+
help="The install command to use (overwrites the default)",
|
|
1380
|
+
),
|
|
1381
|
+
build_command: Optional[str] = typer.Option(
|
|
1382
|
+
None,
|
|
1383
|
+
help="The build command to use (overwrites the default)",
|
|
1384
|
+
),
|
|
1385
|
+
start_command: Optional[str] = typer.Option(
|
|
1386
|
+
None,
|
|
1387
|
+
help="The start command to use (overwrites the default)",
|
|
1388
|
+
),
|
|
1389
|
+
env_name: Optional[str] = typer.Option(
|
|
1390
|
+
None,
|
|
1391
|
+
help="The environment to use (defaults to `.env`, it will use .env.<env_name> if provided)",
|
|
1392
|
+
),
|
|
1322
1393
|
):
|
|
1323
1394
|
if not path.exists():
|
|
1324
1395
|
raise Exception(f"The path {path} does not exist")
|
|
1325
1396
|
|
|
1326
1397
|
if not (path / "Shipit").exists() or regenerate or regenerate_path is not None:
|
|
1327
|
-
generate(
|
|
1398
|
+
generate(
|
|
1399
|
+
path,
|
|
1400
|
+
out=regenerate_path,
|
|
1401
|
+
use_procfile=use_procfile,
|
|
1402
|
+
install_command=install_command,
|
|
1403
|
+
build_command=build_command,
|
|
1404
|
+
start_command=start_command,
|
|
1405
|
+
)
|
|
1328
1406
|
|
|
1329
1407
|
build(
|
|
1330
1408
|
path,
|
|
@@ -1335,6 +1413,7 @@ def auto(
|
|
|
1335
1413
|
wasmer_token=wasmer_token,
|
|
1336
1414
|
wasmer_bin=wasmer_bin,
|
|
1337
1415
|
skip_prepare=skip_prepare,
|
|
1416
|
+
env_name=env_name,
|
|
1338
1417
|
)
|
|
1339
1418
|
if start or wasmer_deploy:
|
|
1340
1419
|
serve(
|
|
@@ -1364,13 +1443,53 @@ def generate(
|
|
|
1364
1443
|
None,
|
|
1365
1444
|
help="Output path (defaults to the Shipit file in the provided path).",
|
|
1366
1445
|
),
|
|
1446
|
+
use_procfile: bool = typer.Option(
|
|
1447
|
+
True,
|
|
1448
|
+
help="Use the Procfile to generate the default custom commands (install, build, start, after_deploy).",
|
|
1449
|
+
),
|
|
1450
|
+
install_command: Optional[str] = typer.Option(
|
|
1451
|
+
None,
|
|
1452
|
+
help="The install command to use (overwrites the default)",
|
|
1453
|
+
),
|
|
1454
|
+
build_command: Optional[str] = typer.Option(
|
|
1455
|
+
None,
|
|
1456
|
+
help="The build command to use (overwrites the default)",
|
|
1457
|
+
),
|
|
1458
|
+
start_command: Optional[str] = typer.Option(
|
|
1459
|
+
None,
|
|
1460
|
+
help="The start command to use (overwrites the default)",
|
|
1461
|
+
),
|
|
1367
1462
|
):
|
|
1368
1463
|
if not path.exists():
|
|
1369
1464
|
raise Exception(f"The path {path} does not exist")
|
|
1370
1465
|
|
|
1371
1466
|
if out is None:
|
|
1372
1467
|
out = path / "Shipit"
|
|
1373
|
-
|
|
1468
|
+
custom_commands = CustomCommands()
|
|
1469
|
+
# if (path / "Dockerfile").exists():
|
|
1470
|
+
# # We get the start command from the Dockerfile
|
|
1471
|
+
# with open(path / "Dockerfile", "r") as f:
|
|
1472
|
+
# cmd = None
|
|
1473
|
+
# for line in f:
|
|
1474
|
+
# if line.startswith("CMD "):
|
|
1475
|
+
# cmd = line[4:].strip()
|
|
1476
|
+
# cmd = json.loads(cmd)
|
|
1477
|
+
# # We get the last command
|
|
1478
|
+
# if cmd:
|
|
1479
|
+
# if isinstance(cmd, list):
|
|
1480
|
+
# cmd = " ".join(cmd)
|
|
1481
|
+
# custom_commands.start = cmd
|
|
1482
|
+
if use_procfile:
|
|
1483
|
+
if (path / "Procfile").exists():
|
|
1484
|
+
procfile = Procfile.loads((path / "Procfile").read_text())
|
|
1485
|
+
custom_commands.start = procfile.get_start_command()
|
|
1486
|
+
if start_command:
|
|
1487
|
+
custom_commands.start = start_command
|
|
1488
|
+
if install_command:
|
|
1489
|
+
custom_commands.install = install_command
|
|
1490
|
+
if build_command:
|
|
1491
|
+
custom_commands.build = build_command
|
|
1492
|
+
content = generate_shipit(path, custom_commands)
|
|
1374
1493
|
out.write_text(content)
|
|
1375
1494
|
console.print(f"[bold]Generated Shipit[/bold] at {out.absolute()}")
|
|
1376
1495
|
|
|
@@ -1499,6 +1618,10 @@ def build(
|
|
|
1499
1618
|
None,
|
|
1500
1619
|
help="Use a specific Docker client (such as depot, podman, etc.)",
|
|
1501
1620
|
),
|
|
1621
|
+
env_name: Optional[str] = typer.Option(
|
|
1622
|
+
None,
|
|
1623
|
+
help="The environment to use (defaults to `.env`, it will use .env.<env_name> if provided)",
|
|
1624
|
+
),
|
|
1502
1625
|
) -> None:
|
|
1503
1626
|
if not path.exists():
|
|
1504
1627
|
raise Exception(f"The path {path} does not exist")
|
|
@@ -1529,15 +1652,12 @@ def build(
|
|
|
1529
1652
|
mod.add_callable("serve", ctx.serve)
|
|
1530
1653
|
mod.add_callable("run", ctx.run)
|
|
1531
1654
|
mod.add_callable("mount", ctx.mount)
|
|
1655
|
+
mod.add_callable("volume", ctx.volume)
|
|
1532
1656
|
mod.add_callable("workdir", ctx.workdir)
|
|
1533
1657
|
mod.add_callable("copy", ctx.copy)
|
|
1534
1658
|
mod.add_callable("path", ctx.path)
|
|
1535
|
-
mod.add_callable("buildpath", ctx.buildpath)
|
|
1536
|
-
mod.add_callable("get_asset", ctx.get_asset)
|
|
1537
1659
|
mod.add_callable("env", ctx.env)
|
|
1538
1660
|
mod.add_callable("use", ctx.use)
|
|
1539
|
-
# REMOVE ME
|
|
1540
|
-
mod.add_callable("serve_mount", ctx.serve_mount)
|
|
1541
1661
|
|
|
1542
1662
|
dialect = sl.Dialect.extended()
|
|
1543
1663
|
dialect.enable_f_strings = True
|
|
@@ -1545,9 +1665,7 @@ def build(
|
|
|
1545
1665
|
ast = sl.parse("shipit", source, dialect=dialect)
|
|
1546
1666
|
|
|
1547
1667
|
sl.eval(mod, ast, glb)
|
|
1548
|
-
# assert len(ctx.builds) == 1, "Only one build is allowed for now"
|
|
1549
1668
|
assert len(ctx.serves) <= 1, "Only one serve is allowed for now"
|
|
1550
|
-
# build = ctx.builds[0]
|
|
1551
1669
|
env = {
|
|
1552
1670
|
"PATH": "",
|
|
1553
1671
|
"COLORTERM": os.environ.get("COLORTERM", ""),
|
|
@@ -1556,13 +1674,19 @@ def build(
|
|
|
1556
1674
|
"CLICOLOR": os.environ.get("CLICOLOR", "0"),
|
|
1557
1675
|
}
|
|
1558
1676
|
serve = next(iter(ctx.serves.values()))
|
|
1677
|
+
serve.env = serve.env or {}
|
|
1678
|
+
if (path / ".env").exists():
|
|
1679
|
+
env_vars = dotenv_values(path / ".env")
|
|
1680
|
+
serve.env.update(env_vars)
|
|
1681
|
+
|
|
1682
|
+
if (path / f".env.{env_name}").exists():
|
|
1683
|
+
env_vars = dotenv_values(path / f".env.{env_name}")
|
|
1684
|
+
serve.env.update(env_vars)
|
|
1559
1685
|
|
|
1560
1686
|
# Build and serve
|
|
1561
1687
|
builder.build(env, serve.mounts, serve.build)
|
|
1562
1688
|
if serve.prepare:
|
|
1563
1689
|
builder.build_prepare(serve)
|
|
1564
|
-
if serve.assets:
|
|
1565
|
-
builder.build_assets(serve.assets)
|
|
1566
1690
|
builder.build_serve(serve)
|
|
1567
1691
|
builder.finalize_build(serve)
|
|
1568
1692
|
if serve.prepare and not skip_prepare:
|
|
@@ -1580,8 +1704,11 @@ def main() -> None:
|
|
|
1580
1704
|
app()
|
|
1581
1705
|
except Exception as e:
|
|
1582
1706
|
console.print(f"[bold red]{type(e).__name__}[/bold red]: {e}")
|
|
1583
|
-
|
|
1707
|
+
raise e
|
|
1584
1708
|
|
|
1585
1709
|
|
|
1586
1710
|
if __name__ == "__main__":
|
|
1587
1711
|
main()
|
|
1712
|
+
|
|
1713
|
+
def flatten(xss):
|
|
1714
|
+
return [x for xs in xss for x in xs]
|