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.
Files changed (29) hide show
  1. {fufu_cloud_cli-0.5.0/src/fufu_cloud_cli.egg-info → fufu_cloud_cli-0.6.0}/PKG-INFO +14 -3
  2. {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/README.md +13 -2
  3. {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/pyproject.toml +1 -1
  4. {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/__init__.py +1 -1
  5. {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/capabilities.json +5 -0
  6. {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/cli.py +12 -2
  7. {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/docker_backend.py +55 -3
  8. {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/remote_compute.py +27 -0
  9. {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0/src/fufu_cloud_cli.egg-info}/PKG-INFO +14 -3
  10. {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/LICENSE +0 -0
  11. {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/MANIFEST.in +0 -0
  12. {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/setup.cfg +0 -0
  13. {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/__main__.py +0 -0
  14. {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/api_backend.py +0 -0
  15. {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/cloud_data.py +0 -0
  16. {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/control.py +0 -0
  17. {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/errors.py +0 -0
  18. {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/lambda_adapter.py +0 -0
  19. {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/manifest.py +0 -0
  20. {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/runtime/functions/Dockerfile +0 -0
  21. {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/runtime/ingress/Dockerfile +0 -0
  22. {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/runtime/ingress/proxy.py +0 -0
  23. {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/runtime_sdk.py +0 -0
  24. {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli/serverless.py +0 -0
  25. {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli.egg-info/SOURCES.txt +0 -0
  26. {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli.egg-info/dependency_links.txt +0 -0
  27. {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli.egg-info/entry_points.txt +0 -0
  28. {fufu_cloud_cli-0.5.0 → fufu_cloud_cli-0.6.0}/src/fufu_cloud_cli.egg-info/requires.txt +0 -0
  29. {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.5.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
- 映像需先由 FUFU 管理者準備及核准。Function 來源會上傳指定目錄中的
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 | 單一已核准 HTTP 容器;無 autoscaling、traffic splitting、Jobs 或公開 ingress |
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
- 映像需先由 FUFU 管理者準備及核准。Function 來源會上傳指定目錄中的
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 | 單一已核准 HTTP 容器;無 autoscaling、traffic splitting、Jobs 或公開 ingress |
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 |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fufu-cloud-cli"
7
- version = "0.5.0"
7
+ version = "0.6.0"
8
8
  description = "FUFU Cloud CLI for tenant-scoped mock cloud development and deployment"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,3 +1,3 @@
1
1
  """FUFU CLI 與本機部署工具;相容範圍見 capabilities.json。"""
2
2
 
3
- __version__ = "0.5.0"
3
+ __version__ = "0.6.0"
@@ -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.add_argument("--image", required=True)
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
- **({"environment_names": sorted(env)} if kind != 'cloud-run-http' else {}),
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.5.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
- 映像需先由 FUFU 管理者準備及核准。Function 來源會上傳指定目錄中的
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 | 單一已核准 HTTP 容器;無 autoscaling、traffic splitting、Jobs 或公開 ingress |
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