fufu-cloud-cli 0.9.5__tar.gz → 0.9.7__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 (60) hide show
  1. {fufu_cloud_cli-0.9.5/src/fufu_cloud_cli.egg-info → fufu_cloud_cli-0.9.7}/PKG-INFO +20 -4
  2. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/README.md +18 -3
  3. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/pyproject.toml +3 -3
  4. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/__init__.py +1 -1
  5. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/api_backend.py +16 -0
  6. fufu_cloud_cli-0.9.7/src/fufu_cloud_cli/aws_errors.py +6 -0
  7. fufu_cloud_cli-0.9.7/src/fufu_cloud_cli/aws_lambda.py +125 -0
  8. fufu_cloud_cli-0.9.7/src/fufu_cloud_cli/aws_output.py +73 -0
  9. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/capabilities.json +22 -6
  10. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/cli.py +86 -15
  11. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/control.py +37 -12
  12. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/docker_backend.py +8 -3
  13. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/limits.py +1 -0
  14. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/remote_compute.py +8 -3
  15. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/runtime_sdk.py +44 -12
  16. fufu_cloud_cli-0.9.7/src/fufu_cloud_cli/vendor/AWS-CLI-LICENSE.txt +12 -0
  17. fufu_cloud_cli-0.9.7/src/fufu_cloud_cli/vendor/__init__.py +1 -0
  18. fufu_cloud_cli-0.9.7/src/fufu_cloud_cli/vendor/aws_text.py +117 -0
  19. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7/src/fufu_cloud_cli.egg-info}/PKG-INFO +20 -4
  20. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli.egg-info/SOURCES.txt +7 -1
  21. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli.egg-info/requires.txt +1 -0
  22. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/LICENSE +0 -0
  23. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/MANIFEST.in +0 -0
  24. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/setup.cfg +0 -0
  25. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/__main__.py +0 -0
  26. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/bigquery.py +0 -0
  27. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/capacity.py +0 -0
  28. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/cloud_data.py +0 -0
  29. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/cloudflare.py +0 -0
  30. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/errors.py +0 -0
  31. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/gcloud_environment.py +0 -0
  32. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/gcloud_output.py +0 -0
  33. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/gcp_infra.py +0 -0
  34. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/gcp_sdk.py +0 -0
  35. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/gcs.py +0 -0
  36. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/http_runtime.py +0 -0
  37. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/iac_state.py +0 -0
  38. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/lambda_adapter.py +0 -0
  39. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/manifest.py +0 -0
  40. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/object_transfer.py +0 -0
  41. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/public_access.py +0 -0
  42. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/pubsub.py +0 -0
  43. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/pulumi-endpoints.json +0 -0
  44. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/pulumi_bridge.py +0 -0
  45. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/runtime/functions/Dockerfile +0 -0
  46. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/runtime/ingress/Dockerfile +0 -0
  47. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/runtime/ingress/proxy.py +0 -0
  48. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/secrets.py +0 -0
  49. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/serverless.py +0 -0
  50. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/terraform_bridge/__init__.py +0 -0
  51. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/terraform_bridge/aws.py +0 -0
  52. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/terraform_bridge/aws_data.py +0 -0
  53. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/terraform_bridge/common.py +0 -0
  54. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/terraform_bridge/google.py +0 -0
  55. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/terraform_bridge/google_pubsub.py +0 -0
  56. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/terraform_bridge/remote_state.py +0 -0
  57. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli/terraform_bridge/runner.py +0 -0
  58. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli.egg-info/dependency_links.txt +0 -0
  59. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/src/fufu_cloud_cli.egg-info/entry_points.txt +0 -0
  60. {fufu_cloud_cli-0.9.5 → fufu_cloud_cli-0.9.7}/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.5
3
+ Version: 0.9.7
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"
@@ -26,6 +27,20 @@ Dynamic: license-file
26
27
  `gcloud-fufu`、`az-fufu`、`wrangler-fufu`、`bq-fufu`。這是 AWS、Google Cloud、Azure 與 Cloudflare 的**明確子集**,
27
28
  只供 FUFU mock 環境使用;不會把部署轉送到正式雲端。
28
29
 
30
+ 0.9.7 補齊 Lambda 五種 `wait` 指令、設定/程式碼更新的 `--revision-id`,
31
+ 以及常用設定的 `--cli-input-json` 和 `Variables={KEY=value}` shorthand。
32
+ 過期 RevisionId 回 `PreconditionFailedException`,保留原部署;CLI 仍等待
33
+ 部署完成才返回,waiter 有有限次數與失敗退出碼 255。程式碼雜湊、大小、
34
+ 最新修改時間與執行架構來自實際部署;預設記憶體為 128 MiB、期限 3 秒。
35
+ 需要配套 Console/compute worker 更新。JSON 只接受明列欄位,ZIP 另以
36
+ `--zip-file fileb://...` 傳入;不支援 Code.S3Bucket、Layers、Image、版本
37
+ 或 aliases。Function URL 的無效 AWS 簽名與匿名呼叫回 403。
38
+
39
+ 0.9.6 新增 AWS Lambda Event/DryRun、configuration 更新、JMESPath `--query`、
40
+ json/text/yaml 輸出、SQS FIFO/batch、SSM path/history、DynamoDB Binary
41
+ 及 CloudWatch Logs。需要配套後端;Event 202 只表示持久化接受,並非 handler
42
+ 執行完成。仍無完整 AWS CLI 自動分頁、table、Layers/Image/aliases 或全量 IAM。
43
+
29
44
  0.9.5 新增 `gcloud-fufu scheduler`、`tasks`、`logging` 與 `bq-fufu`。
30
45
  支援同租戶已登錄 HTTP 應用的排程/背景任務、BigQuery dataset/table、
31
46
  insertAll/SELECT 與持久化日誌。`fufu_cloud_cli.gcp_sdk` 提供官方 Python
@@ -65,7 +80,7 @@ Python 預設識別而遇到 Cloudflare HTTP 403/1010。識別標頭不取代
65
80
  ## Cloudflare 前端部署與 R2
66
81
 
67
82
  ```bash
68
- pip install 'fufu-cloud-cli==0.9.5'
83
+ pip install 'fufu-cloud-cli==0.9.7'
69
84
  export FUFU_CLOUDFLARE_CREDENTIAL_FILE=/run/secrets/fufu-cloudflare.json
70
85
  wrangler-fufu capabilities
71
86
  wrangler-fufu pages project create frontend --production-branch main
@@ -236,10 +251,11 @@ CLI 無法判斷任意原始碼或 ZIP 內的資料是否可以分享。
236
251
  | --- | --- |
237
252
  | Cloud Run | 已核准 HTTP 容器或 API 自動建置 Python ASGI 來源;支援配置後的 HTTPS 入口、Invoker policy;無 autoscaling、traffic splitting、Jobs |
238
253
  | Cloud Run functions / Functions Gen 1 | Python 3.12 HTTP 函式;Gen 1 為相同 HTTP runtime 的模擬子集,無背景事件 |
239
- | AWS Lambda | Python 3.10 / 3.12 ZIP、同步 invoke、程式碼更新、BUFFERED Function URL;ZIP 50 MiB/解壓 250 MiB,同步 payload 6 MiB |
254
+ | 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 |
240
255
  | GCS | bucket/object 子集、分段上傳/串流下載/binary cat、generation/metageneration 條件、metadata describe/update、前綴分頁;8 KiB custom metadata;無 versioning、signed URL、bucket IAM、一般 SDK 或 GCS IaC 資源 |
241
256
  | S3 | bucket 與 object 子集;分段上傳/串流下載,受主機磁碟限制;已驗證超過 1 GiB,無 versioning / signed URL |
242
- | DynamoDB / SQS / SSM | 明列資料操作;無完整交易、Streams、FIFO 或 KMS / SecureString |
257
+ | DynamoDB / SQS / SSM | 明列資料操作、Binary、FIFO group/dedup/batch、SSM 版本/path/history;無交易/TTL/Streams、SQS 背景 poller/DLQ/加密、KMS/SecureString |
258
+ | CloudWatch Logs | group/stream、put/get/filter events、retention 設定、CLI/UI/boto3;無 stdout 自動收集、Insights、subscriptions 或 retention 刪除保證 |
243
259
  | AWS IAM / GCP IAM | 租戶內 role / service account / policy 子集;default deny、限定 session;無原生雲端全量 IAM 語義 |
244
260
  | API Gateway / Serverless profile | 簡化 HTTP Lambda proxy 和明確來源清單;不執行 plugins / CloudFormation |
245
261
  | Azure | account 與資源群組目錄;尚無 Azure Functions 或其他 Azure runtime |
@@ -4,6 +4,20 @@
4
4
  `gcloud-fufu`、`az-fufu`、`wrangler-fufu`、`bq-fufu`。這是 AWS、Google Cloud、Azure 與 Cloudflare 的**明確子集**,
5
5
  只供 FUFU mock 環境使用;不會把部署轉送到正式雲端。
6
6
 
7
+ 0.9.7 補齊 Lambda 五種 `wait` 指令、設定/程式碼更新的 `--revision-id`,
8
+ 以及常用設定的 `--cli-input-json` 和 `Variables={KEY=value}` shorthand。
9
+ 過期 RevisionId 回 `PreconditionFailedException`,保留原部署;CLI 仍等待
10
+ 部署完成才返回,waiter 有有限次數與失敗退出碼 255。程式碼雜湊、大小、
11
+ 最新修改時間與執行架構來自實際部署;預設記憶體為 128 MiB、期限 3 秒。
12
+ 需要配套 Console/compute worker 更新。JSON 只接受明列欄位,ZIP 另以
13
+ `--zip-file fileb://...` 傳入;不支援 Code.S3Bucket、Layers、Image、版本
14
+ 或 aliases。Function URL 的無效 AWS 簽名與匿名呼叫回 403。
15
+
16
+ 0.9.6 新增 AWS Lambda Event/DryRun、configuration 更新、JMESPath `--query`、
17
+ json/text/yaml 輸出、SQS FIFO/batch、SSM path/history、DynamoDB Binary
18
+ 及 CloudWatch Logs。需要配套後端;Event 202 只表示持久化接受,並非 handler
19
+ 執行完成。仍無完整 AWS CLI 自動分頁、table、Layers/Image/aliases 或全量 IAM。
20
+
7
21
  0.9.5 新增 `gcloud-fufu scheduler`、`tasks`、`logging` 與 `bq-fufu`。
8
22
  支援同租戶已登錄 HTTP 應用的排程/背景任務、BigQuery dataset/table、
9
23
  insertAll/SELECT 與持久化日誌。`fufu_cloud_cli.gcp_sdk` 提供官方 Python
@@ -43,7 +57,7 @@ Python 預設識別而遇到 Cloudflare HTTP 403/1010。識別標頭不取代
43
57
  ## Cloudflare 前端部署與 R2
44
58
 
45
59
  ```bash
46
- pip install 'fufu-cloud-cli==0.9.5'
60
+ pip install 'fufu-cloud-cli==0.9.7'
47
61
  export FUFU_CLOUDFLARE_CREDENTIAL_FILE=/run/secrets/fufu-cloudflare.json
48
62
  wrangler-fufu capabilities
49
63
  wrangler-fufu pages project create frontend --production-branch main
@@ -214,10 +228,11 @@ CLI 無法判斷任意原始碼或 ZIP 內的資料是否可以分享。
214
228
  | --- | --- |
215
229
  | Cloud Run | 已核准 HTTP 容器或 API 自動建置 Python ASGI 來源;支援配置後的 HTTPS 入口、Invoker policy;無 autoscaling、traffic splitting、Jobs |
216
230
  | Cloud Run functions / Functions Gen 1 | Python 3.12 HTTP 函式;Gen 1 為相同 HTTP runtime 的模擬子集,無背景事件 |
217
- | AWS Lambda | Python 3.10 / 3.12 ZIP、同步 invoke、程式碼更新、BUFFERED Function URL;ZIP 50 MiB/解壓 250 MiB,同步 payload 6 MiB |
231
+ | 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 |
218
232
  | GCS | bucket/object 子集、分段上傳/串流下載/binary cat、generation/metageneration 條件、metadata describe/update、前綴分頁;8 KiB custom metadata;無 versioning、signed URL、bucket IAM、一般 SDK 或 GCS IaC 資源 |
219
233
  | S3 | bucket 與 object 子集;分段上傳/串流下載,受主機磁碟限制;已驗證超過 1 GiB,無 versioning / signed URL |
220
- | DynamoDB / SQS / SSM | 明列資料操作;無完整交易、Streams、FIFO 或 KMS / SecureString |
234
+ | DynamoDB / SQS / SSM | 明列資料操作、Binary、FIFO group/dedup/batch、SSM 版本/path/history;無交易/TTL/Streams、SQS 背景 poller/DLQ/加密、KMS/SecureString |
235
+ | CloudWatch Logs | group/stream、put/get/filter events、retention 設定、CLI/UI/boto3;無 stdout 自動收集、Insights、subscriptions 或 retention 刪除保證 |
221
236
  | AWS IAM / GCP IAM | 租戶內 role / service account / policy 子集;default deny、限定 session;無原生雲端全量 IAM 語義 |
222
237
  | API Gateway / Serverless profile | 簡化 HTTP Lambda proxy 和明確來源清單;不執行 plugins / CloudFormation |
223
238
  | 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.5"
7
+ version = "0.9.7"
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
 
@@ -30,4 +30,4 @@ bq-fufu = "fufu_cloud_cli.cli:bq_main"
30
30
  where = ["src"]
31
31
 
32
32
  [tool.setuptools.package-data]
33
- 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.5"
3
+ __version__ = "0.9.7"
@@ -184,6 +184,22 @@ class ApiClient:
184
184
  return json.loads(raw)
185
185
  except HTTPError as exc:
186
186
  status = exc.code
187
+ if self.provider == 'aws' and '/compute/' in path and status in {400, 412}:
188
+ try:
189
+ value = json.loads(exc.read(4096))
190
+ code = value.get('error', {}).get('code') if isinstance(value, dict) and isinstance(value.get('error'), dict) else None
191
+ if code in {'PreconditionFailedException', 'InvalidRequestContentException'}:
192
+ raise FufuError(code, 'Lambda 操作遭拒;未輸出原始設定或錯誤內容。') from None
193
+ except (ValueError, OSError): pass
194
+ finally: exc.close()
195
+ if self.provider == 'aws' and '/control/native' in path:
196
+ from .aws_errors import AWS_ERRORS
197
+ try:
198
+ value = json.loads(exc.read(4096))
199
+ code = value.get('code') if isinstance(value, dict) else None
200
+ if code in AWS_ERRORS: raise FufuError(code, 'AWS 操作遭拒;未輸出原始錯誤內容。') from None
201
+ except (ValueError, OSError): pass
202
+ finally: exc.close()
187
203
  exc.close()
188
204
  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
205
  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,125 @@
1
+ """Lambda command inputs and bounded AWS waiter semantics for the supported subset."""
2
+ import json
3
+ import re
4
+ import time
5
+
6
+ from .aws_output import configuration
7
+ from .errors import FufuError, require
8
+
9
+
10
+ WAITERS = {
11
+ 'function-exists': ('describe', None, None, 1, 20),
12
+ 'function-active': ('configuration', 'State', 'Active', 5, 60),
13
+ 'function-active-v2': ('describe', 'State', 'Active', 1, 300),
14
+ 'function-updated': ('configuration', 'LastUpdateStatus', 'Successful', 5, 60),
15
+ 'function-updated-v2': ('describe', 'LastUpdateStatus', 'Successful', 1, 300),
16
+ }
17
+
18
+
19
+ def wait(backend, waiter, name):
20
+ method, field, success, delay, attempts = WAITERS[waiter]
21
+ deadline = time.monotonic() + delay * attempts
22
+ for attempt in range(attempts):
23
+ try:
24
+ value = configuration(getattr(backend, method)('aws-lambda-sync', name))
25
+ except FufuError as error:
26
+ if waiter != 'function-exists' or error.code not in {'NOT_FOUND', 'RESOURCE_NOT_FOUND', 'ResourceNotFoundException'}:
27
+ raise FufuError('WAITER_FAILED', '等待服務時查詢失敗;請確認權限與服務狀態。', 255) from None
28
+ else:
29
+ if field is None or value.get(field) == success: return
30
+ if value.get(field) == 'Failed':
31
+ raise FufuError('WAITER_FAILED', '服務回報失敗狀態;未達到等待條件。', 255)
32
+ remaining = deadline - time.monotonic()
33
+ if attempt + 1 == attempts or remaining <= 0: break
34
+ time.sleep(min(delay, remaining))
35
+ raise FufuError('WAITER_FAILED', '等待超過期限;服務操作可能仍在執行,請重新查詢。', 255)
36
+
37
+
38
+ def environment(value):
39
+ """Native JSON or Variables={KEY=value,...}; malformed/ambiguous input fails closed."""
40
+ from .control import read_json
41
+ if value.startswith(('{', 'file://')):
42
+ return read_json(value)
43
+ require(value.startswith('Variables={') and value.endswith('}'), 'INVALID_INPUT',
44
+ 'Environment 請使用 JSON、file:// JSON 或 Variables={KEY=value,...}。')
45
+ text = value[len('Variables={'):-1]
46
+ values = {}; position = 0
47
+ # Quotes and escaped commas are supported without shell execution or expansion.
48
+ pattern = re.compile(r'\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*')
49
+ while position < len(text):
50
+ matched = pattern.match(text, position)
51
+ require(matched is not None, 'INVALID_INPUT', 'Environment shorthand 格式無效。')
52
+ key = matched[1]; position = matched.end(); chars = []; quote = None
53
+ require(key not in values, 'INVALID_INPUT', 'Environment 不可重複指定相同變數。')
54
+ if position < len(text) and text[position] in {'"', "'"}:
55
+ quote = text[position]; position += 1
56
+ closed = quote is None
57
+ while position < len(text):
58
+ character = text[position]; position += 1
59
+ if character == '\\':
60
+ require(position < len(text), 'INVALID_INPUT', 'Environment 跳脫序列未完成。')
61
+ chars.append(text[position]); position += 1
62
+ elif quote and character == quote:
63
+ closed = True
64
+ while position < len(text) and text[position].isspace(): position += 1
65
+ require(position == len(text) or text[position] == ',', 'INVALID_INPUT', 'Environment 引號後有多餘內容。')
66
+ if position < len(text): position += 1
67
+ break
68
+ elif not quote and character == ',': break
69
+ else:
70
+ require(quote is not None or character not in {'{', '}', '[', ']', '"', "'"},
71
+ 'UNSUPPORTED_ARGUMENT', '複雜 Environment 值請使用 JSON 或引號。')
72
+ chars.append(character)
73
+ require(closed, 'INVALID_INPUT', 'Environment 引號未關閉。')
74
+ values[key] = ''.join(chars) if quote else ''.join(chars).rstrip()
75
+ require(position != len(text) or not text.endswith(','), 'INVALID_INPUT', 'Environment 不可有結尾逗號。')
76
+ return {'Variables': values}
77
+
78
+
79
+ FIELDS = {
80
+ 'FunctionName': ('--function-name', str), 'Runtime': ('--runtime', str),
81
+ 'Handler': ('--handler', str), 'Role': ('--role', str),
82
+ 'Environment': ('--environment', dict), 'MemorySize': ('--memory-size', int),
83
+ 'Timeout': ('--timeout', int), 'EphemeralStorage': ('--ephemeral-storage', dict),
84
+ 'RevisionId': ('--revision-id', str), 'InvocationType': ('--invocation-type', str),
85
+ 'Payload': ('--payload', str),
86
+ }
87
+ INPUTS = {
88
+ 'create-function': {'FunctionName', 'Runtime', 'Handler', 'Role', 'Environment', 'MemorySize', 'Timeout', 'EphemeralStorage'},
89
+ 'update-function-code': {'FunctionName', 'RevisionId'},
90
+ 'update-function-configuration': {'FunctionName', 'Role', 'Environment', 'MemorySize', 'Timeout', 'EphemeralStorage', 'RevisionId'},
91
+ 'get-function': {'FunctionName'}, 'get-function-configuration': {'FunctionName'},
92
+ 'delete-function': {'FunctionName'}, 'invoke': {'FunctionName', 'InvocationType', 'Payload'},
93
+ 'list-functions': set(),
94
+ }
95
+
96
+
97
+ def input_json(argv):
98
+ """Expand validated native JSON before argparse; explicit flags take precedence."""
99
+ from .control import read_json
100
+ if len(argv) < 2: return argv
101
+ rest = []; documents = []; index = 2
102
+ while index < len(argv):
103
+ flag, separator, value = argv[index].partition('=')
104
+ if flag == '--cli-input-json':
105
+ if not separator:
106
+ index += 1
107
+ require(index < len(argv), 'INVALID_INPUT', 'cli-input-json 缺少值。')
108
+ value = argv[index]
109
+ documents.append(value)
110
+ else: rest.append(argv[index])
111
+ index += 1
112
+ if not documents: return argv
113
+ require(len(documents) == 1 and argv[1] in INPUTS, 'UNSUPPORTED_ARGUMENT', '此操作不支援指定的 cli-input-json。')
114
+ value = read_json(documents[0])
115
+ require(isinstance(value, dict) and not value.keys() - INPUTS[argv[1]], 'UNSUPPORTED_ARGUMENT',
116
+ 'Lambda JSON 含未支援的欄位;ZIP 請另用 --zip-file fileb:// 傳入,未執行操作。')
117
+ explicit = {arg.partition('=')[0] for arg in rest if arg.startswith('--')}
118
+ # FUFU memory alias selects the same official field.
119
+ if '--memory' in explicit: explicit.add('--memory-size')
120
+ expanded = []
121
+ for key, item in value.items():
122
+ flag, expected = FIELDS[key]
123
+ require(type(item) is expected, 'INVALID_INPUT', 'Lambda JSON 欄位型別無效。')
124
+ if flag not in explicit: expanded.extend([flag + '=' + (json.dumps(item) if expected is dict else str(item))])
125
+ return argv[:2] + expanded + rest
@@ -0,0 +1,73 @@
1
+ """AWS JMESPath and native text formatting over the supported response subset."""
2
+ import io
3
+ import json
4
+ import base64
5
+
6
+ import jmespath
7
+ from jmespath.exceptions import JMESPathError
8
+
9
+ from .errors import FufuError, require
10
+ from .vendor.aws_text import format_text
11
+
12
+
13
+ def expression(value):
14
+ require(isinstance(value, str) and 0 < len(value) <= 65536,
15
+ 'INVALID_QUERY', 'AWS query 必須為非空 JMESPath 運算式,最多 65536 字元。')
16
+ try:
17
+ return jmespath.compile(value)
18
+ except JMESPathError:
19
+ raise FufuError('INVALID_QUERY', '無效的 AWS JMESPath query;未執行操作。', 2) from None
20
+
21
+
22
+ def configuration(value):
23
+ if 'FunctionName' in value:
24
+ return value
25
+ resources = value.get('resources', {})
26
+ fields = {'FunctionName': value.get('name'), 'FunctionArn': value.get('functionArn'),
27
+ 'Runtime': value.get('runtime'), 'Handler': value.get('handler'), 'Role': value.get('role'),
28
+ 'MemorySize': resources.get('memoryMiB'), 'Timeout': resources.get('timeoutSeconds'),
29
+ 'RevisionId': value.get('revision'), 'LastModified': value.get('lastModified', value.get('createdAt'))}
30
+ result = {key: item for key, item in fields.items() if item is not None}
31
+ if resources.get('tmpMiB') is not None: result['EphemeralStorage'] = {'Size': resources['tmpMiB']}
32
+ if value.get('status') == 'running': result.update(State='Active', LastUpdateStatus='Successful')
33
+ if value.get('state'): result['State'] = value['state']
34
+ if value.get('lastUpdateStatus'): result['LastUpdateStatus'] = value['lastUpdateStatus']
35
+ result.update(Version='$LATEST', PackageType='Zip')
36
+ if value.get('architecture'): result['Architectures'] = [value['architecture']]
37
+ source = value.get('source', {})
38
+ if source.get('sha256'): result['CodeSha256'] = base64.b64encode(bytes.fromhex(source['sha256'])).decode()
39
+ if source.get('size') is not None: result['CodeSize'] = source['size']
40
+ if 'env' in value: result['Environment'] = {'Variables': value['env']}
41
+ result['Fufu'] = {key: value[key] for key in ['mode', 'backend', 'readiness', 'serviceId', 'publicAccess', 'iamEnforced'] if key in value}
42
+ result['Fufu'].setdefault('mode', 'simplified')
43
+ return result
44
+
45
+
46
+ def native(value, argv):
47
+ if argv[:1] != ['lambda'] or len(argv) < 2 or not isinstance(value, dict):
48
+ return value
49
+ action = argv[1]
50
+ if action == 'list-functions':
51
+ return {**value, 'Functions': [configuration(item) for item in value.get('Functions', [])]}
52
+ if action == 'get-function':
53
+ return {'Configuration': configuration(value), 'Fufu': {'mode': 'simplified', 'codeDownloadAvailable': False}}
54
+ if action in {'create-function', 'update-function-code', 'get-function-configuration', 'update-function-configuration'}:
55
+ return configuration(value)
56
+ if action == 'delete-function': return None
57
+ return value
58
+
59
+
60
+ def render(value, output='json', query=None):
61
+ require(output in {'json', 'text', 'yaml', 'off'}, 'UNSUPPORTED_FORMAT',
62
+ 'AWS 輸出支援 json、text、yaml、off;table 與 yaml-stream 尚未支援。')
63
+ try:
64
+ value = query.search(value) if query else value
65
+ except JMESPathError:
66
+ raise FufuError('INVALID_QUERY', 'AWS query 無法套用到回應;操作可能已完成,請先查詢資源狀態。', 2) from None
67
+ if output == 'off': return ''
68
+ if output == 'text':
69
+ stream = io.StringIO(); format_text(value, stream); return stream.getvalue()
70
+ if output == 'yaml':
71
+ import yaml
72
+ return yaml.safe_dump(value, allow_unicode=True, sort_keys=False)
73
+ return json.dumps(value, ensure_ascii=False, indent=4) + '\n'
@@ -103,6 +103,10 @@
103
103
  "operations": [
104
104
  "create-function-python310-python312-zip",
105
105
  "invoke-RequestResponse",
106
+ "invoke-Event-api-only",
107
+ "invoke-DryRun-api-only",
108
+ "get-function-configuration",
109
+ "update-function-configuration",
106
110
  "get-function",
107
111
  "list-functions",
108
112
  "delete-function",
@@ -113,7 +117,7 @@
113
117
  "AWS-IAM-inline-policy-subset",
114
118
  "boto3-data-transport-subset",
115
119
  "no-managed-timeout-equivalence",
116
- "no-async-Invoke",
120
+ "async-API-only-single-host-queue-no-destinations-or-DLQ",
117
121
  "remote-ZIP-50MiB-expanded-250MiB",
118
122
  "synchronous-payload-6MiB",
119
123
  "workload-lease-24h-renewable-while-active",
@@ -222,9 +226,12 @@
222
226
  "no-Entra-STS"
223
227
  ]
224
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"]},
230
+ "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
+ "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"]},
225
232
  "aws-data-services": {
226
233
  "status": "partial",
227
- "reason": "DynamoDB、S3、SQS、SSM 已接 CLI 與 boto3;事件僅 SQS 手動 drain,其他服务不推定可用。"
234
+ "reason": "DynamoDB、S3、SQS、SSM、CloudWatch Logs 與 Lambda Invoke 已接 CLI/boto3 明列子集;Lambda Event 可背景執行,SQS mapping 仍需手動 drain。"
228
235
  },
229
236
  "gcp-data-services": {
230
237
  "status": "partial",
@@ -281,6 +288,10 @@
281
288
  "list-queues",
282
289
  "delete-queue",
283
290
  "send-message",
291
+ "send-message-batch",
292
+ "delete-message-batch",
293
+ "change-message-visibility-batch",
294
+ "purge-queue",
284
295
  "receive-message",
285
296
  "delete-message",
286
297
  "get-queue-attributes",
@@ -291,13 +302,13 @@
291
302
  "terraform-lifecycle"
292
303
  ],
293
304
  "limitations": [
294
- "standard-queues",
305
+ "Standard-and-FIFO-queues",
295
306
  "message-1MiB",
296
307
  "long-poll-up-to-20s",
297
308
  "receive-max-10",
298
309
  "fufu-QueueUrl",
299
- "no-FIFO",
300
- "no-KMS-or-SQS-encryption",
310
+ "no-DLQ-or-standard-fair-queues",
311
+ "SSE-default-false-unlike-AWS-true-no-KMS",
301
312
  "manual-Lambda-drain-only",
302
313
  "no-background-poller"
303
314
  ]
@@ -309,6 +320,10 @@
309
320
  "put-parameter",
310
321
  "get-parameter",
311
322
  "get-parameters",
323
+ "get-parameters-by-path",
324
+ "get-parameter-history",
325
+ "get-parameter-version-selector",
326
+ "delete-parameters",
312
327
  "delete-parameter",
313
328
  "describe-parameters-exact-name",
314
329
  "list-tags-for-resource",
@@ -748,7 +763,8 @@
748
763
  "mode": "simplified",
749
764
  "limitations": [
750
765
  "botocore-event-transport",
751
- "DynamoDB-S3-SQS-SSM-only",
766
+ "DynamoDB-S3-SQS-SSM-SecretsManager-Logs-Lambda-Invoke-subsets",
767
+ "modeled-binary-timestamps-and-service-exceptions",
752
768
  "FUFU-token-not-SigV4",
753
769
  "no-real-cloud-fallback",
754
770
  "no-presigned-URLs"