fufu-cloud-cli 0.9.3__tar.gz → 0.9.5__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 (54) hide show
  1. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/PKG-INFO +45 -12
  2. fufu_cloud_cli-0.9.3/src/fufu_cloud_cli.egg-info/PKG-INFO → fufu_cloud_cli-0.9.5/README.md +40 -29
  3. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/pyproject.toml +3 -1
  4. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/__init__.py +1 -1
  5. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/__main__.py +1 -1
  6. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/api_backend.py +6 -2
  7. fufu_cloud_cli-0.9.5/src/fufu_cloud_cli/bigquery.py +92 -0
  8. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/capabilities.json +170 -21
  9. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/cli.py +25 -3
  10. fufu_cloud_cli-0.9.5/src/fufu_cloud_cli/gcp_infra.py +222 -0
  11. fufu_cloud_cli-0.9.5/src/fufu_cloud_cli/gcp_sdk.py +60 -0
  12. fufu_cloud_cli-0.9.5/src/fufu_cloud_cli/gcs.py +137 -0
  13. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/object_transfer.py +4 -3
  14. fufu_cloud_cli-0.9.3/README.md → fufu_cloud_cli-0.9.5/src/fufu_cloud_cli.egg-info/PKG-INFO +62 -11
  15. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli.egg-info/SOURCES.txt +4 -0
  16. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli.egg-info/entry_points.txt +1 -0
  17. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli.egg-info/requires.txt +5 -0
  18. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/LICENSE +0 -0
  19. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/MANIFEST.in +0 -0
  20. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/setup.cfg +0 -0
  21. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/capacity.py +0 -0
  22. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/cloud_data.py +0 -0
  23. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/cloudflare.py +0 -0
  24. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/control.py +0 -0
  25. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/docker_backend.py +0 -0
  26. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/errors.py +0 -0
  27. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/gcloud_environment.py +0 -0
  28. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/gcloud_output.py +0 -0
  29. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/http_runtime.py +0 -0
  30. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/iac_state.py +0 -0
  31. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/lambda_adapter.py +0 -0
  32. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/limits.py +0 -0
  33. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/manifest.py +0 -0
  34. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/public_access.py +0 -0
  35. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/pubsub.py +0 -0
  36. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/pulumi-endpoints.json +0 -0
  37. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/pulumi_bridge.py +0 -0
  38. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/remote_compute.py +0 -0
  39. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/runtime/functions/Dockerfile +0 -0
  40. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/runtime/ingress/Dockerfile +0 -0
  41. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/runtime/ingress/proxy.py +0 -0
  42. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/runtime_sdk.py +0 -0
  43. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/secrets.py +0 -0
  44. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/serverless.py +0 -0
  45. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/terraform_bridge/__init__.py +0 -0
  46. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/terraform_bridge/aws.py +0 -0
  47. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/terraform_bridge/aws_data.py +0 -0
  48. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/terraform_bridge/common.py +0 -0
  49. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/terraform_bridge/google.py +0 -0
  50. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/terraform_bridge/google_pubsub.py +0 -0
  51. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/terraform_bridge/remote_state.py +0 -0
  52. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli/terraform_bridge/runner.py +0 -0
  53. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/src/fufu_cloud_cli.egg-info/dependency_links.txt +0 -0
  54. {fufu_cloud_cli-0.9.3 → fufu_cloud_cli-0.9.5}/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
3
+ Version: 0.9.5
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
@@ -14,15 +14,46 @@ Provides-Extra: test
14
14
  Requires-Dist: boto3==1.40.66; extra == "test"
15
15
  Provides-Extra: terraform
16
16
  Requires-Dist: python-hcl2==7.3.1; extra == "terraform"
17
+ Provides-Extra: gcp
18
+ Requires-Dist: google-cloud-bigquery==3.38.0; extra == "gcp"
19
+ Requires-Dist: google-cloud-tasks==2.20.0; extra == "gcp"
20
+ Requires-Dist: google-cloud-logging==3.12.1; extra == "gcp"
17
21
  Dynamic: license-file
18
22
 
19
23
  # FUFU Cloud CLI
20
24
 
21
- 以五個指令操作 FUFU Cloud 的租戶隔離開發環境:`fufu`、`aws-fufu`、
22
- `gcloud-fufu`、`az-fufu`、`wrangler-fufu`。這是 AWS、Google Cloud、Azure 與 Cloudflare 的**明確子集**,
25
+ 以六個指令操作 FUFU Cloud 的租戶隔離開發環境:`fufu`、`aws-fufu`、
26
+ `gcloud-fufu`、`az-fufu`、`wrangler-fufu`、`bq-fufu`。這是 AWS、Google Cloud、Azure 與 Cloudflare 的**明確子集**,
23
27
  只供 FUFU mock 環境使用;不會把部署轉送到正式雲端。
24
28
 
25
- 0.9.3 新增 gcloud 常用輸出投影、資源清單篩選/排序/limit、Cloud Run 設定與環境變數更新、Service Account 查詢/停用/啟用,以及 Pub/Sub attributes、分頁和 message ACK deadline。這些行為已用官方 gcloud、小用量真實 GCP 與隔離 Docker 驗收;不表示完整 Google IAM、SDK/ADC 或自動擴縮相容。新增後端能力需要同步更新 FUFU Console 和 compute worker。
29
+ 0.9.5 新增 `gcloud-fufu scheduler`、`tasks`、`logging` 與 `bq-fufu`。
30
+ 支援同租戶已登錄 HTTP 應用的排程/背景任務、BigQuery dataset/table、
31
+ insertAll/SELECT 與持久化日誌。`fufu_cloud_cli.gcp_sdk` 提供官方 Python
32
+ BigQuery、Cloud Tasks、Logging SDK 的明確 REST factory;可用 `[gcp]` extra
33
+ 安裝已驗收相依。需要對應的新後端,並非透明 ADC 或所有 Google SDK 替換。
34
+
35
+ `fufu infrastructure` 提供 14 類 typed configuration mock。Compute Engine、
36
+ Cloud SQL、App Engine、DNS、VPC、Artifact Registry、Cloud Build、Gemini、
37
+ VM Manager、Eventarc 等設定不會啟動 VM/SQL/registry/網路/推論。
38
+ UI 顯示僅設定,CLI stderr 保留 MOCK ONLY,不因輸出投影而隱藏。
39
+ 新服務尚未接入 Terraform/OpenTofu/Pulumi;單機背景投遞不是雲端 HA。
40
+
41
+ ```bash
42
+ gcloud-fufu scheduler jobs list
43
+ gcloud-fufu tasks queues list
44
+ bq-fufu query --use_legacy_sql=false 'SELECT 1 AS ready'
45
+ gcloud-fufu logging read 'severity>=ERROR'
46
+ fufu infrastructure catalog
47
+ ```
48
+
49
+ BigQuery 是 tenant emulator 的 GoogleSQL 子集,無 DML/DDL、load/export、
50
+ 外部表與效能/費用等價。Logging 保留每租戶最新 10,000 筆,不自動收集所有
51
+ 容器 stdout。Tasks 每 queue 每秒最多 1 次、每租戶 2 個同時投遞;耗盡 task
52
+ 留待檢查,與官方刪除策略不同。新的 dependency flow 不代表完整應用都能遷移。
53
+
54
+ 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 格式可讀舊資料,回滾須還原升級前備份。
55
+
56
+ 0.9.3 的常用輸出投影、清單篩選/排序/limit、Cloud Run 設定更新、Service Account 啟停及 Pub/Sub attributes/ACK deadline 持續保留。僅明列子集經過官方 gcloud、小用量真實 GCP 與隔離 Docker 驗收;不表示完整 Google IAM、SDK/ADC 或自動擴縮相容。
26
57
 
27
58
  0.9.1 統一所有 CLI API 請求的 `User-Agent: fufu-cloud-cli`,涵蓋物件傳輸、
28
59
  Cloudflare 靜態檔案部署、IaC 遠端 state 與 Cloud Shell session,避免沿用
@@ -34,7 +65,7 @@ Python 預設識別而遇到 Cloudflare HTTP 403/1010。識別標頭不取代
34
65
  ## Cloudflare 前端部署與 R2
35
66
 
36
67
  ```bash
37
- pip install 'fufu-cloud-cli==0.9.3'
68
+ pip install 'fufu-cloud-cli==0.9.5'
38
69
  export FUFU_CLOUDFLARE_CREDENTIAL_FILE=/run/secrets/fufu-cloudflare.json
39
70
  wrangler-fufu capabilities
40
71
  wrangler-fufu pages project create frontend --production-branch main
@@ -78,7 +109,8 @@ pip install fufu-cloud-cli
78
109
  fufu --version
79
110
  ```
80
111
 
81
- npm 版本需要 Node.js 18 以上,以及已安裝的 Python 3.10 以上;各入口
112
+ npm registry 尚未發布;以下是 registry 可用後的安裝語法。目前可安裝經驗收
113
+ 的 npm tarball。npm 版本需要 Node.js 18 以上,以及已安裝的 Python 3.10 以上;各入口
82
114
  呼叫相同的 Python CLI。0.8.0 的 npm 建置包內附純 Python YAML/TOML parser,沒有安裝腳本,
83
115
  不會自行下載 Python 或執行 pip。
84
116
 
@@ -95,12 +127,12 @@ npx --no-install aws-fufu --help
95
127
 
96
128
  ## 憑證與 CI
97
129
 
98
- 目前工作樹另提供 `fufu tofu`、`fufu pulumi`、`fufu state`。
130
+ 目前版本另提供 `fufu tofu`、`fufu pulumi`、`fufu state`。
99
131
  Terraform/OpenTofu 可選擇租戶遠端 HTTP state,具備鎖、加密、版本與
100
132
  復原;需配套 Console API 和平台金鑰。Pulumi 使用固定官方 Node.js SDK,
101
133
  已驗收 SQS、SSM String、Pub/Sub topic/pull subscription,採獨立本機
102
- DIY backend,秘密欄位需外部 passphrase。這些新能力尚未包含在已發布
103
- 的 0.8.0,不能把套件安裝成功當成支援驗收。
134
+ DIY backend,秘密欄位需外部 passphrase。這些能力從 0.9.0 起納入 CLI,
135
+ 仍需要配套服務端;不能把套件安裝成功當成支援驗收。
104
136
 
105
137
  此工作樹也提供 `fufu terraform`:以官方 AWS/Google Terraform provider
106
138
  管理 Lambda ZIP、Cloud Run v2、IAM 與服務入口的明列子集,並使用獨立的
@@ -110,9 +142,9 @@ GCS/S3、Functions Gen 1/Gen 2 等一般 CLI 能力
110
142
  不表示官方 Terraform 資源已相容。正式環境使用正常 Terraform、正式認證
111
143
  與獨立 state,不可直接搬用 FUFU state。
112
144
 
113
- 後續開發版另接入 SQS queue、SSM String parameter、Pub/Sub topic/pull
145
+ 目前版本另接入 SQS queue、SSM String parameter、Pub/Sub topic/pull
114
146
  subscription;新增能力需要配套的 Console 與 Pub/Sub 後端更新,不能從
115
- 已發布的 0.8.0 安裝成功就推定已具備。`gcloud-fufu pubsub` 提供建立、
147
+ CLI 安裝成功就推定服務端已具備。`gcloud-fufu pubsub` 提供建立、
116
148
  查詢、更新、刪除、publish/pull/ack 子集;不支援 push、ordering、
117
149
  exactly-once、Function trigger 或保留期限保證。
118
150
 
@@ -205,7 +237,8 @@ CLI 無法判斷任意原始碼或 ZIP 內的資料是否可以分享。
205
237
  | Cloud Run | 已核准 HTTP 容器或 API 自動建置 Python ASGI 來源;支援配置後的 HTTPS 入口、Invoker policy;無 autoscaling、traffic splitting、Jobs |
206
238
  | Cloud Run functions / Functions Gen 1 | Python 3.12 HTTP 函式;Gen 1 為相同 HTTP runtime 的模擬子集,無背景事件 |
207
239
  | AWS Lambda | Python 3.10 / 3.12 ZIP、同步 invoke、程式碼更新、BUFFERED Function URL;ZIP 50 MiB/解壓 250 MiB,同步 payload 6 MiB |
208
- | GCS / S3 | bucket 與 object 子集;分段上傳/串流下載,受主機磁碟限制;已驗證超過 1 GiB,無 versioning / signed URL |
240
+ | GCS | bucket/object 子集、分段上傳/串流下載/binary cat、generation/metageneration 條件、metadata describe/update、前綴分頁;8 KiB custom metadata;無 versioning、signed URL、bucket IAM、一般 SDK 或 GCS IaC 資源 |
241
+ | S3 | bucket 與 object 子集;分段上傳/串流下載,受主機磁碟限制;已驗證超過 1 GiB,無 versioning / signed URL |
209
242
  | DynamoDB / SQS / SSM | 明列資料操作;無完整交易、Streams、FIFO 或 KMS / SecureString |
210
243
  | AWS IAM / GCP IAM | 租戶內 role / service account / policy 子集;default deny、限定 session;無原生雲端全量 IAM 語義 |
211
244
  | API Gateway / Serverless profile | 簡化 HTTP Lambda proxy 和明確來源清單;不執行 plugins / CloudFormation |
@@ -1,28 +1,37 @@
1
- Metadata-Version: 2.4
2
- Name: fufu-cloud-cli
3
- Version: 0.9.3
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
 
25
- 0.9.3 新增 gcloud 常用輸出投影、資源清單篩選/排序/limit、Cloud Run 設定與環境變數更新、Service Account 查詢/停用/啟用,以及 Pub/Sub attributes、分頁和 message ACK deadline。這些行為已用官方 gcloud、小用量真實 GCP 與隔離 Docker 驗收;不表示完整 Google IAM、SDK/ADC 或自動擴縮相容。新增後端能力需要同步更新 FUFU Console 和 compute worker。
7
+ 0.9.5 新增 `gcloud-fufu scheduler`、`tasks`、`logging` 與 `bq-fufu`。
8
+ 支援同租戶已登錄 HTTP 應用的排程/背景任務、BigQuery dataset/table、
9
+ insertAll/SELECT 與持久化日誌。`fufu_cloud_cli.gcp_sdk` 提供官方 Python
10
+ BigQuery、Cloud Tasks、Logging SDK 的明確 REST factory;可用 `[gcp]` extra
11
+ 安裝已驗收相依。需要對應的新後端,並非透明 ADC 或所有 Google SDK 替換。
12
+
13
+ `fufu infrastructure` 提供 14 類 typed configuration mock。Compute Engine、
14
+ Cloud SQL、App Engine、DNS、VPC、Artifact Registry、Cloud Build、Gemini、
15
+ VM Manager、Eventarc 等設定不會啟動 VM/SQL/registry/網路/推論。
16
+ UI 顯示僅設定,CLI stderr 保留 MOCK ONLY,不因輸出投影而隱藏。
17
+ 新服務尚未接入 Terraform/OpenTofu/Pulumi;單機背景投遞不是雲端 HA。
18
+
19
+ ```bash
20
+ gcloud-fufu scheduler jobs list
21
+ gcloud-fufu tasks queues list
22
+ bq-fufu query --use_legacy_sql=false 'SELECT 1 AS ready'
23
+ gcloud-fufu logging read 'severity>=ERROR'
24
+ fufu infrastructure catalog
25
+ ```
26
+
27
+ BigQuery 是 tenant emulator 的 GoogleSQL 子集,無 DML/DDL、load/export、
28
+ 外部表與效能/費用等價。Logging 保留每租戶最新 10,000 筆,不自動收集所有
29
+ 容器 stdout。Tasks 每 queue 每秒最多 1 次、每租戶 2 個同時投遞;耗盡 task
30
+ 留待檢查,與官方刪除策略不同。新的 dependency flow 不代表完整應用都能遷移。
31
+
32
+ 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 格式可讀舊資料,回滾須還原升級前備份。
33
+
34
+ 0.9.3 的常用輸出投影、清單篩選/排序/limit、Cloud Run 設定更新、Service Account 啟停及 Pub/Sub attributes/ACK deadline 持續保留。僅明列子集經過官方 gcloud、小用量真實 GCP 與隔離 Docker 驗收;不表示完整 Google IAM、SDK/ADC 或自動擴縮相容。
26
35
 
27
36
  0.9.1 統一所有 CLI API 請求的 `User-Agent: fufu-cloud-cli`,涵蓋物件傳輸、
28
37
  Cloudflare 靜態檔案部署、IaC 遠端 state 與 Cloud Shell session,避免沿用
@@ -34,7 +43,7 @@ Python 預設識別而遇到 Cloudflare HTTP 403/1010。識別標頭不取代
34
43
  ## Cloudflare 前端部署與 R2
35
44
 
36
45
  ```bash
37
- pip install 'fufu-cloud-cli==0.9.3'
46
+ pip install 'fufu-cloud-cli==0.9.5'
38
47
  export FUFU_CLOUDFLARE_CREDENTIAL_FILE=/run/secrets/fufu-cloudflare.json
39
48
  wrangler-fufu capabilities
40
49
  wrangler-fufu pages project create frontend --production-branch main
@@ -78,7 +87,8 @@ pip install fufu-cloud-cli
78
87
  fufu --version
79
88
  ```
80
89
 
81
- npm 版本需要 Node.js 18 以上,以及已安裝的 Python 3.10 以上;各入口
90
+ npm registry 尚未發布;以下是 registry 可用後的安裝語法。目前可安裝經驗收
91
+ 的 npm tarball。npm 版本需要 Node.js 18 以上,以及已安裝的 Python 3.10 以上;各入口
82
92
  呼叫相同的 Python CLI。0.8.0 的 npm 建置包內附純 Python YAML/TOML parser,沒有安裝腳本,
83
93
  不會自行下載 Python 或執行 pip。
84
94
 
@@ -95,12 +105,12 @@ npx --no-install aws-fufu --help
95
105
 
96
106
  ## 憑證與 CI
97
107
 
98
- 目前工作樹另提供 `fufu tofu`、`fufu pulumi`、`fufu state`。
108
+ 目前版本另提供 `fufu tofu`、`fufu pulumi`、`fufu state`。
99
109
  Terraform/OpenTofu 可選擇租戶遠端 HTTP state,具備鎖、加密、版本與
100
110
  復原;需配套 Console API 和平台金鑰。Pulumi 使用固定官方 Node.js SDK,
101
111
  已驗收 SQS、SSM String、Pub/Sub topic/pull subscription,採獨立本機
102
- DIY backend,秘密欄位需外部 passphrase。這些新能力尚未包含在已發布
103
- 的 0.8.0,不能把套件安裝成功當成支援驗收。
112
+ DIY backend,秘密欄位需外部 passphrase。這些能力從 0.9.0 起納入 CLI,
113
+ 仍需要配套服務端;不能把套件安裝成功當成支援驗收。
104
114
 
105
115
  此工作樹也提供 `fufu terraform`:以官方 AWS/Google Terraform provider
106
116
  管理 Lambda ZIP、Cloud Run v2、IAM 與服務入口的明列子集,並使用獨立的
@@ -110,9 +120,9 @@ GCS/S3、Functions Gen 1/Gen 2 等一般 CLI 能力
110
120
  不表示官方 Terraform 資源已相容。正式環境使用正常 Terraform、正式認證
111
121
  與獨立 state,不可直接搬用 FUFU state。
112
122
 
113
- 後續開發版另接入 SQS queue、SSM String parameter、Pub/Sub topic/pull
123
+ 目前版本另接入 SQS queue、SSM String parameter、Pub/Sub topic/pull
114
124
  subscription;新增能力需要配套的 Console 與 Pub/Sub 後端更新,不能從
115
- 已發布的 0.8.0 安裝成功就推定已具備。`gcloud-fufu pubsub` 提供建立、
125
+ CLI 安裝成功就推定服務端已具備。`gcloud-fufu pubsub` 提供建立、
116
126
  查詢、更新、刪除、publish/pull/ack 子集;不支援 push、ordering、
117
127
  exactly-once、Function trigger 或保留期限保證。
118
128
 
@@ -205,7 +215,8 @@ CLI 無法判斷任意原始碼或 ZIP 內的資料是否可以分享。
205
215
  | Cloud Run | 已核准 HTTP 容器或 API 自動建置 Python ASGI 來源;支援配置後的 HTTPS 入口、Invoker policy;無 autoscaling、traffic splitting、Jobs |
206
216
  | Cloud Run functions / Functions Gen 1 | Python 3.12 HTTP 函式;Gen 1 為相同 HTTP runtime 的模擬子集,無背景事件 |
207
217
  | AWS Lambda | Python 3.10 / 3.12 ZIP、同步 invoke、程式碼更新、BUFFERED Function URL;ZIP 50 MiB/解壓 250 MiB,同步 payload 6 MiB |
208
- | GCS / S3 | bucket 與 object 子集;分段上傳/串流下載,受主機磁碟限制;已驗證超過 1 GiB,無 versioning / signed URL |
218
+ | GCS | bucket/object 子集、分段上傳/串流下載/binary cat、generation/metageneration 條件、metadata describe/update、前綴分頁;8 KiB custom metadata;無 versioning、signed URL、bucket IAM、一般 SDK 或 GCS IaC 資源 |
219
+ | S3 | bucket 與 object 子集;分段上傳/串流下載,受主機磁碟限制;已驗證超過 1 GiB,無 versioning / signed URL |
209
220
  | DynamoDB / SQS / SSM | 明列資料操作;無完整交易、Streams、FIFO 或 KMS / SecureString |
210
221
  | AWS IAM / GCP IAM | 租戶內 role / service account / policy 子集;default deny、限定 session;無原生雲端全量 IAM 語義 |
211
222
  | API Gateway / Serverless profile | 簡化 HTTP Lambda proxy 和明確來源清單;不執行 plugins / CloudFormation |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fufu-cloud-cli"
7
- version = "0.9.3"
7
+ version = "0.9.5"
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"
@@ -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,6 +24,7 @@ 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"]
@@ -1,3 +1,3 @@
1
1
  """FUFU CLI 與本機部署工具;相容範圍見 capabilities.json。"""
2
2
 
3
- __version__ = "0.9.3"
3
+ __version__ = "0.9.5"
@@ -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:])
@@ -185,7 +185,7 @@ class ApiClient:
185
185
  except HTTPError as exc:
186
186
  status = exc.code
187
187
  exc.close()
188
- codes = {401: "AUTHENTICATION_FAILED", 403: "ACCESS_DENIED", 404: "RESOURCE_NOT_FOUND", 409: "RESOURCE_CONFLICT", 410: "UPLOAD_EXPIRED", 413: "PAYLOAD_LIMIT", 429: "RATE_LIMITED", 507: "STORAGE_CAPACITY"}
188
+ 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
189
  if status == 507: raise FufuError('STORAGE_CAPACITY', 'FUFU 主機磁碟容量不足;這是目前可用空間的限制,並非雲端物件大小上限。') from None
190
190
  raise FufuError(codes.get(status, "API_REQUEST_FAILED"), f"FUFU API 拒絕請求(HTTP {status});未輸出憑證或原始回應。") from None
191
191
  except (URLError, TimeoutError, ConnectionError) as exc:
@@ -211,7 +211,8 @@ class ApiClient:
211
211
  return json.loads(response.read(65537))
212
212
  except HTTPError as error:
213
213
  status = error.code; error.close()
214
- raise FufuError('OBJECT_TRANSFER_FAILED', f'物件傳輸未完成(HTTP {status});未覆寫下載目的地。') from None
214
+ code = 'PRECONDITION_FAILED' if self.provider == 'gcp' and status == 412 else 'OBJECT_TRANSFER_FAILED'
215
+ raise FufuError(code, f'物件傳輸未完成(HTTP {status});未覆寫下載目的地。') from None
215
216
 
216
217
  def close(self):
217
218
  if self.token and self.owns_token:
@@ -243,6 +244,9 @@ def storage_path(value):
243
244
 
244
245
 
245
246
  def remote_command(provider, argv, Parser):
247
+ if provider == 'gcp' and argv[:1] == ['storage']:
248
+ from .gcs import command
249
+ return command(argv[1:], Parser)
246
250
  p = Parser(prog={"aws": "aws-fufu", "gcp": "gcloud-fufu", "azure": "az-fufu"}[provider],
247
251
  description="FUFU API 憑證與租戶資源操作子集;無原生雲端 IAM/部署相容承諾。")
248
252
  roots = p.add_subparsers(dest="service", required=True)
@@ -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