fufu-cloud-cli 0.9.6__tar.gz → 0.9.8__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.9.6/src/fufu_cloud_cli.egg-info → fufu_cloud_cli-0.9.8}/PKG-INFO +40 -4
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/README.md +39 -3
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/pyproject.toml +1 -1
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/__init__.py +1 -1
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/api_backend.py +8 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/aws_errors.py +1 -1
- fufu_cloud_cli-0.9.8/src/fufu_cloud_cli/aws_lambda.py +125 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/aws_output.py +9 -1
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/capabilities.json +55 -19
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/cli.py +59 -14
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/cloud_data.py +1 -1
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/control.py +2 -2
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/docker_backend.py +23 -6
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/gcp_infra.py +42 -9
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/gcs.py +95 -6
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/lambda_adapter.py +17 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/limits.py +1 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/object_transfer.py +11 -6
- fufu_cloud_cli-0.9.8/src/fufu_cloud_cli/observability.py +127 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/remote_compute.py +2 -1
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/runtime_sdk.py +1 -1
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/terraform_bridge/runner.py +15 -1
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8/src/fufu_cloud_cli.egg-info}/PKG-INFO +40 -4
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli.egg-info/SOURCES.txt +2 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/LICENSE +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/MANIFEST.in +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/setup.cfg +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/__main__.py +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/bigquery.py +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/capacity.py +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/cloudflare.py +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/errors.py +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/gcloud_environment.py +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/gcloud_output.py +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/gcp_sdk.py +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/http_runtime.py +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/iac_state.py +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/manifest.py +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/public_access.py +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/pubsub.py +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/pulumi-endpoints.json +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/pulumi_bridge.py +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/runtime/functions/Dockerfile +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/runtime/ingress/Dockerfile +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/runtime/ingress/proxy.py +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/secrets.py +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/serverless.py +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/terraform_bridge/__init__.py +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/terraform_bridge/aws.py +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/terraform_bridge/aws_data.py +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/terraform_bridge/common.py +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/terraform_bridge/google.py +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/terraform_bridge/google_pubsub.py +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/terraform_bridge/remote_state.py +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/vendor/AWS-CLI-LICENSE.txt +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/vendor/__init__.py +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli/vendor/aws_text.py +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli.egg-info/dependency_links.txt +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli.egg-info/entry_points.txt +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/src/fufu_cloud_cli.egg-info/requires.txt +0 -0
- {fufu_cloud_cli-0.9.6 → fufu_cloud_cli-0.9.8}/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.9.
|
|
3
|
+
Version: 0.9.8
|
|
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
|
|
@@ -27,6 +27,41 @@ Dynamic: license-file
|
|
|
27
27
|
`gcloud-fufu`、`az-fufu`、`wrangler-fufu`、`bq-fufu`。這是 AWS、Google Cloud、Azure 與 Cloudflare 的**明確子集**,
|
|
28
28
|
只供 FUFU mock 環境使用;不會把部署轉送到正式雲端。
|
|
29
29
|
|
|
30
|
+
2026-09-10 後續原始碼與 Cloud Shell 新增 GKE、firewall、五類 global LB 與 Redis
|
|
31
|
+
配置命令,typed configuration 合計 22 類;都有 `MOCK ONLY` 提示、引用存在性與
|
|
32
|
+
相依刪除檢查,不建立 Kubernetes、VM、Redis 或網路資料面,也未增加 IaC 資源。
|
|
33
|
+
這批包含在 0.9.8;需要配套的 FUFU Console 與運算服務。
|
|
34
|
+
|
|
35
|
+
0.9.8 提供 Cloud Run/Functions 的原生形式日誌查詢、CloudWatch Logs
|
|
36
|
+
tail/follow,以及 `fufu observability` 的日誌、請求指標與追蹤查詢。
|
|
37
|
+
公開套件包含完整的日誌指令模組;重複建立 CloudWatch log group 的
|
|
38
|
+
`ResourceAlreadyExistsException` 會保留至 CLI 與 boto3 例外處理。
|
|
39
|
+
配套服務端持久化收集 Cloud Run、Functions Gen 1/Gen 2、Lambda 的
|
|
40
|
+
stdout/stderr 與例外堆疊。支援租戶隔離、IAM、敏感值遮罩、查詢分頁、
|
|
41
|
+
request ID/trace 關聯及重啟恢復。GCP log names 清單、dry run、欄位存在
|
|
42
|
+
查詢,以及 AWS 10,000 筆/1 MiB 寫入批次均有契約驗收。
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
gcloud-fufu run services logs read hello --limit=100 --log-filter='severity>=ERROR'
|
|
46
|
+
gcloud-fufu functions logs read hello --gen2 --limit=100
|
|
47
|
+
aws-fufu logs tail /aws/lambda/hello --since 1h --follow --fufu-duration 300
|
|
48
|
+
fufu observability status --provider gcp
|
|
49
|
+
fufu observability logs --provider aws --request-id REQUEST_ID --since 1h
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
請求 metrics/traces 是明示的 FUFU 擴充,沒有實作完整 Cloud Monitoring、
|
|
53
|
+
CloudWatch Metrics、Trace 或 X-Ray。Log Router sinks、Logs Insights、
|
|
54
|
+
subscriptions、alerts 及完整 OpenTelemetry collector 尚未支援。
|
|
55
|
+
|
|
56
|
+
0.9.7 補齊 Lambda 五種 `wait` 指令、設定/程式碼更新的 `--revision-id`,
|
|
57
|
+
以及常用設定的 `--cli-input-json` 和 `Variables={KEY=value}` shorthand。
|
|
58
|
+
過期 RevisionId 回 `PreconditionFailedException`,保留原部署;CLI 仍等待
|
|
59
|
+
部署完成才返回,waiter 有有限次數與失敗退出碼 255。程式碼雜湊、大小、
|
|
60
|
+
最新修改時間與執行架構來自實際部署;預設記憶體為 128 MiB、期限 3 秒。
|
|
61
|
+
需要配套 Console/compute worker 更新。JSON 只接受明列欄位,ZIP 另以
|
|
62
|
+
`--zip-file fileb://...` 傳入;不支援 Code.S3Bucket、Layers、Image、版本
|
|
63
|
+
或 aliases。Function URL 的無效 AWS 簽名與匿名呼叫回 403。
|
|
64
|
+
|
|
30
65
|
0.9.6 新增 AWS Lambda Event/DryRun、configuration 更新、JMESPath `--query`、
|
|
31
66
|
json/text/yaml 輸出、SQS FIFO/batch、SSM path/history、DynamoDB Binary
|
|
32
67
|
及 CloudWatch Logs。需要配套後端;Event 202 只表示持久化接受,並非 handler
|
|
@@ -53,8 +88,9 @@ fufu infrastructure catalog
|
|
|
53
88
|
```
|
|
54
89
|
|
|
55
90
|
BigQuery 是 tenant emulator 的 GoogleSQL 子集,無 DML/DDL、load/export、
|
|
56
|
-
|
|
57
|
-
|
|
91
|
+
外部表與效能/費用等價。配套新版 Logging 預設每租戶 2 GiB;GCP 保留
|
|
92
|
+
30 天,AWS 依 log group retention 設定,未設定時不自動到期。只收集已
|
|
93
|
+
驗證歸屬的 FUFU 工作負載容器。Tasks 每 queue 每秒最多 1 次、每租戶 2 個同時投遞;耗盡 task
|
|
58
94
|
留待檢查,與官方刪除策略不同。新的 dependency flow 不代表完整應用都能遷移。
|
|
59
95
|
|
|
60
96
|
0.9.4 新增 GCS generation/metageneration 條件、`storage objects describe/update/list`、8 KiB 自訂 metadata 與二進位 `cat`。競爭寫入、過期條件、CLI metadata 和官方 gcloud 輸出欄位已對照驗收;不相符條件回傳 `PRECONDITION_FAILED`/HTTP 412。新功能需要同步更新 FUFU Console、GCS 儲存映像和 Cloud Shell。儲存升級前須停寫並備份完整 volume;新 metadata 格式可讀舊資料,回滾須還原升級前備份。
|
|
@@ -71,7 +107,7 @@ Python 預設識別而遇到 Cloudflare HTTP 403/1010。識別標頭不取代
|
|
|
71
107
|
## Cloudflare 前端部署與 R2
|
|
72
108
|
|
|
73
109
|
```bash
|
|
74
|
-
pip install 'fufu-cloud-cli==0.9.
|
|
110
|
+
pip install 'fufu-cloud-cli==0.9.8'
|
|
75
111
|
export FUFU_CLOUDFLARE_CREDENTIAL_FILE=/run/secrets/fufu-cloudflare.json
|
|
76
112
|
wrangler-fufu capabilities
|
|
77
113
|
wrangler-fufu pages project create frontend --production-branch main
|
|
@@ -4,6 +4,41 @@
|
|
|
4
4
|
`gcloud-fufu`、`az-fufu`、`wrangler-fufu`、`bq-fufu`。這是 AWS、Google Cloud、Azure 與 Cloudflare 的**明確子集**,
|
|
5
5
|
只供 FUFU mock 環境使用;不會把部署轉送到正式雲端。
|
|
6
6
|
|
|
7
|
+
2026-09-10 後續原始碼與 Cloud Shell 新增 GKE、firewall、五類 global LB 與 Redis
|
|
8
|
+
配置命令,typed configuration 合計 22 類;都有 `MOCK ONLY` 提示、引用存在性與
|
|
9
|
+
相依刪除檢查,不建立 Kubernetes、VM、Redis 或網路資料面,也未增加 IaC 資源。
|
|
10
|
+
這批包含在 0.9.8;需要配套的 FUFU Console 與運算服務。
|
|
11
|
+
|
|
12
|
+
0.9.8 提供 Cloud Run/Functions 的原生形式日誌查詢、CloudWatch Logs
|
|
13
|
+
tail/follow,以及 `fufu observability` 的日誌、請求指標與追蹤查詢。
|
|
14
|
+
公開套件包含完整的日誌指令模組;重複建立 CloudWatch log group 的
|
|
15
|
+
`ResourceAlreadyExistsException` 會保留至 CLI 與 boto3 例外處理。
|
|
16
|
+
配套服務端持久化收集 Cloud Run、Functions Gen 1/Gen 2、Lambda 的
|
|
17
|
+
stdout/stderr 與例外堆疊。支援租戶隔離、IAM、敏感值遮罩、查詢分頁、
|
|
18
|
+
request ID/trace 關聯及重啟恢復。GCP log names 清單、dry run、欄位存在
|
|
19
|
+
查詢,以及 AWS 10,000 筆/1 MiB 寫入批次均有契約驗收。
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
gcloud-fufu run services logs read hello --limit=100 --log-filter='severity>=ERROR'
|
|
23
|
+
gcloud-fufu functions logs read hello --gen2 --limit=100
|
|
24
|
+
aws-fufu logs tail /aws/lambda/hello --since 1h --follow --fufu-duration 300
|
|
25
|
+
fufu observability status --provider gcp
|
|
26
|
+
fufu observability logs --provider aws --request-id REQUEST_ID --since 1h
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
請求 metrics/traces 是明示的 FUFU 擴充,沒有實作完整 Cloud Monitoring、
|
|
30
|
+
CloudWatch Metrics、Trace 或 X-Ray。Log Router sinks、Logs Insights、
|
|
31
|
+
subscriptions、alerts 及完整 OpenTelemetry collector 尚未支援。
|
|
32
|
+
|
|
33
|
+
0.9.7 補齊 Lambda 五種 `wait` 指令、設定/程式碼更新的 `--revision-id`,
|
|
34
|
+
以及常用設定的 `--cli-input-json` 和 `Variables={KEY=value}` shorthand。
|
|
35
|
+
過期 RevisionId 回 `PreconditionFailedException`,保留原部署;CLI 仍等待
|
|
36
|
+
部署完成才返回,waiter 有有限次數與失敗退出碼 255。程式碼雜湊、大小、
|
|
37
|
+
最新修改時間與執行架構來自實際部署;預設記憶體為 128 MiB、期限 3 秒。
|
|
38
|
+
需要配套 Console/compute worker 更新。JSON 只接受明列欄位,ZIP 另以
|
|
39
|
+
`--zip-file fileb://...` 傳入;不支援 Code.S3Bucket、Layers、Image、版本
|
|
40
|
+
或 aliases。Function URL 的無效 AWS 簽名與匿名呼叫回 403。
|
|
41
|
+
|
|
7
42
|
0.9.6 新增 AWS Lambda Event/DryRun、configuration 更新、JMESPath `--query`、
|
|
8
43
|
json/text/yaml 輸出、SQS FIFO/batch、SSM path/history、DynamoDB Binary
|
|
9
44
|
及 CloudWatch Logs。需要配套後端;Event 202 只表示持久化接受,並非 handler
|
|
@@ -30,8 +65,9 @@ fufu infrastructure catalog
|
|
|
30
65
|
```
|
|
31
66
|
|
|
32
67
|
BigQuery 是 tenant emulator 的 GoogleSQL 子集,無 DML/DDL、load/export、
|
|
33
|
-
|
|
34
|
-
|
|
68
|
+
外部表與效能/費用等價。配套新版 Logging 預設每租戶 2 GiB;GCP 保留
|
|
69
|
+
30 天,AWS 依 log group retention 設定,未設定時不自動到期。只收集已
|
|
70
|
+
驗證歸屬的 FUFU 工作負載容器。Tasks 每 queue 每秒最多 1 次、每租戶 2 個同時投遞;耗盡 task
|
|
35
71
|
留待檢查,與官方刪除策略不同。新的 dependency flow 不代表完整應用都能遷移。
|
|
36
72
|
|
|
37
73
|
0.9.4 新增 GCS generation/metageneration 條件、`storage objects describe/update/list`、8 KiB 自訂 metadata 與二進位 `cat`。競爭寫入、過期條件、CLI metadata 和官方 gcloud 輸出欄位已對照驗收;不相符條件回傳 `PRECONDITION_FAILED`/HTTP 412。新功能需要同步更新 FUFU Console、GCS 儲存映像和 Cloud Shell。儲存升級前須停寫並備份完整 volume;新 metadata 格式可讀舊資料,回滾須還原升級前備份。
|
|
@@ -48,7 +84,7 @@ Python 預設識別而遇到 Cloudflare HTTP 403/1010。識別標頭不取代
|
|
|
48
84
|
## Cloudflare 前端部署與 R2
|
|
49
85
|
|
|
50
86
|
```bash
|
|
51
|
-
pip install 'fufu-cloud-cli==0.9.
|
|
87
|
+
pip install 'fufu-cloud-cli==0.9.8'
|
|
52
88
|
export FUFU_CLOUDFLARE_CREDENTIAL_FILE=/run/secrets/fufu-cloudflare.json
|
|
53
89
|
wrangler-fufu capabilities
|
|
54
90
|
wrangler-fufu pages project create frontend --production-branch main
|
|
@@ -184,6 +184,14 @@ class ApiClient:
|
|
|
184
184
|
return json.loads(raw)
|
|
185
185
|
except HTTPError as exc:
|
|
186
186
|
status = exc.code
|
|
187
|
+
if self.provider == 'aws' and '/compute/' in path and status in {400, 412}:
|
|
188
|
+
try:
|
|
189
|
+
value = json.loads(exc.read(4096))
|
|
190
|
+
code = value.get('error', {}).get('code') if isinstance(value, dict) and isinstance(value.get('error'), dict) else None
|
|
191
|
+
if code in {'PreconditionFailedException', 'InvalidRequestContentException'}:
|
|
192
|
+
raise FufuError(code, 'Lambda 操作遭拒;未輸出原始設定或錯誤內容。') from None
|
|
193
|
+
except (ValueError, OSError): pass
|
|
194
|
+
finally: exc.close()
|
|
187
195
|
if self.provider == 'aws' and '/control/native' in path:
|
|
188
196
|
from .aws_errors import AWS_ERRORS
|
|
189
197
|
try:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""Only documented provider error codes may cross the public transport boundary."""
|
|
2
|
-
AWS_ERRORS = frozenset('''ConditionalCheckFailedException ResourceNotFoundException ResourceInUseException ValidationException
|
|
2
|
+
AWS_ERRORS = frozenset('''ConditionalCheckFailedException ResourceNotFoundException ResourceInUseException ResourceAlreadyExistsException ValidationException
|
|
3
3
|
NoSuchKey NoSuchBucket NotFound NotModified ParameterNotFound ParameterAlreadyExists ParameterVersionNotFound QueueDoesNotExist
|
|
4
4
|
PreconditionFailed InvalidRange InvalidParameterException InvalidParameterValue InvalidAttributeName InvalidAttributeValue QueueNameExists
|
|
5
5
|
PurgeQueueInProgress ReceiptHandleIsInvalid BatchEntryIdsNotDistinct EmptyBatchRequest BatchRequestTooLong InvalidBatchEntryId
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Lambda command inputs and bounded AWS waiter semantics for the supported subset."""
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
from .aws_output import configuration
|
|
7
|
+
from .errors import FufuError, require
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
WAITERS = {
|
|
11
|
+
'function-exists': ('describe', None, None, 1, 20),
|
|
12
|
+
'function-active': ('configuration', 'State', 'Active', 5, 60),
|
|
13
|
+
'function-active-v2': ('describe', 'State', 'Active', 1, 300),
|
|
14
|
+
'function-updated': ('configuration', 'LastUpdateStatus', 'Successful', 5, 60),
|
|
15
|
+
'function-updated-v2': ('describe', 'LastUpdateStatus', 'Successful', 1, 300),
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def wait(backend, waiter, name):
|
|
20
|
+
method, field, success, delay, attempts = WAITERS[waiter]
|
|
21
|
+
deadline = time.monotonic() + delay * attempts
|
|
22
|
+
for attempt in range(attempts):
|
|
23
|
+
try:
|
|
24
|
+
value = configuration(getattr(backend, method)('aws-lambda-sync', name))
|
|
25
|
+
except FufuError as error:
|
|
26
|
+
if waiter != 'function-exists' or error.code not in {'NOT_FOUND', 'RESOURCE_NOT_FOUND', 'ResourceNotFoundException'}:
|
|
27
|
+
raise FufuError('WAITER_FAILED', '等待服務時查詢失敗;請確認權限與服務狀態。', 255) from None
|
|
28
|
+
else:
|
|
29
|
+
if field is None or value.get(field) == success: return
|
|
30
|
+
if value.get(field) == 'Failed':
|
|
31
|
+
raise FufuError('WAITER_FAILED', '服務回報失敗狀態;未達到等待條件。', 255)
|
|
32
|
+
remaining = deadline - time.monotonic()
|
|
33
|
+
if attempt + 1 == attempts or remaining <= 0: break
|
|
34
|
+
time.sleep(min(delay, remaining))
|
|
35
|
+
raise FufuError('WAITER_FAILED', '等待超過期限;服務操作可能仍在執行,請重新查詢。', 255)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def environment(value):
|
|
39
|
+
"""Native JSON or Variables={KEY=value,...}; malformed/ambiguous input fails closed."""
|
|
40
|
+
from .control import read_json
|
|
41
|
+
if value.startswith(('{', 'file://')):
|
|
42
|
+
return read_json(value)
|
|
43
|
+
require(value.startswith('Variables={') and value.endswith('}'), 'INVALID_INPUT',
|
|
44
|
+
'Environment 請使用 JSON、file:// JSON 或 Variables={KEY=value,...}。')
|
|
45
|
+
text = value[len('Variables={'):-1]
|
|
46
|
+
values = {}; position = 0
|
|
47
|
+
# Quotes and escaped commas are supported without shell execution or expansion.
|
|
48
|
+
pattern = re.compile(r'\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*')
|
|
49
|
+
while position < len(text):
|
|
50
|
+
matched = pattern.match(text, position)
|
|
51
|
+
require(matched is not None, 'INVALID_INPUT', 'Environment shorthand 格式無效。')
|
|
52
|
+
key = matched[1]; position = matched.end(); chars = []; quote = None
|
|
53
|
+
require(key not in values, 'INVALID_INPUT', 'Environment 不可重複指定相同變數。')
|
|
54
|
+
if position < len(text) and text[position] in {'"', "'"}:
|
|
55
|
+
quote = text[position]; position += 1
|
|
56
|
+
closed = quote is None
|
|
57
|
+
while position < len(text):
|
|
58
|
+
character = text[position]; position += 1
|
|
59
|
+
if character == '\\':
|
|
60
|
+
require(position < len(text), 'INVALID_INPUT', 'Environment 跳脫序列未完成。')
|
|
61
|
+
chars.append(text[position]); position += 1
|
|
62
|
+
elif quote and character == quote:
|
|
63
|
+
closed = True
|
|
64
|
+
while position < len(text) and text[position].isspace(): position += 1
|
|
65
|
+
require(position == len(text) or text[position] == ',', 'INVALID_INPUT', 'Environment 引號後有多餘內容。')
|
|
66
|
+
if position < len(text): position += 1
|
|
67
|
+
break
|
|
68
|
+
elif not quote and character == ',': break
|
|
69
|
+
else:
|
|
70
|
+
require(quote is not None or character not in {'{', '}', '[', ']', '"', "'"},
|
|
71
|
+
'UNSUPPORTED_ARGUMENT', '複雜 Environment 值請使用 JSON 或引號。')
|
|
72
|
+
chars.append(character)
|
|
73
|
+
require(closed, 'INVALID_INPUT', 'Environment 引號未關閉。')
|
|
74
|
+
values[key] = ''.join(chars) if quote else ''.join(chars).rstrip()
|
|
75
|
+
require(position != len(text) or not text.endswith(','), 'INVALID_INPUT', 'Environment 不可有結尾逗號。')
|
|
76
|
+
return {'Variables': values}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
FIELDS = {
|
|
80
|
+
'FunctionName': ('--function-name', str), 'Runtime': ('--runtime', str),
|
|
81
|
+
'Handler': ('--handler', str), 'Role': ('--role', str),
|
|
82
|
+
'Environment': ('--environment', dict), 'MemorySize': ('--memory-size', int),
|
|
83
|
+
'Timeout': ('--timeout', int), 'EphemeralStorage': ('--ephemeral-storage', dict),
|
|
84
|
+
'RevisionId': ('--revision-id', str), 'InvocationType': ('--invocation-type', str),
|
|
85
|
+
'Payload': ('--payload', str),
|
|
86
|
+
}
|
|
87
|
+
INPUTS = {
|
|
88
|
+
'create-function': {'FunctionName', 'Runtime', 'Handler', 'Role', 'Environment', 'MemorySize', 'Timeout', 'EphemeralStorage'},
|
|
89
|
+
'update-function-code': {'FunctionName', 'RevisionId'},
|
|
90
|
+
'update-function-configuration': {'FunctionName', 'Role', 'Environment', 'MemorySize', 'Timeout', 'EphemeralStorage', 'RevisionId'},
|
|
91
|
+
'get-function': {'FunctionName'}, 'get-function-configuration': {'FunctionName'},
|
|
92
|
+
'delete-function': {'FunctionName'}, 'invoke': {'FunctionName', 'InvocationType', 'Payload'},
|
|
93
|
+
'list-functions': set(),
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def input_json(argv):
|
|
98
|
+
"""Expand validated native JSON before argparse; explicit flags take precedence."""
|
|
99
|
+
from .control import read_json
|
|
100
|
+
if len(argv) < 2: return argv
|
|
101
|
+
rest = []; documents = []; index = 2
|
|
102
|
+
while index < len(argv):
|
|
103
|
+
flag, separator, value = argv[index].partition('=')
|
|
104
|
+
if flag == '--cli-input-json':
|
|
105
|
+
if not separator:
|
|
106
|
+
index += 1
|
|
107
|
+
require(index < len(argv), 'INVALID_INPUT', 'cli-input-json 缺少值。')
|
|
108
|
+
value = argv[index]
|
|
109
|
+
documents.append(value)
|
|
110
|
+
else: rest.append(argv[index])
|
|
111
|
+
index += 1
|
|
112
|
+
if not documents: return argv
|
|
113
|
+
require(len(documents) == 1 and argv[1] in INPUTS, 'UNSUPPORTED_ARGUMENT', '此操作不支援指定的 cli-input-json。')
|
|
114
|
+
value = read_json(documents[0])
|
|
115
|
+
require(isinstance(value, dict) and not value.keys() - INPUTS[argv[1]], 'UNSUPPORTED_ARGUMENT',
|
|
116
|
+
'Lambda JSON 含未支援的欄位;ZIP 請另用 --zip-file fileb:// 傳入,未執行操作。')
|
|
117
|
+
explicit = {arg.partition('=')[0] for arg in rest if arg.startswith('--')}
|
|
118
|
+
# FUFU memory alias selects the same official field.
|
|
119
|
+
if '--memory' in explicit: explicit.add('--memory-size')
|
|
120
|
+
expanded = []
|
|
121
|
+
for key, item in value.items():
|
|
122
|
+
flag, expected = FIELDS[key]
|
|
123
|
+
require(type(item) is expected, 'INVALID_INPUT', 'Lambda JSON 欄位型別無效。')
|
|
124
|
+
if flag not in explicit: expanded.extend([flag + '=' + (json.dumps(item) if expected is dict else str(item))])
|
|
125
|
+
return argv[:2] + expanded + rest
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""AWS JMESPath and native text formatting over the supported response subset."""
|
|
2
2
|
import io
|
|
3
3
|
import json
|
|
4
|
+
import base64
|
|
4
5
|
|
|
5
6
|
import jmespath
|
|
6
7
|
from jmespath.exceptions import JMESPathError
|
|
@@ -25,10 +26,17 @@ def configuration(value):
|
|
|
25
26
|
fields = {'FunctionName': value.get('name'), 'FunctionArn': value.get('functionArn'),
|
|
26
27
|
'Runtime': value.get('runtime'), 'Handler': value.get('handler'), 'Role': value.get('role'),
|
|
27
28
|
'MemorySize': resources.get('memoryMiB'), 'Timeout': resources.get('timeoutSeconds'),
|
|
28
|
-
'RevisionId': value.get('revision'), 'LastModified': value.get('createdAt')}
|
|
29
|
+
'RevisionId': value.get('revision'), 'LastModified': value.get('lastModified', value.get('createdAt'))}
|
|
29
30
|
result = {key: item for key, item in fields.items() if item is not None}
|
|
30
31
|
if resources.get('tmpMiB') is not None: result['EphemeralStorage'] = {'Size': resources['tmpMiB']}
|
|
31
32
|
if value.get('status') == 'running': result.update(State='Active', LastUpdateStatus='Successful')
|
|
33
|
+
if value.get('state'): result['State'] = value['state']
|
|
34
|
+
if value.get('lastUpdateStatus'): result['LastUpdateStatus'] = value['lastUpdateStatus']
|
|
35
|
+
result.update(Version='$LATEST', PackageType='Zip')
|
|
36
|
+
if value.get('architecture'): result['Architectures'] = [value['architecture']]
|
|
37
|
+
source = value.get('source', {})
|
|
38
|
+
if source.get('sha256'): result['CodeSha256'] = base64.b64encode(bytes.fromhex(source['sha256'])).decode()
|
|
39
|
+
if source.get('size') is not None: result['CodeSize'] = source['size']
|
|
32
40
|
if 'env' in value: result['Environment'] = {'Variables': value['env']}
|
|
33
41
|
result['Fufu'] = {key: value[key] for key in ['mode', 'backend', 'readiness', 'serviceId', 'publicAccess', 'iamEnforced'] if key in value}
|
|
34
42
|
result['Fufu'].setdefault('mode', 'simplified')
|
|
@@ -226,7 +226,8 @@
|
|
|
226
226
|
"no-Entra-STS"
|
|
227
227
|
]
|
|
228
228
|
},
|
|
229
|
-
"aws-cloudwatch-logs": {"status": "implemented", "backend": "api", "mode": "simplified", "operations": ["log-group-crud", "log-stream-crud", "put-get-filter-events", "retention-policy", "CLI-UI-boto3"], "limitations": ["
|
|
229
|
+
"aws-cloudwatch-logs": {"status": "implemented", "backend": "api", "mode": "simplified", "operations": ["log-group-crud", "log-stream-crud", "put-get-filter-events", "retention-policy-expiry", "CLI-UI-boto3", "owned-Lambda-stdout-stderr", "logs-tail-follow", "SQLite-persistence", "signed-pagination", "credential-redaction"], "limitations": ["single-host-configurable-storage-capacity", "simple-text-filters-only", "no-Insights-subscriptions-or-KMS", "follow-default-300s-max-3600s", "service-page-tokens-except-tail-auto-pagination"]},
|
|
230
|
+
"fufu-observability": {"status": "implemented", "backend": "api", "mode": "simplified", "operations": ["logs", "request-metrics", "request-traces", "collector-status", "GCP-AWS-IAM", "structured-trace-links"], "limitations": ["explicit-FUFU-extension-not-native-Monitoring-CloudWatch-Trace-XRay-API", "gateway-request-duration-not-handler-or-billing-metrics", "no-automatic-SDK-spans-alerts-or-cross-service-trace-assembly", "plain-stdout-correlates-by-service-revision", "async-Lambda-logs-without-request-metrics", "single-host-no-HA"]},
|
|
230
231
|
"aws-lambda-async": {"status": "implemented", "backend": "api", "mode": "simplified", "operations": ["invoke-Event", "invoke-DryRun", "CLI-UI-boto3-Invoke"], "limitations": ["1MiB-payload", "single-host-SQLite-two-dispatchers", "at-least-once", "two-handler-retries-60s-120s", "system-retries-up-to-6h", "no-destinations-or-DLQ", "no-AWS-scaling-or-managed-timeout-equivalence", "10000-events-or-512MiB-encoded-queue-per-tenant"]},
|
|
231
232
|
"aws-cli-output": {"status": "implemented", "mode": "simplified", "operations": ["JMESPath-query", "json", "text", "yaml"], "limitations": ["no-table-or-yaml-stream", "no-full-CLI-auto-pagination", "Lambda-response-subset-no-Code-download"]},
|
|
232
233
|
"aws-data-services": {
|
|
@@ -345,17 +346,21 @@
|
|
|
345
346
|
"mode": "simplified",
|
|
346
347
|
"operations": [
|
|
347
348
|
"write-read",
|
|
348
|
-
"filter-AND-severity-timestamp-
|
|
349
|
+
"filter-AND-OR-NOT-structured-fields-severity-timestamp-trace-text",
|
|
349
350
|
"logs-list-delete",
|
|
350
351
|
"partialSuccess",
|
|
351
352
|
"explicit-official-Python-REST-SDK",
|
|
352
|
-
"setup-logging"
|
|
353
|
+
"setup-logging",
|
|
354
|
+
"owned-CloudRun-Functions-stdout-stderr",
|
|
355
|
+
"run-services-logs-read",
|
|
356
|
+
"functions-logs-read-gen1-gen2",
|
|
357
|
+
"SQLite-persistence-retention-redaction"
|
|
353
358
|
],
|
|
354
359
|
"limitations": [
|
|
355
|
-
"
|
|
356
|
-
"
|
|
357
|
-
"
|
|
358
|
-
"no-sinks-
|
|
360
|
+
"single-host-configurable-storage-capacity-default-2GiB-per-tenant",
|
|
361
|
+
"default-30-day-log-retention",
|
|
362
|
+
"bounded-durable-spool-and-Docker-log-rotation",
|
|
363
|
+
"no-sinks-alerts-or-native-Monitoring-Trace-API",
|
|
359
364
|
"no-new-IaC-resources"
|
|
360
365
|
]
|
|
361
366
|
},
|
|
@@ -378,16 +383,30 @@
|
|
|
378
383
|
"builds",
|
|
379
384
|
"gemini-models",
|
|
380
385
|
"vm-policies",
|
|
381
|
-
"eventarc-triggers"
|
|
386
|
+
"eventarc-triggers",
|
|
387
|
+
"gke-clusters",
|
|
388
|
+
"firewall-rules",
|
|
389
|
+
"health-checks",
|
|
390
|
+
"backend-services",
|
|
391
|
+
"url-maps",
|
|
392
|
+
"target-http-proxies",
|
|
393
|
+
"forwarding-rules",
|
|
394
|
+
"redis-instances"
|
|
382
395
|
],
|
|
383
396
|
"operations": [
|
|
384
397
|
"typed-crud",
|
|
385
398
|
"three-locale-mobile-console",
|
|
386
|
-
"fufu-infrastructure-cli"
|
|
399
|
+
"fufu-infrastructure-cli",
|
|
400
|
+
"native-gcloud-command-subsets",
|
|
401
|
+
"same-tenant-reference-validation",
|
|
402
|
+
"referenced-resource-delete-conflict"
|
|
387
403
|
],
|
|
388
404
|
"limitations": [
|
|
389
405
|
"configuration-only",
|
|
390
|
-
"no-VM-SQL-network-DNS-build-registry-inference-runtime",
|
|
406
|
+
"no-VM-SQL-network-DNS-build-registry-inference-Kubernetes-Redis-LB-runtime",
|
|
407
|
+
"configuration-envelope-not-official-resource-JSON",
|
|
408
|
+
"LB-global-subset-regional-flags-rejected",
|
|
409
|
+
"no-complete-reference-use-permission-model",
|
|
391
410
|
"no-new-IaC-resources"
|
|
392
411
|
]
|
|
393
412
|
},
|
|
@@ -492,8 +511,11 @@
|
|
|
492
511
|
},
|
|
493
512
|
"gcp-memorystore": {
|
|
494
513
|
"name": "Memorystore",
|
|
495
|
-
"status": "
|
|
496
|
-
"
|
|
514
|
+
"status": "mock-only",
|
|
515
|
+
"backend": "api",
|
|
516
|
+
"mode": "mock-only",
|
|
517
|
+
"operations": ["typed-redis-configuration-crud", "network-reference-validation"],
|
|
518
|
+
"reason": "僅保存 Memorystore 配置;不建立 Redis process、endpoint、replication 或 persistence。工作區既有 Redis 引擎是獨立服務。"
|
|
497
519
|
},
|
|
498
520
|
"aws-elasticache": {
|
|
499
521
|
"name": "ElastiCache",
|
|
@@ -522,8 +544,19 @@
|
|
|
522
544
|
},
|
|
523
545
|
"gcp-gke": {
|
|
524
546
|
"name": "GKE",
|
|
525
|
-
"status": "
|
|
526
|
-
"
|
|
547
|
+
"status": "mock-only",
|
|
548
|
+
"backend": "api",
|
|
549
|
+
"mode": "mock-only",
|
|
550
|
+
"operations": ["typed-cluster-configuration-crud", "network-reference-validation"],
|
|
551
|
+
"reason": "僅保存叢集配置;無 Kubernetes API、nodes、kubectl、scheduler 或 workloads。"
|
|
552
|
+
},
|
|
553
|
+
"gcp-vpc": {
|
|
554
|
+
"name": "VPC / Firewall",
|
|
555
|
+
"status": "mock-only",
|
|
556
|
+
"backend": "api",
|
|
557
|
+
"mode": "mock-only",
|
|
558
|
+
"operations": ["network-subnet-connector-firewall-configuration-crud"],
|
|
559
|
+
"reason": "僅保存配置;規則不會過濾封包,沒有路由、位址配置或真實 VPC。"
|
|
527
560
|
},
|
|
528
561
|
"gcp-cloud-run-jobs": {
|
|
529
562
|
"name": "Cloud Run Jobs",
|
|
@@ -592,12 +625,15 @@
|
|
|
592
625
|
"gcp-cloud-logging-monitoring": {
|
|
593
626
|
"name": "Cloud Logging / Monitoring",
|
|
594
627
|
"status": "partial",
|
|
595
|
-
"reason": "
|
|
628
|
+
"reason": "Cloud Logging 已接持久化、工作負載 stdout/stderr、CLI 與 UI;請求 metrics/traces 為明示 FUFU 擴充。未實作官方 Monitoring/Cloud Trace API、alerts 或 sinks。"
|
|
596
629
|
},
|
|
597
630
|
"gcp-load-balancing-nat": {
|
|
598
631
|
"name": "Cloud Load Balancing / NAT",
|
|
599
|
-
"status": "
|
|
600
|
-
"
|
|
632
|
+
"status": "mock-only",
|
|
633
|
+
"backend": "api",
|
|
634
|
+
"mode": "mock-only",
|
|
635
|
+
"operations": ["global-health-check-backend-url-map-http-proxy-forwarding-rule-configuration-crud"],
|
|
636
|
+
"reason": "LB 僅保存 global 配置相依鏈;沒有探測、IP、listener、TLS 或轉送。NAT 尚未支援。"
|
|
601
637
|
},
|
|
602
638
|
"aws-ec2": {
|
|
603
639
|
"name": "EC2",
|
|
@@ -664,8 +700,8 @@
|
|
|
664
700
|
},
|
|
665
701
|
"aws-cloudwatch": {
|
|
666
702
|
"name": "CloudWatch",
|
|
667
|
-
"status": "
|
|
668
|
-
"reason": "
|
|
703
|
+
"status": "partial",
|
|
704
|
+
"reason": "CloudWatch Logs 明列 API 子集、Lambda stdout/stderr、CLI tail、持久化與 retention 已接入;請求 metrics/traces 使用 FUFU 擴充,未實作官方 Metrics/X-Ray API 或 alarms。"
|
|
669
705
|
},
|
|
670
706
|
"aws-cloudfront": {
|
|
671
707
|
"name": "CloudFront",
|