py-serverless-kit 0.1.0__py3-none-any.whl
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.
- py_serverless_kit/__init__.py +47 -0
- py_serverless_kit/__main__.py +3 -0
- py_serverless_kit/_version.py +2 -0
- py_serverless_kit/cli/__init__.py +0 -0
- py_serverless_kit/cli/main.py +491 -0
- py_serverless_kit/cli/templates.py +212 -0
- py_serverless_kit/config/__init__.py +12 -0
- py_serverless_kit/config/loader.py +316 -0
- py_serverless_kit/config/schema.py +457 -0
- py_serverless_kit/core/__init__.py +0 -0
- py_serverless_kit/core/deployer.py +180 -0
- py_serverless_kit/core/describe.py +112 -0
- py_serverless_kit/core/project.py +147 -0
- py_serverless_kit/dashboard/__init__.py +3 -0
- py_serverless_kit/dashboard/generator.py +65 -0
- py_serverless_kit/dashboard/template.html +248 -0
- py_serverless_kit/local/__init__.py +3 -0
- py_serverless_kit/local/runner.py +234 -0
- py_serverless_kit/local/server.py +200 -0
- py_serverless_kit/packaging/__init__.py +3 -0
- py_serverless_kit/packaging/builder.py +252 -0
- py_serverless_kit/providers/__init__.py +15 -0
- py_serverless_kit/providers/alibaba.py +268 -0
- py_serverless_kit/providers/aws.py +508 -0
- py_serverless_kit/providers/azure.py +417 -0
- py_serverless_kit/providers/base.py +310 -0
- py_serverless_kit/providers/cloudflare.py +264 -0
- py_serverless_kit/providers/digitalocean.py +211 -0
- py_serverless_kit/providers/gcp.py +480 -0
- py_serverless_kit/providers/huawei.py +292 -0
- py_serverless_kit/providers/ibm.py +132 -0
- py_serverless_kit/providers/oracle.py +304 -0
- py_serverless_kit/providers/registry.py +60 -0
- py_serverless_kit/providers/schedule.py +153 -0
- py_serverless_kit/providers/tencent.py +275 -0
- py_serverless_kit/providers/terraform_doc.py +125 -0
- py_serverless_kit/runtime/__init__.py +6 -0
- py_serverless_kit/runtime/adapters/__init__.py +18 -0
- py_serverless_kit/runtime/adapters/_httpfn.py +91 -0
- py_serverless_kit/runtime/adapters/alibaba.py +147 -0
- py_serverless_kit/runtime/adapters/aws.py +201 -0
- py_serverless_kit/runtime/adapters/azure.py +185 -0
- py_serverless_kit/runtime/adapters/base.py +86 -0
- py_serverless_kit/runtime/adapters/cloudflare.py +126 -0
- py_serverless_kit/runtime/adapters/digitalocean.py +74 -0
- py_serverless_kit/runtime/adapters/gcp.py +176 -0
- py_serverless_kit/runtime/adapters/huawei.py +173 -0
- py_serverless_kit/runtime/adapters/ibm.py +91 -0
- py_serverless_kit/runtime/adapters/oracle.py +161 -0
- py_serverless_kit/runtime/adapters/tencent.py +151 -0
- py_serverless_kit/runtime/batch.py +47 -0
- py_serverless_kit/runtime/context.py +45 -0
- py_serverless_kit/runtime/dispatch.py +277 -0
- py_serverless_kit/runtime/errors.py +83 -0
- py_serverless_kit/runtime/event.py +267 -0
- py_serverless_kit/runtime/invoke.py +119 -0
- py_serverless_kit/runtime/log.py +69 -0
- py_serverless_kit/runtime/manifest.py +121 -0
- py_serverless_kit/runtime/response.py +177 -0
- py_serverless_kit/runtime/routing.py +74 -0
- py_serverless_kit/runtime/workflow.py +312 -0
- py_serverless_kit/terraform/__init__.py +3 -0
- py_serverless_kit/terraform/runner.py +104 -0
- py_serverless_kit/workflows/__init__.py +35 -0
- py_serverless_kit/workflows/asl.py +170 -0
- py_serverless_kit/workflows/gcp.py +328 -0
- py_serverless_kit-0.1.0.dist-info/METADATA +300 -0
- py_serverless_kit-0.1.0.dist-info/RECORD +71 -0
- py_serverless_kit-0.1.0.dist-info/WHEEL +4 -0
- py_serverless_kit-0.1.0.dist-info/entry_points.txt +3 -0
- py_serverless_kit-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from .base import Artifact, Issue, Provider, ProviderError, RenderContext
|
|
2
|
+
from .registry import all_providers, available, get_provider
|
|
3
|
+
from .terraform_doc import TerraformDoc
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"Artifact",
|
|
7
|
+
"Issue",
|
|
8
|
+
"Provider",
|
|
9
|
+
"ProviderError",
|
|
10
|
+
"RenderContext",
|
|
11
|
+
"TerraformDoc",
|
|
12
|
+
"all_providers",
|
|
13
|
+
"available",
|
|
14
|
+
"get_provider",
|
|
15
|
+
]
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"""Alibaba Cloud: Function Compute 3.0 + HTTP / timer / OSS / MNS-topic triggers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
from ..config.schema import FunctionConfig, ServiceConfig
|
|
9
|
+
from ..runtime.adapters.base import envelope
|
|
10
|
+
from . import schedule as sched
|
|
11
|
+
from .base import HTTP, SCHEDULE, STORAGE, TOPIC, Issue, Provider, RenderContext
|
|
12
|
+
from .terraform_doc import TerraformDoc, escape, ref, tf_id
|
|
13
|
+
|
|
14
|
+
FUNCTION = "alicloud_fcv3_function"
|
|
15
|
+
TRIGGER = "alicloud_fcv3_trigger"
|
|
16
|
+
ALL_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]
|
|
17
|
+
_TRUST = json.dumps(
|
|
18
|
+
{
|
|
19
|
+
"Version": "1",
|
|
20
|
+
"Statement": [
|
|
21
|
+
{
|
|
22
|
+
"Action": "sts:AssumeRole",
|
|
23
|
+
"Effect": "Allow",
|
|
24
|
+
"Principal": {"Service": ["oss.aliyuncs.com", "mns.aliyuncs.com"]},
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class AlibabaProvider(Provider):
|
|
32
|
+
name = "alibaba"
|
|
33
|
+
display_name = "Alibaba Cloud Function Compute"
|
|
34
|
+
tier = "beta"
|
|
35
|
+
terraform_source = "aliyun/alicloud"
|
|
36
|
+
terraform_version = ">= 1.230.0"
|
|
37
|
+
terraform_local_name = "alicloud"
|
|
38
|
+
default_region = "cn-hangzhou"
|
|
39
|
+
capabilities = frozenset({HTTP, SCHEDULE, STORAGE, TOPIC})
|
|
40
|
+
max_name_length = 64
|
|
41
|
+
name_pattern = r"[^a-z0-9_-]"
|
|
42
|
+
runtimes = {"python3.9": "python3.9", "python3.10": "python3.10", "python3.12": "python3.12"}
|
|
43
|
+
vendor_dependencies = True
|
|
44
|
+
max_timeout = 3600 # FC allows up to 86400s, but the config schema caps timeout at 3600
|
|
45
|
+
notes = (
|
|
46
|
+
"FC 3.0 with one HTTP trigger per function (fcapp.run URL); schedules via timer triggers; "
|
|
47
|
+
"OSS and MNS-topic triggers; workflows run on the embedded engine."
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# ------------------------------------------------------------------ bundle
|
|
51
|
+
def shim_files(self, config: ServiceConfig) -> Dict[str, str]:
|
|
52
|
+
return {
|
|
53
|
+
"index.py": (
|
|
54
|
+
"# Generated by py-serverless-kit. Function Compute handler: index.handler\n"
|
|
55
|
+
"from py_serverless_kit.runtime.adapters.alibaba import handler # noqa: F401\n"
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
def provider_block(self, ctx: RenderContext) -> Dict[str, Any]:
|
|
60
|
+
block: Dict[str, Any] = {"region": ctx.region}
|
|
61
|
+
if ctx.opt("profile"):
|
|
62
|
+
block["profile"] = ctx.opt("profile")
|
|
63
|
+
return block
|
|
64
|
+
|
|
65
|
+
def extra_validation(self, config: ServiceConfig) -> List[Issue]:
|
|
66
|
+
issues = []
|
|
67
|
+
if config.provider.iam.statements:
|
|
68
|
+
issues.append(
|
|
69
|
+
Issue(
|
|
70
|
+
"warning",
|
|
71
|
+
"iam.statements are not translated to RAM policies on Alibaba; set iam.role to an existing "
|
|
72
|
+
"RAM role ARN with the permissions your functions need",
|
|
73
|
+
"provider.iam",
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
for name, fn in config.functions.items():
|
|
77
|
+
if fn.iam.statements:
|
|
78
|
+
issues.append(
|
|
79
|
+
Issue("warning", "iam.statements are ignored on Alibaba; use iam.role", f"functions.{name}.iam")
|
|
80
|
+
)
|
|
81
|
+
for ev in fn.events_of("http"):
|
|
82
|
+
if ev.auth not in ("none",):
|
|
83
|
+
issues.append(
|
|
84
|
+
Issue(
|
|
85
|
+
"error",
|
|
86
|
+
f"http auth `{ev.auth}` is not supported on Alibaba (use none)",
|
|
87
|
+
f"functions.{name}",
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
return issues
|
|
91
|
+
|
|
92
|
+
# ------------------------------------------------------------------ render
|
|
93
|
+
def render_resources(self, doc: TerraformDoc, ctx: RenderContext) -> None:
|
|
94
|
+
cfg = ctx.config
|
|
95
|
+
needs_role = any(fn.events_of("storage") or fn.events_of("topic") for fn in cfg.functions.values())
|
|
96
|
+
role_arn = self._trigger_role(doc, ctx) if needs_role else None
|
|
97
|
+
account = doc.data("alicloud_account", "current", {}) if needs_role else None
|
|
98
|
+
|
|
99
|
+
fn_out: Dict[str, Dict[str, Any]] = {}
|
|
100
|
+
endpoint: Optional[str] = None
|
|
101
|
+
for name, fn in ctx.function_items():
|
|
102
|
+
res = self._function(doc, ctx, name, fn)
|
|
103
|
+
out: Dict[str, Any] = {
|
|
104
|
+
"id": ref(res, "function_name"),
|
|
105
|
+
"arn": ref(res, "function_arn"),
|
|
106
|
+
"console": f"https://fcnext.console.aliyun.com/{ctx.region}/functions/{ctx.physical_name(name)}",
|
|
107
|
+
}
|
|
108
|
+
http_events = fn.events_of("http")
|
|
109
|
+
if http_events:
|
|
110
|
+
trig = self._http(doc, name, res, http_events)
|
|
111
|
+
url = ref(trig, "http_trigger[0].url_internet")
|
|
112
|
+
out["url"] = url
|
|
113
|
+
out["base_url"] = url
|
|
114
|
+
endpoint = endpoint or url
|
|
115
|
+
for i, ev in enumerate(fn.events_of("schedule")):
|
|
116
|
+
self._timer(doc, name, res, i, ev)
|
|
117
|
+
for ev in fn.events_of("storage"):
|
|
118
|
+
self._oss(doc, ctx, name, res, ev, role_arn, account)
|
|
119
|
+
for ev in fn.events_of("topic"):
|
|
120
|
+
self._mns_topic(doc, ctx, name, res, ev, role_arn, account)
|
|
121
|
+
fn_out[name] = out
|
|
122
|
+
|
|
123
|
+
wf_out = {n: {"id": ref(FUNCTION, tf_id(f"{n}-orchestrator"), "function_name")} for n in sorted(cfg.workflows)}
|
|
124
|
+
doc.output(
|
|
125
|
+
"pskit",
|
|
126
|
+
{"functions": fn_out, "workflows": wf_out, "endpoint": endpoint},
|
|
127
|
+
description="py-serverless-kit deployment summary",
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# ------------------------------------------------------------------ pieces
|
|
131
|
+
def _trigger_role(self, doc: TerraformDoc, ctx: RenderContext) -> str:
|
|
132
|
+
role = doc.resource(
|
|
133
|
+
"alicloud_ram_role",
|
|
134
|
+
"triggers",
|
|
135
|
+
{
|
|
136
|
+
"role_name": ctx.physical_name("triggers")[:64],
|
|
137
|
+
"assume_role_policy_document": _TRUST,
|
|
138
|
+
"description": f"py-serverless-kit trigger role for {ctx.prefix}",
|
|
139
|
+
"force": True,
|
|
140
|
+
},
|
|
141
|
+
)
|
|
142
|
+
doc.resource(
|
|
143
|
+
"alicloud_ram_role_policy_attachment",
|
|
144
|
+
"triggers-invoke",
|
|
145
|
+
{"role_name": ref(role, "role_name"), "policy_name": "AliyunFCInvocationAccess", "policy_type": "System"},
|
|
146
|
+
)
|
|
147
|
+
return ref(role, "arn")
|
|
148
|
+
|
|
149
|
+
def _function(self, doc: TerraformDoc, ctx: RenderContext, name: str, fn: FunctionConfig) -> str:
|
|
150
|
+
cfg = ctx.config
|
|
151
|
+
body: Dict[str, Any] = {
|
|
152
|
+
"function_name": ctx.physical_name(name),
|
|
153
|
+
"description": fn.description[:256],
|
|
154
|
+
"handler": "index.handler",
|
|
155
|
+
"runtime": self.runtime_id(cfg.runtime_of(fn)),
|
|
156
|
+
"memory_size": cfg.memory_of(fn),
|
|
157
|
+
"timeout": cfg.timeout_of(fn),
|
|
158
|
+
"internet_access": True,
|
|
159
|
+
"environment_variables": {k: escape(v) for k, v in ctx.environment(name, fn).items()},
|
|
160
|
+
"code": {"zip_file": '${filebase64("${path.module}/artifact.zip")}'},
|
|
161
|
+
"tags": ctx.tags(fn),
|
|
162
|
+
}
|
|
163
|
+
role = fn.iam.role or cfg.provider.iam.role
|
|
164
|
+
if role:
|
|
165
|
+
body["role"] = role
|
|
166
|
+
if fn.concurrency:
|
|
167
|
+
body["instance_concurrency"] = fn.concurrency
|
|
168
|
+
body.update(fn.options) # escape hatch: vpc_config, layers, log_config, ...
|
|
169
|
+
return doc.resource(FUNCTION, name, body)
|
|
170
|
+
|
|
171
|
+
def _http(self, doc: TerraformDoc, name: str, fn_res: str, events: List[Any]) -> str:
|
|
172
|
+
methods: List[str] = []
|
|
173
|
+
for ev in events:
|
|
174
|
+
for m in ALL_METHODS if ev.method == "ANY" else [ev.method]:
|
|
175
|
+
if m not in methods:
|
|
176
|
+
methods.append(m)
|
|
177
|
+
if any(ev.cors_config is not None for ev in events) and "OPTIONS" not in methods:
|
|
178
|
+
methods.append("OPTIONS")
|
|
179
|
+
config = {"authType": "anonymous", "methods": methods, "disableURLInternet": False}
|
|
180
|
+
return doc.resource(
|
|
181
|
+
TRIGGER,
|
|
182
|
+
f"{name}-http",
|
|
183
|
+
{
|
|
184
|
+
"function_name": ref(fn_res, "function_name"),
|
|
185
|
+
"trigger_name": "http",
|
|
186
|
+
"trigger_type": "http",
|
|
187
|
+
"qualifier": "LATEST",
|
|
188
|
+
"trigger_config": json.dumps(config),
|
|
189
|
+
},
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
def _timer(self, doc: TerraformDoc, name: str, fn_res: str, i: int, ev: Any) -> None:
|
|
193
|
+
expr = sched.every_expression(ev) if ev.rate else "0 " + sched.named_cron5(ev)
|
|
194
|
+
if ev.cron and ev.timezone and ev.timezone != "UTC":
|
|
195
|
+
expr = f"CRON_TZ={ev.timezone} {expr}"
|
|
196
|
+
config = {
|
|
197
|
+
"cronExpression": expr,
|
|
198
|
+
"enable": bool(ev.enabled),
|
|
199
|
+
"payload": json.dumps(envelope("schedule", ev.input, source="alibaba.timer")),
|
|
200
|
+
}
|
|
201
|
+
doc.resource(
|
|
202
|
+
TRIGGER,
|
|
203
|
+
f"{name}-timer-{i}",
|
|
204
|
+
{
|
|
205
|
+
"function_name": ref(fn_res, "function_name"),
|
|
206
|
+
"trigger_name": (ev.name or f"timer-{i}")[:128],
|
|
207
|
+
"trigger_type": "timer",
|
|
208
|
+
"qualifier": "LATEST",
|
|
209
|
+
"trigger_config": escape(json.dumps(config)),
|
|
210
|
+
},
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
def _oss(
|
|
214
|
+
self,
|
|
215
|
+
doc: TerraformDoc,
|
|
216
|
+
ctx: RenderContext,
|
|
217
|
+
name: str,
|
|
218
|
+
fn_res: str,
|
|
219
|
+
ev: Any,
|
|
220
|
+
role: Optional[str],
|
|
221
|
+
account: Optional[str],
|
|
222
|
+
) -> None:
|
|
223
|
+
label = f"b-{ev.bucket}"
|
|
224
|
+
if not ev.existing and not doc.has_resource("alicloud_oss_bucket", label):
|
|
225
|
+
doc.resource("alicloud_oss_bucket", label, {"bucket": ev.bucket, "tags": ctx.tags()})
|
|
226
|
+
events = [{"created": "oss:ObjectCreated:*", "deleted": "oss:ObjectRemoved:*"}[e] for e in ev.events]
|
|
227
|
+
config = {"events": events, "filter": {"key": {"prefix": ev.prefix or "", "suffix": ev.suffix or ""}}}
|
|
228
|
+
body: Dict[str, Any] = {
|
|
229
|
+
"function_name": ref(fn_res, "function_name"),
|
|
230
|
+
"trigger_name": f"oss-{tf_id(ev.bucket)}"[:128],
|
|
231
|
+
"trigger_type": "oss",
|
|
232
|
+
"qualifier": "LATEST",
|
|
233
|
+
"invocation_role": role,
|
|
234
|
+
"source_arn": f"acs:oss:{ctx.region}:{ref(str(account), 'id')}:{ev.bucket}",
|
|
235
|
+
"trigger_config": json.dumps(config),
|
|
236
|
+
}
|
|
237
|
+
if not ev.existing:
|
|
238
|
+
body["depends_on"] = [f"alicloud_oss_bucket.{tf_id(label)}"]
|
|
239
|
+
doc.resource(TRIGGER, f"{name}-oss-{ev.bucket}", body)
|
|
240
|
+
|
|
241
|
+
def _mns_topic(
|
|
242
|
+
self,
|
|
243
|
+
doc: TerraformDoc,
|
|
244
|
+
ctx: RenderContext,
|
|
245
|
+
name: str,
|
|
246
|
+
fn_res: str,
|
|
247
|
+
ev: Any,
|
|
248
|
+
role: Optional[str],
|
|
249
|
+
account: Optional[str],
|
|
250
|
+
) -> None:
|
|
251
|
+
label = f"t-{ev.name}"
|
|
252
|
+
if not ev.existing and not doc.has_resource("alicloud_message_service_topic", label):
|
|
253
|
+
doc.resource("alicloud_message_service_topic", label, {"topic_name": ev.name, "tags": ctx.tags()})
|
|
254
|
+
config: Dict[str, Any] = {"notifyContentFormat": "JSON", "notifyStrategy": "BACKOFF_RETRY"}
|
|
255
|
+
if ev.filter and isinstance(ev.filter.get("tag"), str):
|
|
256
|
+
config["filterTag"] = ev.filter["tag"]
|
|
257
|
+
body: Dict[str, Any] = {
|
|
258
|
+
"function_name": ref(fn_res, "function_name"),
|
|
259
|
+
"trigger_name": f"mns-{tf_id(ev.name)}"[:128],
|
|
260
|
+
"trigger_type": "mns_topic",
|
|
261
|
+
"qualifier": "LATEST",
|
|
262
|
+
"invocation_role": role,
|
|
263
|
+
"source_arn": f"acs:mns:{ctx.region}:{ref(str(account), 'id')}:/topics/{ev.name}",
|
|
264
|
+
"trigger_config": json.dumps(config),
|
|
265
|
+
}
|
|
266
|
+
if not ev.existing:
|
|
267
|
+
body["depends_on"] = [f"alicloud_message_service_topic.{tf_id(label)}"]
|
|
268
|
+
doc.resource(TRIGGER, f"{name}-mns-{ev.name}", body)
|