fufu-cloud-cli 0.5.0__tar.gz → 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fufu_cloud_cli-0.5.0/src/fufu_cloud_cli.egg-info → fufu_cloud_cli-0.6.0}/PKG-INFO +14 -3
- {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/README.md +13 -2
- {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/pyproject.toml +1 -1
- {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/__init__.py +1 -1
- {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/capabilities.json +5 -0
- {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/cli.py +12 -2
- {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/docker_backend.py +55 -3
- {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/remote_compute.py +27 -0
- {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0/src/fufu_cloud_cli.egg-info}/PKG-INFO +14 -3
- {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/LICENSE +0 -0
- {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/MANIFEST.in +0 -0
- {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/setup.cfg +0 -0
- {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/__main__.py +0 -0
- {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/api_backend.py +0 -0
- {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/cloud_data.py +0 -0
- {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/control.py +0 -0
- {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/errors.py +0 -0
- {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/lambda_adapter.py +0 -0
- {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/manifest.py +0 -0
- {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/runtime/functions/Dockerfile +0 -0
- {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/runtime/ingress/Dockerfile +0 -0
- {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/runtime/ingress/proxy.py +0 -0
- {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/runtime_sdk.py +0 -0
- {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/serverless.py +0 -0
- {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli.egg-info/SOURCES.txt +0 -0
- {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli.egg-info/dependency_links.txt +0 -0
- {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli.egg-info/entry_points.txt +0 -0
- {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli.egg-info/requires.txt +0 -0
- {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fufu-cloud-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: FUFU Cloud CLI for tenant-scoped mock cloud development and deployment
|
|
5
5
|
License-Expression: LicenseRef-Proprietary
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -77,19 +77,30 @@ FUFU credential 與 session 檔仍屬秘密,必須由作業系統和 CI 控制
|
|
|
77
77
|
fufu capabilities
|
|
78
78
|
fufu capabilities --remote --provider gcp --require cloud-run-http
|
|
79
79
|
gcloud-fufu run deploy example --image approved-image:tag
|
|
80
|
+
gcloud-fufu run deploy example --source ./prepared-asgi.zip --entry-point main:app
|
|
80
81
|
gcloud-fufu functions deploy example-function --gen2 \
|
|
81
82
|
--runtime python312 --source ./function --entry-point handler --trigger-http
|
|
82
83
|
aws-fufu lambda list-functions
|
|
83
84
|
```
|
|
84
85
|
|
|
85
|
-
|
|
86
|
+
自訂映像需先由 FUFU 管理者準備及核准。API 模式的 Cloud Run `--source`
|
|
87
|
+
接受已封裝 Uvicorn 與相依套件的 Python 3.12 ASGI ZIP/乾淨目錄,自動離線建置、
|
|
88
|
+
檢查映像並核准到目前租戶;不需要主機 SSH 或每次人工核准。
|
|
89
|
+
`--entry-point module:app` 是 FUFU 擴充旗標,預設 `main:app`。
|
|
90
|
+
來源上限為 ZIP 6 MiB、解壓縮 32 MiB、2000 個檔案;不接受隱藏檔、Dockerfile、
|
|
91
|
+
自訂安裝設定或非空 requirements.txt。相依套件需在 CI 封裝,服務端不執行安裝 hook。
|
|
92
|
+
這是固定 ASGI 配方,不提供完整 Cloud Build 或 registry API。
|
|
93
|
+
成功回應包含 `build.status`、`imageDigest` 與來源包雜湊;自動核准映像
|
|
94
|
+
僅供該租戶目前服務使用,取代或刪除最後一個引用後不再開放重用。
|
|
95
|
+
|
|
96
|
+
Function 來源會上傳指定目錄中的
|
|
86
97
|
Python 與 requirements 檔;Lambda 會上傳指定的 ZIP。請使用只含必要程式碼的
|
|
87
98
|
目錄或 ZIP,先移除硬編碼秘密、個人資料與不需要的檔案。
|
|
88
99
|
CLI 無法判斷任意原始碼或 ZIP 內的資料是否可以分享。
|
|
89
100
|
|
|
90
101
|
| 服務 | 可用範圍與主要限制 |
|
|
91
102
|
| --- | --- |
|
|
92
|
-
| Cloud Run |
|
|
103
|
+
| Cloud Run | 已核准 HTTP 容器或 API 自動建置 Python ASGI 來源;無 autoscaling、traffic splitting、Jobs 或公開 ingress |
|
|
93
104
|
| Cloud Run functions / Functions Gen 1 | Python 3.12 HTTP 函式;Gen 1 為相同 HTTP runtime 的模擬子集,無背景事件 |
|
|
94
105
|
| AWS Lambda | Python 3.10 / 3.12 ZIP、同步 invoke、程式碼更新;遠端 ZIP 上限 6 MiB |
|
|
95
106
|
| GCS / S3 | bucket 與 object 操作子集;單一 object 上限 6 MiB,無 versioning / signed URL |
|
|
@@ -64,19 +64,30 @@ FUFU credential 與 session 檔仍屬秘密,必須由作業系統和 CI 控制
|
|
|
64
64
|
fufu capabilities
|
|
65
65
|
fufu capabilities --remote --provider gcp --require cloud-run-http
|
|
66
66
|
gcloud-fufu run deploy example --image approved-image:tag
|
|
67
|
+
gcloud-fufu run deploy example --source ./prepared-asgi.zip --entry-point main:app
|
|
67
68
|
gcloud-fufu functions deploy example-function --gen2 \
|
|
68
69
|
--runtime python312 --source ./function --entry-point handler --trigger-http
|
|
69
70
|
aws-fufu lambda list-functions
|
|
70
71
|
```
|
|
71
72
|
|
|
72
|
-
|
|
73
|
+
自訂映像需先由 FUFU 管理者準備及核准。API 模式的 Cloud Run `--source`
|
|
74
|
+
接受已封裝 Uvicorn 與相依套件的 Python 3.12 ASGI ZIP/乾淨目錄,自動離線建置、
|
|
75
|
+
檢查映像並核准到目前租戶;不需要主機 SSH 或每次人工核准。
|
|
76
|
+
`--entry-point module:app` 是 FUFU 擴充旗標,預設 `main:app`。
|
|
77
|
+
來源上限為 ZIP 6 MiB、解壓縮 32 MiB、2000 個檔案;不接受隱藏檔、Dockerfile、
|
|
78
|
+
自訂安裝設定或非空 requirements.txt。相依套件需在 CI 封裝,服務端不執行安裝 hook。
|
|
79
|
+
這是固定 ASGI 配方,不提供完整 Cloud Build 或 registry API。
|
|
80
|
+
成功回應包含 `build.status`、`imageDigest` 與來源包雜湊;自動核准映像
|
|
81
|
+
僅供該租戶目前服務使用,取代或刪除最後一個引用後不再開放重用。
|
|
82
|
+
|
|
83
|
+
Function 來源會上傳指定目錄中的
|
|
73
84
|
Python 與 requirements 檔;Lambda 會上傳指定的 ZIP。請使用只含必要程式碼的
|
|
74
85
|
目錄或 ZIP,先移除硬編碼秘密、個人資料與不需要的檔案。
|
|
75
86
|
CLI 無法判斷任意原始碼或 ZIP 內的資料是否可以分享。
|
|
76
87
|
|
|
77
88
|
| 服務 | 可用範圍與主要限制 |
|
|
78
89
|
| --- | --- |
|
|
79
|
-
| Cloud Run |
|
|
90
|
+
| Cloud Run | 已核准 HTTP 容器或 API 自動建置 Python ASGI 來源;無 autoscaling、traffic splitting、Jobs 或公開 ingress |
|
|
80
91
|
| Cloud Run functions / Functions Gen 1 | Python 3.12 HTTP 函式;Gen 1 為相同 HTTP runtime 的模擬子集,無背景事件 |
|
|
81
92
|
| AWS Lambda | Python 3.10 / 3.12 ZIP、同步 invoke、程式碼更新;遠端 ZIP 上限 6 MiB |
|
|
82
93
|
| GCS / S3 | bucket 與 object 操作子集;單一 object 上限 6 MiB,無 versioning / signed URL |
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
"status": "implemented",
|
|
12
12
|
"operations": [
|
|
13
13
|
"deploy-image",
|
|
14
|
+
"deploy-python312-asgi-source-api",
|
|
15
|
+
"automatic-tenant-image-admission",
|
|
14
16
|
"replace-service-subset",
|
|
15
17
|
"describe",
|
|
16
18
|
"list",
|
|
@@ -19,6 +21,9 @@
|
|
|
19
21
|
],
|
|
20
22
|
"limitations": [
|
|
21
23
|
"single-approved-container",
|
|
24
|
+
"source-prepackaged-dependencies-6MiB",
|
|
25
|
+
"source-fixed-recipe-no-custom-Dockerfile",
|
|
26
|
+
"source-approval-current-tenant-services-only",
|
|
22
27
|
"GCP-service-account-subset",
|
|
23
28
|
"explicit-GCS-workload-helper",
|
|
24
29
|
"no-registry-upload",
|
|
@@ -12,7 +12,7 @@ from .docker_backend import (Backend, FUNCTIONS_IMAGE, INGRESS_IMAGE, KINDS, LAM
|
|
|
12
12
|
inspect_image, prepare_runtime)
|
|
13
13
|
from .errors import FufuError, require
|
|
14
14
|
from .api_backend import ApiClient, api_selected, remote_command
|
|
15
|
-
from .remote_compute import RemoteBackend, source_archive, lambda_archive
|
|
15
|
+
from .remote_compute import RemoteBackend, source_archive, lambda_archive, cloud_run_archive
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class Parser(argparse.ArgumentParser):
|
|
@@ -204,7 +204,10 @@ def gcloud_command(argv):
|
|
|
204
204
|
run_sub = run_p.add_subparsers(dest="action", required=True)
|
|
205
205
|
d = run_sub.add_parser("deploy")
|
|
206
206
|
d.add_argument("name")
|
|
207
|
-
d.
|
|
207
|
+
source = d.add_mutually_exclusive_group(required=True)
|
|
208
|
+
source.add_argument('--image')
|
|
209
|
+
source.add_argument('--source', help='已封裝相依的 Python 3.12 ASGI ZIP/目錄;API 自動建置及核准')
|
|
210
|
+
d.add_argument('--entry-point', default='main:app', help='FUFU ASGI 來源子集,預設 main:app')
|
|
208
211
|
d.add_argument("--port", type=int, default=8080)
|
|
209
212
|
d.add_argument('--service-account')
|
|
210
213
|
deployment_options(d)
|
|
@@ -243,6 +246,13 @@ def gcloud_command(argv):
|
|
|
243
246
|
if action == "deploy":
|
|
244
247
|
manifest.name(a.name)
|
|
245
248
|
env = env_from_names(a.env)
|
|
249
|
+
if a.service == 'run' and a.source:
|
|
250
|
+
require(api_selected(), 'API_BACKEND_REQUIRED', 'Cloud Run --source 自動核准需要 FUFU API 模式。')
|
|
251
|
+
return RemoteBackend(kind).deploy(kind=kind, service=a.name, env=env, port=a.port,
|
|
252
|
+
archive=cloud_run_archive(a.source), runtime='python312', handler=a.entry_point,
|
|
253
|
+
startup_timeout=a.startup_timeout, role=a.service_account)
|
|
254
|
+
if a.service == 'run':
|
|
255
|
+
require(a.entry_point == 'main:app', 'UNSUPPORTED_ARGUMENT', '--entry-point 只用於 --source。')
|
|
246
256
|
if a.service == 'functions' and api_selected():
|
|
247
257
|
return RemoteBackend(kind).deploy(kind=kind, service=a.name, env=env,
|
|
248
258
|
archive=source_archive(a.source), runtime=a.runtime, handler=a.entry_point, startup_timeout=a.startup_timeout, role=a.service_account)
|
|
@@ -231,12 +231,12 @@ class Backend:
|
|
|
231
231
|
def public(self, r):
|
|
232
232
|
return {"name": r["name"], "kind": r["kind"], "workspace": self.workspace,
|
|
233
233
|
"revision": r["revision"], "url": self.url(r), "readiness": "tcp",
|
|
234
|
-
**{k: r[k] for k in ("runtime", "handler", "role", "sdkTransport", "iamEnforced") if k in r},
|
|
234
|
+
**{k: r[k] for k in ("runtime", "handler", "role", "sdkTransport", "iamEnforced", "build") if k in r},
|
|
235
235
|
"status": "running", "backend": "local-docker"}
|
|
236
236
|
|
|
237
237
|
def deploy(self, *, kind, service, image, port=8080, env=None, command=None,
|
|
238
238
|
startup_timeout=30, replace=True, require_existing=False, runtime=None, handler=None, preserve_environment=False,
|
|
239
|
-
runtime_env=None, role=None, gateway_container=None):
|
|
239
|
+
runtime_env=None, role=None, gateway_container=None, build=None):
|
|
240
240
|
name(service)
|
|
241
241
|
require(kind in KINDS, "UNSUPPORTED_SERVICE", "未支援此服務。")
|
|
242
242
|
require(type(port) is int and 1024 <= port <= 65535, "INVALID_PORT", "無效的容器 port。")
|
|
@@ -301,7 +301,8 @@ class Backend:
|
|
|
301
301
|
"port": port, "revision": revision,
|
|
302
302
|
**({'network': network, 'gateway_id': gateway['id']} if gateway else {}),
|
|
303
303
|
**({'role': role, 'sdkTransport': 'fufu-workload-token', 'iamEnforced': True} if role else {}),
|
|
304
|
-
**({
|
|
304
|
+
**({'build': build} if build else {}),
|
|
305
|
+
"environment_names": sorted(env),
|
|
305
306
|
**({"runtime": runtime, "handler": handler} if runtime is not None else {})}
|
|
306
307
|
deadline = time.monotonic() + startup_timeout
|
|
307
308
|
while True:
|
|
@@ -576,3 +577,54 @@ def build_lambda(archive, runtime, handler):
|
|
|
576
577
|
sdk.chmod(0o644)
|
|
577
578
|
(dst / "Dockerfile").write_text(f"FROM {base}\nENV FUFU_HANDLER={handler}\nCOPY --chown=10001:10001 app/ ${{LAMBDA_TASK_ROOT}}/\nCMD " + json.dumps(["_fufu_runtime_adapter.handler"]) + "\n")
|
|
578
579
|
return build_image(dst, f"fufu-cli-lambda:{uuid.uuid4().hex}")
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
def build_cloud_run(archive, handler, port, tag):
|
|
583
|
+
"""固定 Python ASGI 配方;不執行使用者 Dockerfile、安裝程式或建置 hook。"""
|
|
584
|
+
require(isinstance(handler, str) and re.fullmatch(r"[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*:[A-Za-z_]\w*", handler),
|
|
585
|
+
'INVALID_HANDLER', 'ASGI entry point 必須是 module:app。')
|
|
586
|
+
require(type(port) is int and 1024 <= port <= 65535, 'INVALID_PORT', '容器 port 範圍為 1024–65535。')
|
|
587
|
+
require(re.fullmatch(r'fufu-source:[a-f0-9]{64}', tag), 'INVALID_IMAGE', '來源映像名稱無效。')
|
|
588
|
+
inspect_image(PYTHON_IMAGE)
|
|
589
|
+
base = PYTHON_IMAGE
|
|
590
|
+
with tempfile.TemporaryDirectory(prefix='fufu-asgi-') as tmp:
|
|
591
|
+
context = Path(tmp)
|
|
592
|
+
app = context / 'app'
|
|
593
|
+
app.mkdir()
|
|
594
|
+
# Validate before extraction to bound disk use and reject silent build omissions.
|
|
595
|
+
try:
|
|
596
|
+
with zipfile.ZipFile(archive) as z:
|
|
597
|
+
entries = z.infolist()
|
|
598
|
+
require(len(entries) <= 2000 and sum(i.file_size for i in entries) <= 32 * 1024**2,
|
|
599
|
+
'SOURCE_LIMIT', 'ASGI 來源上限為 2000 個檔案、解壓縮後 32 MiB。')
|
|
600
|
+
require(all(not any(v.startswith('.') or v in {'__pycache__', 'node_modules'}
|
|
601
|
+
for v in PurePosixPath(i.filename).parts) for i in entries),
|
|
602
|
+
'UNSUPPORTED_SOURCE', 'ASGI 來源包不可包含隱藏檔或開發快取。')
|
|
603
|
+
require(not {'Dockerfile', 'docker-compose.yml', 'compose.yaml', 'Procfile', 'pyproject.toml', 'setup.py'}.intersection(z.namelist()),
|
|
604
|
+
'UNSUPPORTED_SOURCE', '來源部署使用固定 ASGI 配方;不接受自訂 Dockerfile、安裝或啟動設定。')
|
|
605
|
+
except (zipfile.BadZipFile, OSError):
|
|
606
|
+
raise FufuError('INVALID_ZIP', '來源必須是有效的 ZIP。') from None
|
|
607
|
+
extract_zip(Path(archive), app)
|
|
608
|
+
module = handler.split(':')[0].replace('.', '/')
|
|
609
|
+
require((app / (module + '.py')).is_file() or (app / module / '__init__.py').is_file(),
|
|
610
|
+
'INVALID_HANDLER', '來源包缺少指定 ASGI 模組。')
|
|
611
|
+
require((app / 'uvicorn/__main__.py').is_file(), 'DEPENDENCIES_NOT_PREPARED', '請將 Uvicorn 與所有 Linux Python 3.12 相依套件打入來源包。')
|
|
612
|
+
requirements = app / 'requirements.txt'
|
|
613
|
+
require(not requirements.exists() or not requirements.read_text().strip(),
|
|
614
|
+
'DEPENDENCIES_NOT_PREPARED', '來源模式不執行 requirements.txt;請先封裝相依套件。')
|
|
615
|
+
sdk = app / '_fufu_runtime_sdk.py'
|
|
616
|
+
require(not sdk.exists(), 'RESERVED_FILE', '來源與 FUFU SDK 保留檔名衝突。')
|
|
617
|
+
shutil.copyfile(Path(__file__).with_name('runtime_sdk.py'), sdk)
|
|
618
|
+
sdk.chmod(0o644)
|
|
619
|
+
command = ['python', '-m', 'uvicorn', handler, '--host', '0.0.0.0', '--port', str(port)]
|
|
620
|
+
(context / 'Dockerfile').write_text(
|
|
621
|
+
f'FROM {base}\nCOPY --chown=10001:10001 app/ /app/\nWORKDIR /app\n'
|
|
622
|
+
'ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1\nUSER 10001:10001\n'
|
|
623
|
+
'CMD ' + json.dumps(command) + '\n')
|
|
624
|
+
digest = build_image(context, tag, network='none')
|
|
625
|
+
policy = object_json(docker('image', 'inspect', digest, '--format',
|
|
626
|
+
'{"user":{{json .Config.User}},"size":{{json .Size}},"volumes":{{json .Config.Volumes}},"cmd":{{json .Config.Cmd}}}'))
|
|
627
|
+
require(policy.get('user') == '10001:10001' and type(policy.get('size')) is int
|
|
628
|
+
and 0 < policy['size'] <= 512 * 1024**2 and not policy.get('volumes') and policy.get('cmd') == command,
|
|
629
|
+
'IMAGE_POLICY_REJECTED', '建置映像未通過執行身分、大小或啟動設定檢查。')
|
|
630
|
+
return digest
|
|
@@ -43,6 +43,33 @@ def lambda_archive(path):
|
|
|
43
43
|
return base64.b64encode(p.read_bytes()).decode()
|
|
44
44
|
|
|
45
45
|
|
|
46
|
+
def cloud_run_archive(source):
|
|
47
|
+
"""Upload an explicitly prepared ZIP or directory, without dependency execution."""
|
|
48
|
+
root = Path(source)
|
|
49
|
+
require(not root.is_symlink(), 'INVALID_SOURCE', '來源不可為符號連結。')
|
|
50
|
+
if root.is_file():
|
|
51
|
+
return lambda_archive(root)
|
|
52
|
+
require(root.is_dir(), 'INVALID_SOURCE', '請指定已封裝相依套件的 ASGI ZIP 或目錄。')
|
|
53
|
+
data = io.BytesIO()
|
|
54
|
+
total = count = 0
|
|
55
|
+
with zipfile.ZipFile(data, 'w', zipfile.ZIP_DEFLATED) as archive:
|
|
56
|
+
for path in sorted(root.rglob('*')):
|
|
57
|
+
relative = path.relative_to(root)
|
|
58
|
+
require(not path.is_symlink(), 'INVALID_SOURCE', '來源不可含符號連結。')
|
|
59
|
+
require(not any(v.startswith('.') or v in {'__pycache__', 'node_modules'} for v in relative.parts),
|
|
60
|
+
'UNSUPPORTED_SOURCE', '請使用只含應用及相依套件的目錄,不可含隱藏檔或快取。')
|
|
61
|
+
if path.is_dir():
|
|
62
|
+
continue
|
|
63
|
+
require(path.is_file(), 'INVALID_SOURCE', '來源只接受一般檔案。')
|
|
64
|
+
total += path.stat().st_size
|
|
65
|
+
count += 1
|
|
66
|
+
require(count <= 2000 and total <= 32 * 1024**2, 'SOURCE_LIMIT', 'ASGI 來源超過上限。')
|
|
67
|
+
archive.write(path, relative.as_posix())
|
|
68
|
+
require(data.tell() <= MAX_ARCHIVE, 'SOURCE_LIMIT', '遠端 ASGI ZIP 上限為 6 MiB。')
|
|
69
|
+
require(0 < data.tell() <= MAX_ARCHIVE, 'SOURCE_LIMIT', '遠端 ASGI ZIP 上限為 6 MiB。')
|
|
70
|
+
return base64.b64encode(data.getvalue()).decode()
|
|
71
|
+
|
|
72
|
+
|
|
46
73
|
class RemoteBackend:
|
|
47
74
|
def __init__(self, kind):
|
|
48
75
|
require(kind in KIND_PROVIDER, "CAPABILITY_NOT_SUPPORTED", "此類型尚無遠端 compute。")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fufu-cloud-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: FUFU Cloud CLI for tenant-scoped mock cloud development and deployment
|
|
5
5
|
License-Expression: LicenseRef-Proprietary
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -77,19 +77,30 @@ FUFU credential 與 session 檔仍屬秘密,必須由作業系統和 CI 控制
|
|
|
77
77
|
fufu capabilities
|
|
78
78
|
fufu capabilities --remote --provider gcp --require cloud-run-http
|
|
79
79
|
gcloud-fufu run deploy example --image approved-image:tag
|
|
80
|
+
gcloud-fufu run deploy example --source ./prepared-asgi.zip --entry-point main:app
|
|
80
81
|
gcloud-fufu functions deploy example-function --gen2 \
|
|
81
82
|
--runtime python312 --source ./function --entry-point handler --trigger-http
|
|
82
83
|
aws-fufu lambda list-functions
|
|
83
84
|
```
|
|
84
85
|
|
|
85
|
-
|
|
86
|
+
自訂映像需先由 FUFU 管理者準備及核准。API 模式的 Cloud Run `--source`
|
|
87
|
+
接受已封裝 Uvicorn 與相依套件的 Python 3.12 ASGI ZIP/乾淨目錄,自動離線建置、
|
|
88
|
+
檢查映像並核准到目前租戶;不需要主機 SSH 或每次人工核准。
|
|
89
|
+
`--entry-point module:app` 是 FUFU 擴充旗標,預設 `main:app`。
|
|
90
|
+
來源上限為 ZIP 6 MiB、解壓縮 32 MiB、2000 個檔案;不接受隱藏檔、Dockerfile、
|
|
91
|
+
自訂安裝設定或非空 requirements.txt。相依套件需在 CI 封裝,服務端不執行安裝 hook。
|
|
92
|
+
這是固定 ASGI 配方,不提供完整 Cloud Build 或 registry API。
|
|
93
|
+
成功回應包含 `build.status`、`imageDigest` 與來源包雜湊;自動核准映像
|
|
94
|
+
僅供該租戶目前服務使用,取代或刪除最後一個引用後不再開放重用。
|
|
95
|
+
|
|
96
|
+
Function 來源會上傳指定目錄中的
|
|
86
97
|
Python 與 requirements 檔;Lambda 會上傳指定的 ZIP。請使用只含必要程式碼的
|
|
87
98
|
目錄或 ZIP,先移除硬編碼秘密、個人資料與不需要的檔案。
|
|
88
99
|
CLI 無法判斷任意原始碼或 ZIP 內的資料是否可以分享。
|
|
89
100
|
|
|
90
101
|
| 服務 | 可用範圍與主要限制 |
|
|
91
102
|
| --- | --- |
|
|
92
|
-
| Cloud Run |
|
|
103
|
+
| Cloud Run | 已核准 HTTP 容器或 API 自動建置 Python ASGI 來源;無 autoscaling、traffic splitting、Jobs 或公開 ingress |
|
|
93
104
|
| Cloud Run functions / Functions Gen 1 | Python 3.12 HTTP 函式;Gen 1 為相同 HTTP runtime 的模擬子集,無背景事件 |
|
|
94
105
|
| AWS Lambda | Python 3.10 / 3.12 ZIP、同步 invoke、程式碼更新;遠端 ZIP 上限 6 MiB |
|
|
95
106
|
| GCS / S3 | bucket 與 object 操作子集;單一 object 上限 6 MiB,無 versioning / signed URL |
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/runtime/functions/Dockerfile
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|