fufu-cloud-cli 0.9.4__tar.gz → 0.9.6__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 (59) hide show
  1. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/PKG-INFO +49 -12
  2. fufu_cloud_cli-0.9.4/src/fufu_cloud_cli.egg-info/PKG-INFO → fufu_cloud_cli-0.9.6/README.md +43 -29
  3. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/pyproject.toml +5 -3
  4. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/__init__.py +1 -1
  5. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/__main__.py +1 -1
  6. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/api_backend.py +8 -0
  7. fufu_cloud_cli-0.9.6/src/fufu_cloud_cli/aws_errors.py +6 -0
  8. fufu_cloud_cli-0.9.6/src/fufu_cloud_cli/aws_output.py +65 -0
  9. fufu_cloud_cli-0.9.6/src/fufu_cloud_cli/bigquery.py +92 -0
  10. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/capabilities.json +181 -24
  11. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/cli.py +86 -14
  12. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/control.py +37 -12
  13. fufu_cloud_cli-0.9.6/src/fufu_cloud_cli/gcp_infra.py +222 -0
  14. fufu_cloud_cli-0.9.6/src/fufu_cloud_cli/gcp_sdk.py +60 -0
  15. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/remote_compute.py +6 -2
  16. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/runtime_sdk.py +44 -12
  17. fufu_cloud_cli-0.9.6/src/fufu_cloud_cli/vendor/AWS-CLI-LICENSE.txt +12 -0
  18. fufu_cloud_cli-0.9.6/src/fufu_cloud_cli/vendor/__init__.py +1 -0
  19. fufu_cloud_cli-0.9.6/src/fufu_cloud_cli/vendor/aws_text.py +117 -0
  20. fufu_cloud_cli-0.9.4/README.md → fufu_cloud_cli-0.9.6/src/fufu_cloud_cli.egg-info/PKG-INFO +66 -11
  21. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli.egg-info/SOURCES.txt +9 -1
  22. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli.egg-info/entry_points.txt +1 -0
  23. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli.egg-info/requires.txt +6 -0
  24. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/LICENSE +0 -0
  25. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/MANIFEST.in +0 -0
  26. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/setup.cfg +0 -0
  27. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/capacity.py +0 -0
  28. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/cloud_data.py +0 -0
  29. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/cloudflare.py +0 -0
  30. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/docker_backend.py +0 -0
  31. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/errors.py +0 -0
  32. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/gcloud_environment.py +0 -0
  33. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/gcloud_output.py +0 -0
  34. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/gcs.py +0 -0
  35. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/http_runtime.py +0 -0
  36. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/iac_state.py +0 -0
  37. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/lambda_adapter.py +0 -0
  38. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/limits.py +0 -0
  39. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/manifest.py +0 -0
  40. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/object_transfer.py +0 -0
  41. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/public_access.py +0 -0
  42. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/pubsub.py +0 -0
  43. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/pulumi-endpoints.json +0 -0
  44. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/pulumi_bridge.py +0 -0
  45. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/runtime/functions/Dockerfile +0 -0
  46. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/runtime/ingress/Dockerfile +0 -0
  47. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/runtime/ingress/proxy.py +0 -0
  48. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/secrets.py +0 -0
  49. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/serverless.py +0 -0
  50. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/terraform_bridge/__init__.py +0 -0
  51. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/terraform_bridge/aws.py +0 -0
  52. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/terraform_bridge/aws_data.py +0 -0
  53. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/terraform_bridge/common.py +0 -0
  54. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/terraform_bridge/google.py +0 -0
  55. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/terraform_bridge/google_pubsub.py +0 -0
  56. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/terraform_bridge/remote_state.py +0 -0
  57. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli/terraform_bridge/runner.py +0 -0
  58. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli.egg-info/dependency_links.txt +0 -0
  59. {fufu_cloud_cli-0.9.4 → fufu_cloud_cli-0.9.6}/src/fufu_cloud_cli.egg-info/top_level.txt +0 -0
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fufu-cloud-cli
3
- Version: 0.9.4
3
+ Version: 0.9.6
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
7
7
  Description-Content-Type: text/markdown
8
8
  License-File: LICENSE
9
9
  Requires-Dist: PyYAML==6.0.3
10
+ Requires-Dist: jmespath==1.1.0
10
11
  Requires-Dist: tomli==2.2.1; python_version < "3.11"
11
12
  Provides-Extra: yaml
12
13
  Requires-Dist: PyYAML==6.0.3; extra == "yaml"
@@ -14,14 +15,48 @@ Provides-Extra: test
14
15
  Requires-Dist: boto3==1.40.66; extra == "test"
15
16
  Provides-Extra: terraform
16
17
  Requires-Dist: python-hcl2==7.3.1; extra == "terraform"
18
+ Provides-Extra: gcp
19
+ Requires-Dist: google-cloud-bigquery==3.38.0; extra == "gcp"
20
+ Requires-Dist: google-cloud-tasks==2.20.0; extra == "gcp"
21
+ Requires-Dist: google-cloud-logging==3.12.1; extra == "gcp"
17
22
  Dynamic: license-file
18
23
 
19
24
  # FUFU Cloud CLI
20
25
 
21
- 以五個指令操作 FUFU Cloud 的租戶隔離開發環境:`fufu`、`aws-fufu`、
22
- `gcloud-fufu`、`az-fufu`、`wrangler-fufu`。這是 AWS、Google Cloud、Azure 與 Cloudflare 的**明確子集**,
26
+ 以六個指令操作 FUFU Cloud 的租戶隔離開發環境:`fufu`、`aws-fufu`、
27
+ `gcloud-fufu`、`az-fufu`、`wrangler-fufu`、`bq-fufu`。這是 AWS、Google Cloud、Azure 與 Cloudflare 的**明確子集**,
23
28
  只供 FUFU mock 環境使用;不會把部署轉送到正式雲端。
24
29
 
30
+ 0.9.6 新增 AWS Lambda Event/DryRun、configuration 更新、JMESPath `--query`、
31
+ json/text/yaml 輸出、SQS FIFO/batch、SSM path/history、DynamoDB Binary
32
+ 及 CloudWatch Logs。需要配套後端;Event 202 只表示持久化接受,並非 handler
33
+ 執行完成。仍無完整 AWS CLI 自動分頁、table、Layers/Image/aliases 或全量 IAM。
34
+
35
+ 0.9.5 新增 `gcloud-fufu scheduler`、`tasks`、`logging` 與 `bq-fufu`。
36
+ 支援同租戶已登錄 HTTP 應用的排程/背景任務、BigQuery dataset/table、
37
+ insertAll/SELECT 與持久化日誌。`fufu_cloud_cli.gcp_sdk` 提供官方 Python
38
+ BigQuery、Cloud Tasks、Logging SDK 的明確 REST factory;可用 `[gcp]` extra
39
+ 安裝已驗收相依。需要對應的新後端,並非透明 ADC 或所有 Google SDK 替換。
40
+
41
+ `fufu infrastructure` 提供 14 類 typed configuration mock。Compute Engine、
42
+ Cloud SQL、App Engine、DNS、VPC、Artifact Registry、Cloud Build、Gemini、
43
+ VM Manager、Eventarc 等設定不會啟動 VM/SQL/registry/網路/推論。
44
+ UI 顯示僅設定,CLI stderr 保留 MOCK ONLY,不因輸出投影而隱藏。
45
+ 新服務尚未接入 Terraform/OpenTofu/Pulumi;單機背景投遞不是雲端 HA。
46
+
47
+ ```bash
48
+ gcloud-fufu scheduler jobs list
49
+ gcloud-fufu tasks queues list
50
+ bq-fufu query --use_legacy_sql=false 'SELECT 1 AS ready'
51
+ gcloud-fufu logging read 'severity>=ERROR'
52
+ fufu infrastructure catalog
53
+ ```
54
+
55
+ BigQuery 是 tenant emulator 的 GoogleSQL 子集,無 DML/DDL、load/export、
56
+ 外部表與效能/費用等價。Logging 保留每租戶最新 10,000 筆,不自動收集所有
57
+ 容器 stdout。Tasks 每 queue 每秒最多 1 次、每租戶 2 個同時投遞;耗盡 task
58
+ 留待檢查,與官方刪除策略不同。新的 dependency flow 不代表完整應用都能遷移。
59
+
25
60
  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 格式可讀舊資料,回滾須還原升級前備份。
26
61
 
27
62
  0.9.3 的常用輸出投影、清單篩選/排序/limit、Cloud Run 設定更新、Service Account 啟停及 Pub/Sub attributes/ACK deadline 持續保留。僅明列子集經過官方 gcloud、小用量真實 GCP 與隔離 Docker 驗收;不表示完整 Google IAM、SDK/ADC 或自動擴縮相容。
@@ -36,7 +71,7 @@ Python 預設識別而遇到 Cloudflare HTTP 403/1010。識別標頭不取代
36
71
  ## Cloudflare 前端部署與 R2
37
72
 
38
73
  ```bash
39
- pip install 'fufu-cloud-cli==0.9.4'
74
+ pip install 'fufu-cloud-cli==0.9.6'
40
75
  export FUFU_CLOUDFLARE_CREDENTIAL_FILE=/run/secrets/fufu-cloudflare.json
41
76
  wrangler-fufu capabilities
42
77
  wrangler-fufu pages project create frontend --production-branch main
@@ -80,7 +115,8 @@ pip install fufu-cloud-cli
80
115
  fufu --version
81
116
  ```
82
117
 
83
- npm 版本需要 Node.js 18 以上,以及已安裝的 Python 3.10 以上;各入口
118
+ npm registry 尚未發布;以下是 registry 可用後的安裝語法。目前可安裝經驗收
119
+ 的 npm tarball。npm 版本需要 Node.js 18 以上,以及已安裝的 Python 3.10 以上;各入口
84
120
  呼叫相同的 Python CLI。0.8.0 的 npm 建置包內附純 Python YAML/TOML parser,沒有安裝腳本,
85
121
  不會自行下載 Python 或執行 pip。
86
122
 
@@ -97,12 +133,12 @@ npx --no-install aws-fufu --help
97
133
 
98
134
  ## 憑證與 CI
99
135
 
100
- 目前工作樹另提供 `fufu tofu`、`fufu pulumi`、`fufu state`。
136
+ 目前版本另提供 `fufu tofu`、`fufu pulumi`、`fufu state`。
101
137
  Terraform/OpenTofu 可選擇租戶遠端 HTTP state,具備鎖、加密、版本與
102
138
  復原;需配套 Console API 和平台金鑰。Pulumi 使用固定官方 Node.js SDK,
103
139
  已驗收 SQS、SSM String、Pub/Sub topic/pull subscription,採獨立本機
104
- DIY backend,秘密欄位需外部 passphrase。這些新能力尚未包含在已發布
105
- 的 0.8.0,不能把套件安裝成功當成支援驗收。
140
+ DIY backend,秘密欄位需外部 passphrase。這些能力從 0.9.0 起納入 CLI,
141
+ 仍需要配套服務端;不能把套件安裝成功當成支援驗收。
106
142
 
107
143
  此工作樹也提供 `fufu terraform`:以官方 AWS/Google Terraform provider
108
144
  管理 Lambda ZIP、Cloud Run v2、IAM 與服務入口的明列子集,並使用獨立的
@@ -112,9 +148,9 @@ GCS/S3、Functions Gen 1/Gen 2 等一般 CLI 能力
112
148
  不表示官方 Terraform 資源已相容。正式環境使用正常 Terraform、正式認證
113
149
  與獨立 state,不可直接搬用 FUFU state。
114
150
 
115
- 後續開發版另接入 SQS queue、SSM String parameter、Pub/Sub topic/pull
151
+ 目前版本另接入 SQS queue、SSM String parameter、Pub/Sub topic/pull
116
152
  subscription;新增能力需要配套的 Console 與 Pub/Sub 後端更新,不能從
117
- 已發布的 0.8.0 安裝成功就推定已具備。`gcloud-fufu pubsub` 提供建立、
153
+ CLI 安裝成功就推定服務端已具備。`gcloud-fufu pubsub` 提供建立、
118
154
  查詢、更新、刪除、publish/pull/ack 子集;不支援 push、ordering、
119
155
  exactly-once、Function trigger 或保留期限保證。
120
156
 
@@ -206,10 +242,11 @@ CLI 無法判斷任意原始碼或 ZIP 內的資料是否可以分享。
206
242
  | --- | --- |
207
243
  | Cloud Run | 已核准 HTTP 容器或 API 自動建置 Python ASGI 來源;支援配置後的 HTTPS 入口、Invoker policy;無 autoscaling、traffic splitting、Jobs |
208
244
  | Cloud Run functions / Functions Gen 1 | Python 3.12 HTTP 函式;Gen 1 為相同 HTTP runtime 的模擬子集,無背景事件 |
209
- | AWS Lambda | Python 3.10 / 3.12 ZIP、同步 invoke、程式碼更新、BUFFERED Function URL;ZIP 50 MiB/解壓 250 MiB,同步 payload 6 MiB |
245
+ | AWS Lambda | Python 3.10 / 3.12 ZIP、configuration、RequestResponse/Event/DryRun、BUFFERED Function URL;ZIP 50 MiB/解壓 250 MiB,同步 6 MiB/Event 1 MiB;Event 為單機持久化重試,無 destinations/DLQ 或正式 autoscaling |
210
246
  | GCS | bucket/object 子集、分段上傳/串流下載/binary cat、generation/metageneration 條件、metadata describe/update、前綴分頁;8 KiB custom metadata;無 versioning、signed URL、bucket IAM、一般 SDK 或 GCS IaC 資源 |
211
247
  | S3 | bucket 與 object 子集;分段上傳/串流下載,受主機磁碟限制;已驗證超過 1 GiB,無 versioning / signed URL |
212
- | DynamoDB / SQS / SSM | 明列資料操作;無完整交易、Streams、FIFO 或 KMS / SecureString |
248
+ | DynamoDB / SQS / SSM | 明列資料操作、Binary、FIFO group/dedup/batch、SSM 版本/path/history;無交易/TTL/Streams、SQS 背景 poller/DLQ/加密、KMS/SecureString |
249
+ | CloudWatch Logs | group/stream、put/get/filter events、retention 設定、CLI/UI/boto3;無 stdout 自動收集、Insights、subscriptions 或 retention 刪除保證 |
213
250
  | AWS IAM / GCP IAM | 租戶內 role / service account / policy 子集;default deny、限定 session;無原生雲端全量 IAM 語義 |
214
251
  | API Gateway / Serverless profile | 簡化 HTTP Lambda proxy 和明確來源清單;不執行 plugins / CloudFormation |
215
252
  | Azure | account 與資源群組目錄;尚無 Azure Functions 或其他 Azure runtime |
@@ -1,27 +1,39 @@
1
- Metadata-Version: 2.4
2
- Name: fufu-cloud-cli
3
- Version: 0.9.4
4
- Summary: FUFU Cloud CLI for tenant-scoped mock cloud development and deployment
5
- License-Expression: LicenseRef-Proprietary
6
- Requires-Python: >=3.10
7
- Description-Content-Type: text/markdown
8
- License-File: LICENSE
9
- Requires-Dist: PyYAML==6.0.3
10
- Requires-Dist: tomli==2.2.1; python_version < "3.11"
11
- Provides-Extra: yaml
12
- Requires-Dist: PyYAML==6.0.3; extra == "yaml"
13
- Provides-Extra: test
14
- Requires-Dist: boto3==1.40.66; extra == "test"
15
- Provides-Extra: terraform
16
- Requires-Dist: python-hcl2==7.3.1; extra == "terraform"
17
- Dynamic: license-file
18
-
19
1
  # FUFU Cloud CLI
20
2
 
21
- 以五個指令操作 FUFU Cloud 的租戶隔離開發環境:`fufu`、`aws-fufu`、
22
- `gcloud-fufu`、`az-fufu`、`wrangler-fufu`。這是 AWS、Google Cloud、Azure 與 Cloudflare 的**明確子集**,
3
+ 以六個指令操作 FUFU Cloud 的租戶隔離開發環境:`fufu`、`aws-fufu`、
4
+ `gcloud-fufu`、`az-fufu`、`wrangler-fufu`、`bq-fufu`。這是 AWS、Google Cloud、Azure 與 Cloudflare 的**明確子集**,
23
5
  只供 FUFU mock 環境使用;不會把部署轉送到正式雲端。
24
6
 
7
+ 0.9.6 新增 AWS Lambda Event/DryRun、configuration 更新、JMESPath `--query`、
8
+ json/text/yaml 輸出、SQS FIFO/batch、SSM path/history、DynamoDB Binary
9
+ 及 CloudWatch Logs。需要配套後端;Event 202 只表示持久化接受,並非 handler
10
+ 執行完成。仍無完整 AWS CLI 自動分頁、table、Layers/Image/aliases 或全量 IAM。
11
+
12
+ 0.9.5 新增 `gcloud-fufu scheduler`、`tasks`、`logging` 與 `bq-fufu`。
13
+ 支援同租戶已登錄 HTTP 應用的排程/背景任務、BigQuery dataset/table、
14
+ insertAll/SELECT 與持久化日誌。`fufu_cloud_cli.gcp_sdk` 提供官方 Python
15
+ BigQuery、Cloud Tasks、Logging SDK 的明確 REST factory;可用 `[gcp]` extra
16
+ 安裝已驗收相依。需要對應的新後端,並非透明 ADC 或所有 Google SDK 替換。
17
+
18
+ `fufu infrastructure` 提供 14 類 typed configuration mock。Compute Engine、
19
+ Cloud SQL、App Engine、DNS、VPC、Artifact Registry、Cloud Build、Gemini、
20
+ VM Manager、Eventarc 等設定不會啟動 VM/SQL/registry/網路/推論。
21
+ UI 顯示僅設定,CLI stderr 保留 MOCK ONLY,不因輸出投影而隱藏。
22
+ 新服務尚未接入 Terraform/OpenTofu/Pulumi;單機背景投遞不是雲端 HA。
23
+
24
+ ```bash
25
+ gcloud-fufu scheduler jobs list
26
+ gcloud-fufu tasks queues list
27
+ bq-fufu query --use_legacy_sql=false 'SELECT 1 AS ready'
28
+ gcloud-fufu logging read 'severity>=ERROR'
29
+ fufu infrastructure catalog
30
+ ```
31
+
32
+ BigQuery 是 tenant emulator 的 GoogleSQL 子集,無 DML/DDL、load/export、
33
+ 外部表與效能/費用等價。Logging 保留每租戶最新 10,000 筆,不自動收集所有
34
+ 容器 stdout。Tasks 每 queue 每秒最多 1 次、每租戶 2 個同時投遞;耗盡 task
35
+ 留待檢查,與官方刪除策略不同。新的 dependency flow 不代表完整應用都能遷移。
36
+
25
37
  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 格式可讀舊資料,回滾須還原升級前備份。
26
38
 
27
39
  0.9.3 的常用輸出投影、清單篩選/排序/limit、Cloud Run 設定更新、Service Account 啟停及 Pub/Sub attributes/ACK deadline 持續保留。僅明列子集經過官方 gcloud、小用量真實 GCP 與隔離 Docker 驗收;不表示完整 Google IAM、SDK/ADC 或自動擴縮相容。
@@ -36,7 +48,7 @@ Python 預設識別而遇到 Cloudflare HTTP 403/1010。識別標頭不取代
36
48
  ## Cloudflare 前端部署與 R2
37
49
 
38
50
  ```bash
39
- pip install 'fufu-cloud-cli==0.9.4'
51
+ pip install 'fufu-cloud-cli==0.9.6'
40
52
  export FUFU_CLOUDFLARE_CREDENTIAL_FILE=/run/secrets/fufu-cloudflare.json
41
53
  wrangler-fufu capabilities
42
54
  wrangler-fufu pages project create frontend --production-branch main
@@ -80,7 +92,8 @@ pip install fufu-cloud-cli
80
92
  fufu --version
81
93
  ```
82
94
 
83
- npm 版本需要 Node.js 18 以上,以及已安裝的 Python 3.10 以上;各入口
95
+ npm registry 尚未發布;以下是 registry 可用後的安裝語法。目前可安裝經驗收
96
+ 的 npm tarball。npm 版本需要 Node.js 18 以上,以及已安裝的 Python 3.10 以上;各入口
84
97
  呼叫相同的 Python CLI。0.8.0 的 npm 建置包內附純 Python YAML/TOML parser,沒有安裝腳本,
85
98
  不會自行下載 Python 或執行 pip。
86
99
 
@@ -97,12 +110,12 @@ npx --no-install aws-fufu --help
97
110
 
98
111
  ## 憑證與 CI
99
112
 
100
- 目前工作樹另提供 `fufu tofu`、`fufu pulumi`、`fufu state`。
113
+ 目前版本另提供 `fufu tofu`、`fufu pulumi`、`fufu state`。
101
114
  Terraform/OpenTofu 可選擇租戶遠端 HTTP state,具備鎖、加密、版本與
102
115
  復原;需配套 Console API 和平台金鑰。Pulumi 使用固定官方 Node.js SDK,
103
116
  已驗收 SQS、SSM String、Pub/Sub topic/pull subscription,採獨立本機
104
- DIY backend,秘密欄位需外部 passphrase。這些新能力尚未包含在已發布
105
- 的 0.8.0,不能把套件安裝成功當成支援驗收。
117
+ DIY backend,秘密欄位需外部 passphrase。這些能力從 0.9.0 起納入 CLI,
118
+ 仍需要配套服務端;不能把套件安裝成功當成支援驗收。
106
119
 
107
120
  此工作樹也提供 `fufu terraform`:以官方 AWS/Google Terraform provider
108
121
  管理 Lambda ZIP、Cloud Run v2、IAM 與服務入口的明列子集,並使用獨立的
@@ -112,9 +125,9 @@ GCS/S3、Functions Gen 1/Gen 2 等一般 CLI 能力
112
125
  不表示官方 Terraform 資源已相容。正式環境使用正常 Terraform、正式認證
113
126
  與獨立 state,不可直接搬用 FUFU state。
114
127
 
115
- 後續開發版另接入 SQS queue、SSM String parameter、Pub/Sub topic/pull
128
+ 目前版本另接入 SQS queue、SSM String parameter、Pub/Sub topic/pull
116
129
  subscription;新增能力需要配套的 Console 與 Pub/Sub 後端更新,不能從
117
- 已發布的 0.8.0 安裝成功就推定已具備。`gcloud-fufu pubsub` 提供建立、
130
+ CLI 安裝成功就推定服務端已具備。`gcloud-fufu pubsub` 提供建立、
118
131
  查詢、更新、刪除、publish/pull/ack 子集;不支援 push、ordering、
119
132
  exactly-once、Function trigger 或保留期限保證。
120
133
 
@@ -206,10 +219,11 @@ CLI 無法判斷任意原始碼或 ZIP 內的資料是否可以分享。
206
219
  | --- | --- |
207
220
  | Cloud Run | 已核准 HTTP 容器或 API 自動建置 Python ASGI 來源;支援配置後的 HTTPS 入口、Invoker policy;無 autoscaling、traffic splitting、Jobs |
208
221
  | Cloud Run functions / Functions Gen 1 | Python 3.12 HTTP 函式;Gen 1 為相同 HTTP runtime 的模擬子集,無背景事件 |
209
- | AWS Lambda | Python 3.10 / 3.12 ZIP、同步 invoke、程式碼更新、BUFFERED Function URL;ZIP 50 MiB/解壓 250 MiB,同步 payload 6 MiB |
222
+ | AWS Lambda | Python 3.10 / 3.12 ZIP、configuration、RequestResponse/Event/DryRun、BUFFERED Function URL;ZIP 50 MiB/解壓 250 MiB,同步 6 MiB/Event 1 MiB;Event 為單機持久化重試,無 destinations/DLQ 或正式 autoscaling |
210
223
  | GCS | bucket/object 子集、分段上傳/串流下載/binary cat、generation/metageneration 條件、metadata describe/update、前綴分頁;8 KiB custom metadata;無 versioning、signed URL、bucket IAM、一般 SDK 或 GCS IaC 資源 |
211
224
  | S3 | bucket 與 object 子集;分段上傳/串流下載,受主機磁碟限制;已驗證超過 1 GiB,無 versioning / signed URL |
212
- | DynamoDB / SQS / SSM | 明列資料操作;無完整交易、Streams、FIFO 或 KMS / SecureString |
225
+ | DynamoDB / SQS / SSM | 明列資料操作、Binary、FIFO group/dedup/batch、SSM 版本/path/history;無交易/TTL/Streams、SQS 背景 poller/DLQ/加密、KMS/SecureString |
226
+ | CloudWatch Logs | group/stream、put/get/filter events、retention 設定、CLI/UI/boto3;無 stdout 自動收集、Insights、subscriptions 或 retention 刪除保證 |
213
227
  | AWS IAM / GCP IAM | 租戶內 role / service account / policy 子集;default deny、限定 session;無原生雲端全量 IAM 語義 |
214
228
  | API Gateway / Serverless profile | 簡化 HTTP Lambda proxy 和明確來源清單;不執行 plugins / CloudFormation |
215
229
  | Azure | account 與資源群組目錄;尚無 Azure Functions 或其他 Azure runtime |
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fufu-cloud-cli"
7
- version = "0.9.4"
7
+ version = "0.9.6"
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"
11
- dependencies = ["PyYAML==6.0.3", "tomli==2.2.1; python_version<'3.11'"]
11
+ dependencies = ["PyYAML==6.0.3", "jmespath==1.1.0", "tomli==2.2.1; python_version<'3.11'"]
12
12
  license = "LicenseRef-Proprietary"
13
13
  license-files = ["LICENSE"]
14
14
 
@@ -16,6 +16,7 @@ license-files = ["LICENSE"]
16
16
  yaml = ["PyYAML==6.0.3"]
17
17
  test = ["boto3==1.40.66"]
18
18
  terraform = ["python-hcl2==7.3.1"]
19
+ gcp = ["google-cloud-bigquery==3.38.0", "google-cloud-tasks==2.20.0", "google-cloud-logging==3.12.1"]
19
20
 
20
21
  [project.scripts]
21
22
  fufu = "fufu_cloud_cli.cli:main"
@@ -23,9 +24,10 @@ aws-fufu = "fufu_cloud_cli.cli:aws_main"
23
24
  gcloud-fufu = "fufu_cloud_cli.cli:gcloud_main"
24
25
  az-fufu = "fufu_cloud_cli.cli:az_main"
25
26
  wrangler-fufu = "fufu_cloud_cli.cli:wrangler_main"
27
+ bq-fufu = "fufu_cloud_cli.cli:bq_main"
26
28
 
27
29
  [tool.setuptools.packages.find]
28
30
  where = ["src"]
29
31
 
30
32
  [tool.setuptools.package-data]
31
- fufu_cloud_cli = ["capabilities.json", "pulumi-endpoints.json", "runtime/*/Dockerfile", "runtime/ingress/proxy.py"]
33
+ fufu_cloud_cli = ["capabilities.json", "pulumi-endpoints.json", "runtime/*/Dockerfile", "runtime/ingress/proxy.py", "vendor/AWS-CLI-LICENSE.txt"]
@@ -1,3 +1,3 @@
1
1
  """FUFU CLI 與本機部署工具;相容範圍見 capabilities.json。"""
2
2
 
3
- __version__ = "0.9.4"
3
+ __version__ = "0.9.6"
@@ -3,7 +3,7 @@ import sys
3
3
  from .cli import entry
4
4
 
5
5
  if __name__ == "__main__":
6
- if len(sys.argv) > 1 and sys.argv[1] in {"fufu", "aws-fufu", "gcloud-fufu", "az-fufu"}:
6
+ if len(sys.argv) > 1 and sys.argv[1] in {"fufu", "aws-fufu", "gcloud-fufu", "az-fufu", "wrangler-fufu", "bq-fufu"}:
7
7
  entry(sys.argv[1], sys.argv[2:])
8
8
  else:
9
9
  entry("fufu", sys.argv[1:])
@@ -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 '/control/native' in path:
188
+ from .aws_errors import AWS_ERRORS
189
+ try:
190
+ value = json.loads(exc.read(4096))
191
+ code = value.get('code') if isinstance(value, dict) else None
192
+ if code in AWS_ERRORS: raise FufuError(code, 'AWS 操作遭拒;未輸出原始錯誤內容。') from None
193
+ except (ValueError, OSError): pass
194
+ finally: exc.close()
187
195
  exc.close()
188
196
  codes = {401: "AUTHENTICATION_FAILED", 403: "ACCESS_DENIED", 404: "RESOURCE_NOT_FOUND", 409: "RESOURCE_CONFLICT", 410: "UPLOAD_EXPIRED", 412: "PRECONDITION_FAILED", 413: "PAYLOAD_LIMIT", 429: "RATE_LIMITED", 507: "STORAGE_CAPACITY"}
189
197
  if status == 507: raise FufuError('STORAGE_CAPACITY', 'FUFU 主機磁碟容量不足;這是目前可用空間的限制,並非雲端物件大小上限。') from None
@@ -0,0 +1,6 @@
1
+ """Only documented provider error codes may cross the public transport boundary."""
2
+ AWS_ERRORS = frozenset('''ConditionalCheckFailedException ResourceNotFoundException ResourceInUseException ValidationException
3
+ NoSuchKey NoSuchBucket NotFound NotModified ParameterNotFound ParameterAlreadyExists ParameterVersionNotFound QueueDoesNotExist
4
+ PreconditionFailed InvalidRange InvalidParameterException InvalidParameterValue InvalidAttributeName InvalidAttributeValue QueueNameExists
5
+ PurgeQueueInProgress ReceiptHandleIsInvalid BatchEntryIdsNotDistinct EmptyBatchRequest BatchRequestTooLong InvalidBatchEntryId
6
+ TooManyEntriesInBatchRequest InvalidMessageContents AWS.SimpleQueueService.NonExistentQueue TooManyRequestsException InvalidRequestContentException'''.split())
@@ -0,0 +1,65 @@
1
+ """AWS JMESPath and native text formatting over the supported response subset."""
2
+ import io
3
+ import json
4
+
5
+ import jmespath
6
+ from jmespath.exceptions import JMESPathError
7
+
8
+ from .errors import FufuError, require
9
+ from .vendor.aws_text import format_text
10
+
11
+
12
+ def expression(value):
13
+ require(isinstance(value, str) and 0 < len(value) <= 65536,
14
+ 'INVALID_QUERY', 'AWS query 必須為非空 JMESPath 運算式,最多 65536 字元。')
15
+ try:
16
+ return jmespath.compile(value)
17
+ except JMESPathError:
18
+ raise FufuError('INVALID_QUERY', '無效的 AWS JMESPath query;未執行操作。', 2) from None
19
+
20
+
21
+ def configuration(value):
22
+ if 'FunctionName' in value:
23
+ return value
24
+ resources = value.get('resources', {})
25
+ fields = {'FunctionName': value.get('name'), 'FunctionArn': value.get('functionArn'),
26
+ 'Runtime': value.get('runtime'), 'Handler': value.get('handler'), 'Role': value.get('role'),
27
+ 'MemorySize': resources.get('memoryMiB'), 'Timeout': resources.get('timeoutSeconds'),
28
+ 'RevisionId': value.get('revision'), 'LastModified': value.get('createdAt')}
29
+ result = {key: item for key, item in fields.items() if item is not None}
30
+ if resources.get('tmpMiB') is not None: result['EphemeralStorage'] = {'Size': resources['tmpMiB']}
31
+ if value.get('status') == 'running': result.update(State='Active', LastUpdateStatus='Successful')
32
+ if 'env' in value: result['Environment'] = {'Variables': value['env']}
33
+ result['Fufu'] = {key: value[key] for key in ['mode', 'backend', 'readiness', 'serviceId', 'publicAccess', 'iamEnforced'] if key in value}
34
+ result['Fufu'].setdefault('mode', 'simplified')
35
+ return result
36
+
37
+
38
+ def native(value, argv):
39
+ if argv[:1] != ['lambda'] or len(argv) < 2 or not isinstance(value, dict):
40
+ return value
41
+ action = argv[1]
42
+ if action == 'list-functions':
43
+ return {**value, 'Functions': [configuration(item) for item in value.get('Functions', [])]}
44
+ if action == 'get-function':
45
+ return {'Configuration': configuration(value), 'Fufu': {'mode': 'simplified', 'codeDownloadAvailable': False}}
46
+ if action in {'create-function', 'update-function-code', 'get-function-configuration', 'update-function-configuration'}:
47
+ return configuration(value)
48
+ if action == 'delete-function': return None
49
+ return value
50
+
51
+
52
+ def render(value, output='json', query=None):
53
+ require(output in {'json', 'text', 'yaml', 'off'}, 'UNSUPPORTED_FORMAT',
54
+ 'AWS 輸出支援 json、text、yaml、off;table 與 yaml-stream 尚未支援。')
55
+ try:
56
+ value = query.search(value) if query else value
57
+ except JMESPathError:
58
+ raise FufuError('INVALID_QUERY', 'AWS query 無法套用到回應;操作可能已完成,請先查詢資源狀態。', 2) from None
59
+ if output == 'off': return ''
60
+ if output == 'text':
61
+ stream = io.StringIO(); format_text(value, stream); return stream.getvalue()
62
+ if output == 'yaml':
63
+ import yaml
64
+ return yaml.safe_dump(value, allow_unicode=True, sort_keys=False)
65
+ return json.dumps(value, ensure_ascii=False, indent=4) + '\n'
@@ -0,0 +1,92 @@
1
+ """bq command subset. Uses the same IAM-enforced BigQuery API as the console."""
2
+ import json
3
+ import sys
4
+ from pathlib import Path
5
+ from .api_backend import ApiClient
6
+ from .errors import require
7
+ from .gcp_infra import call
8
+
9
+
10
+ def reference(value, project):
11
+ if ':' in value: selected, value = value.split(':', 1)
12
+ else: selected = project
13
+ require(selected == project, 'ACCESS_DENIED', 'BigQuery project 必須符合 credential scope。')
14
+ parts = value.split('.')
15
+ require(len(parts) <= 2 and all(parts), 'INVALID_RESOURCE', '請使用 project:dataset.table。')
16
+ return {'projectId': project, 'datasetId': parts[0], **({'tableId': parts[1]} if len(parts) == 2 else {})}
17
+
18
+
19
+ def schema(value):
20
+ path = Path(value)
21
+ if path.is_file():
22
+ require(path.stat().st_size <= 1024**2, 'PAYLOAD_LIMIT', 'Schema 檔案超過 1 MiB。')
23
+ result = json.loads(path.read_text())
24
+ return result if isinstance(result, dict) else {'fields': result}
25
+ return {'fields': [{'name': name, 'type': kind.upper()} for name, kind in (field.split(':', 1) for field in value.split(','))]}
26
+
27
+
28
+ def command(argv, Parser):
29
+ # bq permits global flags before or after the command.
30
+ project = location = None; clean = []; i = 0
31
+ while i < len(argv):
32
+ flag, sep, inline = argv[i].partition('=')
33
+ if flag in {'--project_id', '--location', '--format'}:
34
+ value = inline if sep else (argv[i + 1] if i + 1 < len(argv) else '')
35
+ require(value and not value.startswith('--'), 'INVALID_ARGUMENT', '全域參數缺少值。')
36
+ if flag == '--project_id': project = value
37
+ elif flag == '--location': location = value
38
+ else: require(value in {'json', 'prettyjson'}, 'UNSUPPORTED_FORMAT', 'bq-fufu 輸出支援 json 與 prettyjson。')
39
+ i += 1 if sep else 2
40
+ elif flag in {'--quiet', '-q', '--headless'}: i += 1
41
+ else: clean.append(argv[i]); i += 1
42
+ p = Parser(prog='bq-fufu', description='BigQuery dataset/table、insertAll 與 SELECT 子集。未支援 load jobs、DML/DDL、external tables 或成本估算。')
43
+ actions = p.add_subparsers(dest='action', required=True)
44
+ for action in ['mk', 'ls', 'show', 'rm', 'head']:
45
+ q = actions.add_parser(action)
46
+ q.add_argument('resource', nargs='?' if action == 'ls' else None)
47
+ q.add_argument('--dataset', '-d', action='store_true'); q.add_argument('--table', '-t', action='store_true')
48
+ if action == 'mk': q.add_argument('schema', nargs='?'); q.add_argument('--description')
49
+ if action == 'rm': q.add_argument('--recursive', '-r', action='store_true'); q.add_argument('--force', '-f', action='store_true')
50
+ if action in {'head', 'ls'}: q.add_argument('--max_results', '-n', type=int, default=100); q.add_argument('--page_token')
51
+ q = actions.add_parser('query'); q.add_argument('query'); q.add_argument('--use_legacy_sql', choices=['false'], default='false'); q.add_argument('--nouse_legacy_sql', action='store_true'); q.add_argument('--parameter', action='append', default=[]); q.add_argument('--max_rows', type=int, default=100)
52
+ q = actions.add_parser('insert'); q.add_argument('resource'); q.add_argument('file')
53
+ a = p.parse_args(clean)
54
+ sys.stderr.write('FUFU BigQuery subset: tenant emulator; unsupported SQL and job types fail explicitly.\n')
55
+ with ApiClient('gcp', read_only=a.action in {'ls', 'show', 'head'}) as api:
56
+ selected = api.whoami()['projectId']
57
+ require(project is None or project == selected, 'ACCESS_DENIED', 'BigQuery project 必須符合 credential scope。')
58
+ if a.action == 'query':
59
+ params = {'projectId': selected, 'query': a.query, 'useLegacySql': False, 'maxResults': a.max_rows}
60
+ if location: params['location'] = location
61
+ if a.parameter:
62
+ params['parameterMode'] = 'NAMED'; params['queryParameters'] = []
63
+ for value in a.parameter:
64
+ parts = value.split(':', 2); require(len(parts) == 3, 'INVALID_ARGUMENT', 'parameter 請使用 name:type:value。')
65
+ name, kind, data = parts
66
+ params['queryParameters'].append({'name': name, 'parameterType': {'type': kind.upper() or 'STRING'}, 'parameterValue': {'value': data}})
67
+ return call(api, 'bigquery', 'queries.query', params)
68
+ ref = reference(a.resource, selected) if a.resource else {'projectId': selected}
69
+ table = 'tableId' in ref
70
+ if getattr(a, 'dataset', False): require(not table, 'INVALID_RESOURCE', 'Dataset 命令不可傳入 table。')
71
+ if getattr(a, 'table', False): require(table, 'INVALID_RESOURCE', 'Table 命令需要 dataset.table。')
72
+ group = 'tables' if table else 'datasets'
73
+ if a.action == 'mk':
74
+ require(not table or a.schema, 'INVALID_ARGUMENT', 'Table 建立需要 schema。')
75
+ value = {'tableReference' if table else 'datasetReference': ref}
76
+ if table: value['schema'] = schema(a.schema)
77
+ elif location: value['location'] = location
78
+ if a.description is not None: value['description'] = a.description
79
+ return call(api, 'bigquery', group + '.insert', {'projectId': selected, **({'datasetId': ref['datasetId']} if table else {}), 'table' if table else 'dataset': value})
80
+ if a.action == 'rm': return call(api, 'bigquery', group + '.delete', {**ref, **({'deleteContents': a.recursive} if not table else {})})
81
+ if a.action == 'show': return call(api, 'bigquery', group + '.get', ref)
82
+ if a.action in {'ls', 'head'}:
83
+ require(a.action != 'head' or table, 'INVALID_RESOURCE', 'head 需要 table。')
84
+ require(a.action != 'ls' or not table, 'INVALID_RESOURCE', 'ls 請指定 dataset。')
85
+ op = 'tabledata.list' if table else 'tables.list' if 'datasetId' in ref else 'datasets.list'
86
+ return call(api, 'bigquery', op, {**ref, 'maxResults': a.max_results, **({'pageToken': a.page_token} if a.page_token else {})})
87
+ require(table, 'INVALID_RESOURCE', 'insert 需要 table。')
88
+ file = Path(a.file); require(file.is_file() and file.stat().st_size <= 10 * 1024**2, 'PAYLOAD_LIMIT', 'NDJSON 檔案超過 10 MiB。')
89
+ rows = [{'json': json.loads(line)} for line in file.read_text().splitlines() if line.strip()]
90
+ result = call(api, 'bigquery', 'tabledata.insertAll', {**ref, 'rows': rows})
91
+ require(not result.get('insertErrors'), 'BIGQUERY_INSERT_FAILED', 'BigQuery 拒絕部分資料列;請檢查 schema。')
92
+ return result