fufu-cloud-cli 0.9.7__tar.gz → 0.10.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 (62) hide show
  1. {fufu_cloud_cli-0.9.7/src/fufu_cloud_cli.egg-info → fufu_cloud_cli-0.10.0}/PKG-INFO +58 -7
  2. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/README.md +57 -6
  3. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/pyproject.toml +1 -1
  4. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/__init__.py +1 -1
  5. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/api_backend.py +7 -0
  6. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/aws_errors.py +1 -1
  7. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/capabilities.json +73 -27
  8. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/cli.py +41 -8
  9. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/cloud_data.py +1 -1
  10. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/control.py +2 -2
  11. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/docker_backend.py +15 -3
  12. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/gcp_infra.py +42 -9
  13. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/gcs.py +95 -6
  14. fufu_cloud_cli-0.10.0/src/fufu_cloud_cli/infrastructure.py +283 -0
  15. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/lambda_adapter.py +17 -0
  16. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/object_transfer.py +11 -6
  17. fufu_cloud_cli-0.10.0/src/fufu_cloud_cli/observability.py +127 -0
  18. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/runtime_sdk.py +1 -1
  19. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/terraform_bridge/runner.py +15 -1
  20. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0/src/fufu_cloud_cli.egg-info}/PKG-INFO +58 -7
  21. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli.egg-info/SOURCES.txt +2 -0
  22. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/LICENSE +0 -0
  23. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/MANIFEST.in +0 -0
  24. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/setup.cfg +0 -0
  25. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/__main__.py +0 -0
  26. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/aws_lambda.py +0 -0
  27. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/aws_output.py +0 -0
  28. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/bigquery.py +0 -0
  29. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/capacity.py +0 -0
  30. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/cloudflare.py +0 -0
  31. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/errors.py +0 -0
  32. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/gcloud_environment.py +0 -0
  33. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/gcloud_output.py +0 -0
  34. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/gcp_sdk.py +0 -0
  35. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/http_runtime.py +0 -0
  36. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/iac_state.py +0 -0
  37. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/limits.py +0 -0
  38. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/manifest.py +0 -0
  39. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/public_access.py +0 -0
  40. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/pubsub.py +0 -0
  41. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/pulumi-endpoints.json +0 -0
  42. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/pulumi_bridge.py +0 -0
  43. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/remote_compute.py +0 -0
  44. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/runtime/functions/Dockerfile +0 -0
  45. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/runtime/ingress/Dockerfile +0 -0
  46. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/runtime/ingress/proxy.py +0 -0
  47. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/secrets.py +0 -0
  48. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/serverless.py +0 -0
  49. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/terraform_bridge/__init__.py +0 -0
  50. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/terraform_bridge/aws.py +0 -0
  51. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/terraform_bridge/aws_data.py +0 -0
  52. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/terraform_bridge/common.py +0 -0
  53. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/terraform_bridge/google.py +0 -0
  54. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/terraform_bridge/google_pubsub.py +0 -0
  55. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/terraform_bridge/remote_state.py +0 -0
  56. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/vendor/AWS-CLI-LICENSE.txt +0 -0
  57. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/vendor/__init__.py +0 -0
  58. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli/vendor/aws_text.py +0 -0
  59. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli.egg-info/dependency_links.txt +0 -0
  60. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli.egg-info/entry_points.txt +0 -0
  61. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.0}/src/fufu_cloud_cli.egg-info/requires.txt +0 -0
  62. {fufu_cloud_cli-0.9.7 → fufu_cloud_cli-0.10.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.9.7
3
+ Version: 0.10.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
@@ -27,6 +27,55 @@ 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
+ 0.10.0 新增 Compute Engine/EC2 VM 與 GKE/EKS 介面的實際運算命令。
31
+ 需要啟用相應 infrastructure backend 的 FUFU 服務端;VM 使用 KVM/KubeVirt,
32
+ 叢集使用獨立 Kubernetes API 與共用節點,均不是官方雲端資源。
33
+ 支援 VM 建立、SSH 指令、停止/啟動與刪除,以及叢集建立、查詢、
34
+ get-credentials/update-kubeconfig、waiter 與刪除。
35
+ 取得憑證後可使用標準 kubectl 部署 Deployment、Service、Job 及受管 GPU 容器。
36
+ 工作站需另安裝 kubectl;配套 Cloud Shell 已提供。
37
+
38
+ VM 尚無公開 IP、複寫儲存、live migration 或自助 GPU passthrough;
39
+ 叢集尚無 workload PVC、公開 LoadBalancer、互動式 exec/attach/port-forward。
40
+ 這批新 VM/cluster API 尚未支援官方 Terraform/OpenTofu/Pulumi provider。
41
+ 原有 firewall、global LB、Redis 等 typed configuration 仍明示 `MOCK ONLY`,
42
+ 不代表已建立相應網路或 Redis 資料面。
43
+
44
+ ```bash
45
+ gcloud-fufu compute instances create demo-vm --image-family=debian-12
46
+ gcloud-fufu compute ssh demo-vm --command='uname -s; nproc'
47
+ gcloud-fufu container clusters create demo-gke
48
+ gcloud-fufu container clusters get-credentials demo-gke
49
+ kubectl get pods -A
50
+ aws-fufu eks create-cluster --name demo-eks
51
+ aws-fufu eks wait cluster-active --name demo-eks
52
+ aws-fufu eks update-kubeconfig --name demo-eks
53
+ ```
54
+
55
+ 這些操作需要 FUFU credential;不使用官方雲端金鑰。資源以租戶隔離,配額不足
56
+ 會明確回覆 RESOURCE_EXHAUSTED。測試後請刪除所建 VM/cluster 以釋放資源。
57
+
58
+ 0.9.8 提供 Cloud Run/Functions 的原生形式日誌查詢、CloudWatch Logs
59
+ tail/follow,以及 `fufu observability` 的日誌、請求指標與追蹤查詢。
60
+ 公開套件包含完整的日誌指令模組;重複建立 CloudWatch log group 的
61
+ `ResourceAlreadyExistsException` 會保留至 CLI 與 boto3 例外處理。
62
+ 配套服務端持久化收集 Cloud Run、Functions Gen 1/Gen 2、Lambda 的
63
+ stdout/stderr 與例外堆疊。支援租戶隔離、IAM、敏感值遮罩、查詢分頁、
64
+ request ID/trace 關聯及重啟恢復。GCP log names 清單、dry run、欄位存在
65
+ 查詢,以及 AWS 10,000 筆/1 MiB 寫入批次均有契約驗收。
66
+
67
+ ```bash
68
+ gcloud-fufu run services logs read hello --limit=100 --log-filter='severity>=ERROR'
69
+ gcloud-fufu functions logs read hello --gen2 --limit=100
70
+ aws-fufu logs tail /aws/lambda/hello --since 1h --follow --fufu-duration 300
71
+ fufu observability status --provider gcp
72
+ fufu observability logs --provider aws --request-id REQUEST_ID --since 1h
73
+ ```
74
+
75
+ 請求 metrics/traces 是明示的 FUFU 擴充,沒有實作完整 Cloud Monitoring、
76
+ CloudWatch Metrics、Trace 或 X-Ray。Log Router sinks、Logs Insights、
77
+ subscriptions、alerts 及完整 OpenTelemetry collector 尚未支援。
78
+
30
79
  0.9.7 補齊 Lambda 五種 `wait` 指令、設定/程式碼更新的 `--revision-id`,
31
80
  以及常用設定的 `--cli-input-json` 和 `Variables={KEY=value}` shorthand。
32
81
  過期 RevisionId 回 `PreconditionFailedException`,保留原部署;CLI 仍等待
@@ -47,9 +96,10 @@ insertAll/SELECT 與持久化日誌。`fufu_cloud_cli.gcp_sdk` 提供官方 Py
47
96
  BigQuery、Cloud Tasks、Logging SDK 的明確 REST factory;可用 `[gcp]` extra
48
97
  安裝已驗收相依。需要對應的新後端,並非透明 ADC 或所有 Google SDK 替換。
49
98
 
50
- `fufu infrastructure` 提供 14 類 typed configuration mock。Compute Engine、
51
- Cloud SQL、App Engine、DNS、VPC、Artifact Registry、Cloud Build、Gemini、
52
- VM Manager、Eventarc 等設定不會啟動 VM/SQL/registry/網路/推論。
99
+ `fufu infrastructure` 另保留 typed configuration mock 目錄,包含 Cloud SQL、
100
+ App Engine、DNS、VPC、Artifact Registry、Cloud Build、Gemini、VM Manager、
101
+ Eventarc 等。這個目錄只保存配置,不啟動 SQL/registry/網路/推論;
102
+ 實際 VM/Kubernetes 請使用上述 compute/ec2/container/eks 指令。
53
103
  UI 顯示僅設定,CLI stderr 保留 MOCK ONLY,不因輸出投影而隱藏。
54
104
  新服務尚未接入 Terraform/OpenTofu/Pulumi;單機背景投遞不是雲端 HA。
55
105
 
@@ -62,8 +112,9 @@ fufu infrastructure catalog
62
112
  ```
63
113
 
64
114
  BigQuery 是 tenant emulator 的 GoogleSQL 子集,無 DML/DDL、load/export、
65
- 外部表與效能/費用等價。Logging 保留每租戶最新 10,000 筆,不自動收集所有
66
- 容器 stdout。Tasks 每 queue 每秒最多 1 次、每租戶 2 個同時投遞;耗盡 task
115
+ 外部表與效能/費用等價。配套新版 Logging 預設每租戶 2 GiB;GCP 保留
116
+ 30 天,AWS 依 log group retention 設定,未設定時不自動到期。只收集已
117
+ 驗證歸屬的 FUFU 工作負載容器。Tasks 每 queue 每秒最多 1 次、每租戶 2 個同時投遞;耗盡 task
67
118
  留待檢查,與官方刪除策略不同。新的 dependency flow 不代表完整應用都能遷移。
68
119
 
69
120
  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 格式可讀舊資料,回滾須還原升級前備份。
@@ -80,7 +131,7 @@ Python 預設識別而遇到 Cloudflare HTTP 403/1010。識別標頭不取代
80
131
  ## Cloudflare 前端部署與 R2
81
132
 
82
133
  ```bash
83
- pip install 'fufu-cloud-cli==0.9.7'
134
+ pip install 'fufu-cloud-cli==0.10.0'
84
135
  export FUFU_CLOUDFLARE_CREDENTIAL_FILE=/run/secrets/fufu-cloudflare.json
85
136
  wrangler-fufu capabilities
86
137
  wrangler-fufu pages project create frontend --production-branch main
@@ -4,6 +4,55 @@
4
4
  `gcloud-fufu`、`az-fufu`、`wrangler-fufu`、`bq-fufu`。這是 AWS、Google Cloud、Azure 與 Cloudflare 的**明確子集**,
5
5
  只供 FUFU mock 環境使用;不會把部署轉送到正式雲端。
6
6
 
7
+ 0.10.0 新增 Compute Engine/EC2 VM 與 GKE/EKS 介面的實際運算命令。
8
+ 需要啟用相應 infrastructure backend 的 FUFU 服務端;VM 使用 KVM/KubeVirt,
9
+ 叢集使用獨立 Kubernetes API 與共用節點,均不是官方雲端資源。
10
+ 支援 VM 建立、SSH 指令、停止/啟動與刪除,以及叢集建立、查詢、
11
+ get-credentials/update-kubeconfig、waiter 與刪除。
12
+ 取得憑證後可使用標準 kubectl 部署 Deployment、Service、Job 及受管 GPU 容器。
13
+ 工作站需另安裝 kubectl;配套 Cloud Shell 已提供。
14
+
15
+ VM 尚無公開 IP、複寫儲存、live migration 或自助 GPU passthrough;
16
+ 叢集尚無 workload PVC、公開 LoadBalancer、互動式 exec/attach/port-forward。
17
+ 這批新 VM/cluster API 尚未支援官方 Terraform/OpenTofu/Pulumi provider。
18
+ 原有 firewall、global LB、Redis 等 typed configuration 仍明示 `MOCK ONLY`,
19
+ 不代表已建立相應網路或 Redis 資料面。
20
+
21
+ ```bash
22
+ gcloud-fufu compute instances create demo-vm --image-family=debian-12
23
+ gcloud-fufu compute ssh demo-vm --command='uname -s; nproc'
24
+ gcloud-fufu container clusters create demo-gke
25
+ gcloud-fufu container clusters get-credentials demo-gke
26
+ kubectl get pods -A
27
+ aws-fufu eks create-cluster --name demo-eks
28
+ aws-fufu eks wait cluster-active --name demo-eks
29
+ aws-fufu eks update-kubeconfig --name demo-eks
30
+ ```
31
+
32
+ 這些操作需要 FUFU credential;不使用官方雲端金鑰。資源以租戶隔離,配額不足
33
+ 會明確回覆 RESOURCE_EXHAUSTED。測試後請刪除所建 VM/cluster 以釋放資源。
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
+
7
56
  0.9.7 補齊 Lambda 五種 `wait` 指令、設定/程式碼更新的 `--revision-id`,
8
57
  以及常用設定的 `--cli-input-json` 和 `Variables={KEY=value}` shorthand。
9
58
  過期 RevisionId 回 `PreconditionFailedException`,保留原部署;CLI 仍等待
@@ -24,9 +73,10 @@ insertAll/SELECT 與持久化日誌。`fufu_cloud_cli.gcp_sdk` 提供官方 Py
24
73
  BigQuery、Cloud Tasks、Logging SDK 的明確 REST factory;可用 `[gcp]` extra
25
74
  安裝已驗收相依。需要對應的新後端,並非透明 ADC 或所有 Google SDK 替換。
26
75
 
27
- `fufu infrastructure` 提供 14 類 typed configuration mock。Compute Engine、
28
- Cloud SQL、App Engine、DNS、VPC、Artifact Registry、Cloud Build、Gemini、
29
- VM Manager、Eventarc 等設定不會啟動 VM/SQL/registry/網路/推論。
76
+ `fufu infrastructure` 另保留 typed configuration mock 目錄,包含 Cloud SQL、
77
+ App Engine、DNS、VPC、Artifact Registry、Cloud Build、Gemini、VM Manager、
78
+ Eventarc 等。這個目錄只保存配置,不啟動 SQL/registry/網路/推論;
79
+ 實際 VM/Kubernetes 請使用上述 compute/ec2/container/eks 指令。
30
80
  UI 顯示僅設定,CLI stderr 保留 MOCK ONLY,不因輸出投影而隱藏。
31
81
  新服務尚未接入 Terraform/OpenTofu/Pulumi;單機背景投遞不是雲端 HA。
32
82
 
@@ -39,8 +89,9 @@ fufu infrastructure catalog
39
89
  ```
40
90
 
41
91
  BigQuery 是 tenant emulator 的 GoogleSQL 子集,無 DML/DDL、load/export、
42
- 外部表與效能/費用等價。Logging 保留每租戶最新 10,000 筆,不自動收集所有
43
- 容器 stdout。Tasks 每 queue 每秒最多 1 次、每租戶 2 個同時投遞;耗盡 task
92
+ 外部表與效能/費用等價。配套新版 Logging 預設每租戶 2 GiB;GCP 保留
93
+ 30 天,AWS 依 log group retention 設定,未設定時不自動到期。只收集已
94
+ 驗證歸屬的 FUFU 工作負載容器。Tasks 每 queue 每秒最多 1 次、每租戶 2 個同時投遞;耗盡 task
44
95
  留待檢查,與官方刪除策略不同。新的 dependency flow 不代表完整應用都能遷移。
45
96
 
46
97
  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 格式可讀舊資料,回滾須還原升級前備份。
@@ -57,7 +108,7 @@ Python 預設識別而遇到 Cloudflare HTTP 403/1010。識別標頭不取代
57
108
  ## Cloudflare 前端部署與 R2
58
109
 
59
110
  ```bash
60
- pip install 'fufu-cloud-cli==0.9.7'
111
+ pip install 'fufu-cloud-cli==0.10.0'
61
112
  export FUFU_CLOUDFLARE_CREDENTIAL_FILE=/run/secrets/fufu-cloudflare.json
62
113
  wrangler-fufu capabilities
63
114
  wrangler-fufu pages project create frontend --production-branch main
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fufu-cloud-cli"
7
- version = "0.9.7"
7
+ version = "0.10.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.9.7"
3
+ __version__ = "0.10.0"
@@ -184,6 +184,13 @@ class ApiClient:
184
184
  return json.loads(raw)
185
185
  except HTTPError as exc:
186
186
  status = exc.code
187
+ if '/infrastructure/' in path and status == 429:
188
+ try:
189
+ value = json.loads(exc.read(4096))
190
+ if isinstance(value, dict) and value.get('code') == 'RESOURCE_EXHAUSTED':
191
+ raise FufuError('RESOURCE_EXHAUSTED', 'FUFU 租戶資源配額或主機容量已滿;請刪除不需要的資源,或調整平台資源配額後再重試。') from None
192
+ except (ValueError, OSError): pass
193
+ finally: exc.close()
187
194
  if self.provider == 'aws' and '/compute/' in path and status in {400, 412}:
188
195
  try:
189
196
  value = json.loads(exc.read(4096))
@@ -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
@@ -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": ["no-automatic-stdout-collection", "simple-text-filters-only", "no-Insights-subscriptions-or-KMS", "no-guaranteed-retention-deletion", "service-page-tokens-no-CLI-auto-pagination"]},
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-logName-resourceType",
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
- "last-10000-entries-per-tenant",
356
- "single-writer-128MiB-state-capacity",
357
- "no-automatic-container-stdout",
358
- "no-sinks-retention-metrics-alerts",
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": "partial",
496
- "reason": "工作區有 Redis 引擎;不提供 Memorystore 控制面。"
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",
@@ -512,18 +534,31 @@
512
534
  },
513
535
  "gcp-compute-engine": {
514
536
  "name": "Compute Engine",
515
- "status": "mock-only",
537
+ "status": "implemented",
516
538
  "backend": "api",
517
- "mode": "mock-only",
539
+ "mode": "simplified",
518
540
  "operations": [
519
- "typed-instance-configuration-crud"
541
+ "create-list-describe-delete", "stop-start", "guest-ssh-command"
520
542
  ],
521
- "reason": "僅保存設定;無 VM、disk、SSH、guest OS 或 hypervisor。"
543
+ "limitations": ["requires-FUFU-infrastructure-backend", "Debian-12-only", "private-network", "single-node-disk", "no-self-service-GPU-passthrough", "no-official-IaC-provider"],
544
+ "reason": "實際 KVM/KubeVirt VM、持久磁碟與 SSH 指令;原生風格指令子集,無官方雲端資源。"
522
545
  },
523
546
  "gcp-gke": {
524
547
  "name": "GKE",
525
- "status": "unsupported",
526
- "reason": "尚未實作可供 CLI 自動部署與驗證的服務契約。"
548
+ "status": "implemented",
549
+ "backend": "api",
550
+ "mode": "simplified",
551
+ "operations": ["create-list-describe-delete", "get-credentials", "kubectl-workloads", "GPU-container-jobs"],
552
+ "limitations": ["requires-FUFU-infrastructure-backend", "shared-workers", "single-control-plane-replica", "no-workload-PVC", "no-public-LoadBalancer", "no-interactive-exec-or-portforward", "no-official-IaC-provider"],
553
+ "reason": "獨立 vCluster Kubernetes API 與控制面資料;共用受管節點,支援標準 kubectl 工作負載子集。"
554
+ },
555
+ "gcp-vpc": {
556
+ "name": "VPC / Firewall",
557
+ "status": "mock-only",
558
+ "backend": "api",
559
+ "mode": "mock-only",
560
+ "operations": ["network-subnet-connector-firewall-configuration-crud"],
561
+ "reason": "僅保存配置;規則不會過濾封包,沒有路由、位址配置或真實 VPC。"
527
562
  },
528
563
  "gcp-cloud-run-jobs": {
529
564
  "name": "Cloud Run Jobs",
@@ -592,17 +627,24 @@
592
627
  "gcp-cloud-logging-monitoring": {
593
628
  "name": "Cloud Logging / Monitoring",
594
629
  "status": "partial",
595
- "reason": "gcp-logging 已提供可執行日誌子集;Monitoring、alerts、sinks 與完整 stdout 收集未支援。"
630
+ "reason": "Cloud Logging 已接持久化、工作負載 stdout/stderr、CLI 與 UI;請求 metrics/traces 為明示 FUFU 擴充。未實作官方 Monitoring/Cloud Trace API、alerts 或 sinks。"
596
631
  },
597
632
  "gcp-load-balancing-nat": {
598
633
  "name": "Cloud Load Balancing / NAT",
599
- "status": "unsupported",
600
- "reason": "尚未實作可供 CLI 自動部署與驗證的服務契約。"
634
+ "status": "mock-only",
635
+ "backend": "api",
636
+ "mode": "mock-only",
637
+ "operations": ["global-health-check-backend-url-map-http-proxy-forwarding-rule-configuration-crud"],
638
+ "reason": "LB 僅保存 global 配置相依鏈;沒有探測、IP、listener、TLS 或轉送。NAT 尚未支援。"
601
639
  },
602
640
  "aws-ec2": {
603
641
  "name": "EC2",
604
- "status": "mock-only",
605
- "reason": "明示 mock 目錄;VM 未執行,不能列為成功部署。"
642
+ "status": "implemented",
643
+ "backend": "api",
644
+ "mode": "simplified",
645
+ "operations": ["run-describe-terminate-instances", "stop-start", "instance-waiters"],
646
+ "limitations": ["requires-FUFU-infrastructure-backend", "Debian-12-only", "private-network", "single-node-disk", "no-self-service-GPU-passthrough", "no-official-IaC-provider"],
647
+ "reason": "實際 KVM/KubeVirt VM;支援生命週期與 scoped instance ID,無官方 EC2 網路或完整 API。"
606
648
  },
607
649
  "aws-ecs": {
608
650
  "name": "ECS / Fargate",
@@ -611,8 +653,12 @@
611
653
  },
612
654
  "aws-eks": {
613
655
  "name": "EKS",
614
- "status": "unsupported",
615
- "reason": "尚未實作可供 CLI 自動部署與驗證的服務契約。"
656
+ "status": "implemented",
657
+ "backend": "api",
658
+ "mode": "simplified",
659
+ "operations": ["create-list-describe-delete", "update-kubeconfig", "cluster-waiters", "kubectl-workloads", "GPU-container-jobs"],
660
+ "limitations": ["requires-FUFU-infrastructure-backend", "shared-workers", "single-control-plane-replica", "no-workload-PVC", "no-public-LoadBalancer", "no-interactive-exec-or-portforward", "no-official-IaC-provider"],
661
+ "reason": "獨立 vCluster Kubernetes API 與控制面資料;FUFU 短效認證,不是 AWS 原生 IAM 或託管控制面。"
616
662
  },
617
663
  "aws-app-runner": {
618
664
  "name": "App Runner",
@@ -664,8 +710,8 @@
664
710
  },
665
711
  "aws-cloudwatch": {
666
712
  "name": "CloudWatch",
667
- "status": "unsupported",
668
- "reason": "尚未實作可供 CLI 自動部署與驗證的服務契約。"
713
+ "status": "partial",
714
+ "reason": "CloudWatch Logs 明列 API 子集、Lambda stdout/stderr、CLI tail、持久化與 retention 已接入;請求 metrics/traces 使用 FUFU 擴充,未實作官方 Metrics/X-Ray API 或 alarms。"
669
715
  },
670
716
  "aws-cloudfront": {
671
717
  "name": "CloudFront",
@@ -104,6 +104,7 @@ def fufu_parser():
104
104
  p = Parser(prog="fufu", description="FUFU 租戶認證、遠端部署與本機 Docker;所有輸出均為 FUFU 子集契約。")
105
105
  p.add_argument("--version", action="version", version=__version__)
106
106
  sub = p.add_subparsers(dest="action", required=True)
107
+ sub.add_parser('observability', help='FUFU 擴充:日誌、請求指標、trace 與收集狀態;使用 observability --help')
107
108
  auth = sub.add_parser("auth", help="驗證 CLI credential,顯示目前租戶與 scope").add_subparsers(dest="auth_action", required=True)
108
109
  identity = auth.add_parser("whoami")
109
110
  identity.add_argument("--provider", choices=["aws", "gcp", "azure", "cloudflare"])
@@ -495,7 +496,7 @@ def entry(provider, argv=None):
495
496
  p.add_argument('--quiet', '-q', action='store_true', help='非互動執行')
496
497
  p.add_argument('--version', action='store_true', help='CLI 版本')
497
498
  groups = p.add_subparsers(title='服務')
498
- for group in ['run', 'functions', 'iam', 'projects', 'pubsub', 'secrets', 'storage', 'auth', 'scheduler', 'tasks', 'logging', 'compute', 'sql', 'artifacts', 'dns', 'eventarc']: groups.add_parser(group)
499
+ for group in ['run', 'functions', 'iam', 'projects', 'pubsub', 'secrets', 'storage', 'auth', 'scheduler', 'tasks', 'logging', 'compute', 'sql', 'artifacts', 'dns', 'eventarc', 'container', 'redis']: groups.add_parser(group)
499
500
  p.print_help(); return
500
501
  if provider == 'bq-fufu':
501
502
  from .bigquery import command
@@ -517,12 +518,20 @@ def entry(provider, argv=None):
517
518
  if provider == 'fufu' and argv[:1] == ['infrastructure']:
518
519
  from .gcp_infra import configuration_command
519
520
  emit(configuration_command(argv[1:], Parser)); return
521
+ if provider == 'fufu' and argv[:1] == ['kubernetes-token']:
522
+ from .infrastructure import token_command
523
+ emit(token_command(argv[1:], Parser)); return
520
524
  # Native global selectors are checked against the authenticated tenant scope.
521
525
  if provider in {'aws-fufu', 'gcloud-fufu'}:
522
- rest = []; i = 0
526
+ rest = []; i = 0; explicit_region = False
527
+ native_tail=provider=='aws-fufu' and any(argv[j:j+2]==['logs','tail'] for j in range(len(argv)))
528
+ native_s3=provider=='aws-fufu' and 's3' in argv
529
+ native_storage_ls=provider=='gcloud-fufu' and any(argv[j:j+2]==['storage','ls'] for j in range(len(argv)))
530
+ native_function_logs=provider=='gcloud-fufu' and any(argv[j:j+3]==['functions','logs','read'] for j in range(len(argv)))
523
531
  while i < len(argv):
524
532
  flag, _, inline = argv[i].partition('=')
525
- if flag in {'--region', '--project', '--output', '--format', '--impersonate-service-account', '--filter', '--sort-by', '--query'} or flag == '--limit' and provider == 'gcloud-fufu' and 'list' in argv:
533
+ native_option=flag=='--format' and (native_tail or native_storage_ls) or flag=='--filter' and native_function_logs
534
+ if not native_option and (flag in {'--region', '--project', '--output', '--format', '--impersonate-service-account', '--filter', '--sort-by', '--query'} or flag == '--limit' and provider == 'gcloud-fufu' and 'list' in argv):
526
535
  value = inline if '=' in argv[i] else (argv[i + 1] if i + 1 < len(argv) else '')
527
536
  i += 1 if '=' in argv[i] else 2
528
537
  require(bool(value) and not value.startswith('--'), 'UNSUPPORTED_ARGUMENT', '全域旗標缺少值。')
@@ -531,7 +540,7 @@ def entry(provider, argv=None):
531
540
  from .aws_output import expression
532
541
  aws_query = expression(value)
533
542
  elif flag == '--output' and provider == 'aws-fufu':
534
- require(value in {'json', 'text', 'yaml', 'off'}, 'UNSUPPORTED_FORMAT', 'AWS 輸出支援 json、text、yaml、off。')
543
+ require(value in ({'json', 'text', 'yaml', 'table', 'yaml-stream', 'off'} if native_s3 else {'json', 'text', 'yaml', 'off'}), 'UNSUPPORTED_FORMAT', 'AWS 輸出支援 json、text、yaml、off。')
535
544
  aws_format = value
536
545
  elif flag == '--impersonate-service-account':
537
546
  require(provider == 'gcloud-fufu', 'UNSUPPORTED_ARGUMENT', '此旗標只適用於 gcloud-fufu。')
@@ -550,12 +559,16 @@ def entry(provider, argv=None):
550
559
  if flag == '--limit':
551
560
  require(value.isdigit(), 'UNSUPPORTED_ARGUMENT', 'limit 必須為非負整數。'); output_limit = int(value)
552
561
  elif flag in {'--output', '--format'}: require(value == 'json', 'UNSUPPORTED_ARGUMENT', '此入口目前輸出格式支援 json。')
553
- else: os.environ['FUFU_SELECTED_REGION' if flag == '--region' else 'FUFU_SELECTED_PROJECT'] = value
554
- elif flag in {'--quiet', '-q', '--no-cli-pager'}:
562
+ else:
563
+ if flag == '--region': explicit_region = True
564
+ os.environ['FUFU_SELECTED_REGION' if flag == '--region' else 'FUFU_SELECTED_PROJECT'] = value
565
+ elif flag in {'--quiet', '-q', '--no-cli-pager'} and not (native_s3 and flag == '--quiet'):
555
566
  require('=' not in argv[i], 'UNSUPPORTED_ARGUMENT', '布林旗標不接受額外值。'); i += 1
556
567
  else: rest.append(argv[i]); i += 1
557
568
  argv = rest
558
569
  if provider == 'gcloud-fufu':
570
+ if explicit_region and argv[:2] in [['compute', name] for name in ['health-checks', 'backend-services', 'url-maps', 'target-http-proxies', 'forwarding-rules']]:
571
+ require(False, 'CAPABILITY_NOT_SUPPORTED', '目前 LB 配置僅支援 global 子集;不能以 --region 建立或查詢區域資源。')
559
572
  if output_filter or output_sort or output_limit is not None:
560
573
  list_commands = [('run', 'services', 'list'), ('functions', 'list'), ('iam', 'service-accounts', 'list'),
561
574
  ('pubsub', 'topics', 'list'), ('pubsub', 'subscriptions', 'list'), ('secrets', 'list'),
@@ -564,11 +577,19 @@ def entry(provider, argv=None):
564
577
  ('compute', 'instances', 'list'), ('sql', 'instances', 'list'), ('sql', 'databases', 'list'),
565
578
  ('artifacts', 'repositories', 'list'), ('dns', 'managed-zones', 'list'), ('eventarc', 'triggers', 'list'),
566
579
  ('compute', 'networks', 'list'), ('compute', 'networks', 'subnets', 'list'), ('compute', 'networks', 'vpc-access', 'connectors', 'list')]
580
+ from .gcp_infra import MOCK_GROUPS
581
+ list_commands.extend((*command, 'list') for command in MOCK_GROUPS)
567
582
  require(any(tuple(argv[:len(command)]) == command for command in list_commands), 'UNSUPPORTED_ARGUMENT', '清單選取旗標只適用於資源 list;未執行操作。')
568
583
  if output_format and argv[:2] == ['functions', 'call']:
569
584
  require(output_format == 'json', 'UNSUPPORTED_FORMAT', 'Functions call 目前只支援 JSON 回應。')
570
585
  from . import public_access
571
- if provider == 'gcloud-fufu' and argv and argv[0] in {'scheduler', 'tasks', 'logging', 'compute', 'sql', 'artifacts', 'dns', 'eventarc'}:
586
+ if provider == 'gcloud-fufu' and argv[:2] in [['compute', 'instances'], ['compute', 'ssh'], ['container', 'clusters']]:
587
+ from .infrastructure import gcp_command
588
+ emit(gcp_command(argv, Parser)); return
589
+ if provider == 'aws-fufu' and argv[:1] in [['ec2'], ['eks']]:
590
+ from .infrastructure import aws_command as infrastructure_aws_command
591
+ emit(infrastructure_aws_command(argv, Parser)); return
592
+ if provider == 'gcloud-fufu' and argv and argv[0] in {'scheduler', 'tasks', 'logging', 'compute', 'sql', 'artifacts', 'dns', 'eventarc', 'container', 'redis'}:
572
593
  from .gcp_infra import command
573
594
  emit(command(argv, Parser)); return
574
595
  if provider == 'aws-fufu' and argv[:1] == ['secretsmanager'] or provider == 'gcloud-fufu' and argv[:1] == ['secrets']:
@@ -588,6 +609,15 @@ def entry(provider, argv=None):
588
609
  if provider == 'aws-fufu' and argv and argv[0] == 'serverless':
589
610
  from .serverless import command
590
611
  emit(command(argv[1:], Parser)); return
612
+ if provider=='fufu' and argv and argv[0]=='observability':
613
+ from .observability import command
614
+ emit(command(argv[1:],Parser));return
615
+ if provider=='aws-fufu' and argv[:2]==['logs','tail']:
616
+ from .observability import aws_tail
617
+ aws_tail(argv[2:],Parser);return
618
+ if provider=='gcloud-fufu' and (argv[:4]==['run','services','logs','read'] or argv[:3]==['functions','logs','read']):
619
+ from .observability import service_logs
620
+ emit(service_logs(argv,Parser));return
591
621
  if provider == 'fufu' and argv and argv[0] in {'gateway', 'events', 'mock'}:
592
622
  from .control import fufu_control
593
623
  emit(fufu_control(argv, Parser)); return
@@ -609,7 +639,10 @@ def entry(provider, argv=None):
609
639
  if provider == 'az-fufu' and argv and argv[0] == 'functionapp':
610
640
  raise FufuError('CAPABILITY_NOT_SUPPORTED', 'Azure Functions runtime 尚未實作。', 2)
611
641
  remote = {"aws-fufu": "aws", "gcloud-fufu": "gcp", "az-fufu": "azure"}
612
- if provider == 'aws-fufu' and argv and argv[0] in {'s3', 'sqs', 'ssm'}:
642
+ if provider == 'aws-fufu' and argv[:1] == ['s3']:
643
+ from .s3_highlevel import command
644
+ command(argv[1:], Parser); return
645
+ if provider == 'aws-fufu' and argv and argv[0] in {'sqs', 'ssm'}:
613
646
  from .cloud_data import aws_data_command
614
647
  emit(aws_data_command(argv, Parser))
615
648
  return
@@ -28,7 +28,7 @@ def s3_path(value):
28
28
  p = urlsplit(value)
29
29
  require(p.scheme == 's3' and p.netloc and not p.query and not p.fragment and not p.username and not p.password,
30
30
  'INVALID_RESOURCE', '請使用 s3://bucket 或 s3://bucket/key。')
31
- return '/s3/buckets/' + segment(p.netloc), p.path.lstrip('/')
31
+ return '/s3/buckets/' + segment(p.netloc), p.path.removeprefix('/')
32
32
 
33
33
 
34
34
  def aws_data_command(argv, Parser):
@@ -27,7 +27,7 @@ AWS_OPERATIONS = {
27
27
  'BatchGetItem': 'RequestItems', 'BatchWriteItem': 'RequestItems',
28
28
  },
29
29
  's3api': {
30
- 'CreateBucket': 'Bucket CreateBucketConfiguration', 'DeleteBucket': 'Bucket', 'ListBuckets': '', 'HeadBucket': 'Bucket',
30
+ 'CreateBucket': 'Bucket CreateBucketConfiguration', 'DeleteBucket': 'Bucket', 'ListBuckets': '', 'HeadBucket': 'Bucket', 'GetBucketLocation': 'Bucket',
31
31
  'ListObjectsV2': 'Bucket Prefix MaxKeys ContinuationToken Delimiter StartAfter EncodingType', 'PutObject': 'Bucket Key Body ContentType Metadata',
32
32
  'GetObject': 'Bucket Key ChecksumMode Range IfMatch IfNoneMatch', 'HeadObject': 'Bucket Key ChecksumMode IfMatch IfNoneMatch', 'DeleteObject': 'Bucket Key',
33
33
  },
@@ -134,7 +134,7 @@ def command(argv, Parser):
134
134
  params.setdefault('Type', 'String')
135
135
  if a.service == 'sqs' and 'QueueUrl' in params:
136
136
  require(isinstance(params['QueueUrl'], str) and re.fullmatch(r'(?:fufu://sqs/|https://sqs\.[a-z0-9-]+\.fufu\.invalid/[0-9]{12}/)[A-Za-z0-9_-]+(?:\.fifo)?', params['QueueUrl']) and len(params['QueueUrl'].rsplit('/', 1)[-1]) <= 80, 'INVALID_RESOURCE', 'QueueUrl 必須是 FUFU 租戶資源識別。')
137
- with ApiClient('aws', read_only=bool(re.match(r'^(Get|List|Describe|Query|Scan|BatchGet|Simulate|Head)', a.native_operation))) as api:
137
+ with ApiClient('aws', read_only=bool(re.match(r'^(Get|List|Describe|Query|Scan|BatchGet|Simulate|Head|FilterLogEvents)', a.native_operation))) as api:
138
138
  if a.service == 's3api' and a.native_operation == 'PutObject' and isinstance(params.get('Body'), Path):
139
139
  from .object_transfer import upload_file
140
140
  return upload_file(api, params['Body'], params.get('Bucket'), params.get('Key'), content_type=params.get('ContentType'), metadata=params.get('Metadata'))