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,292 @@
|
|
|
1
|
+
"""Huawei Cloud: FunctionGraph + dedicated API Gateway + timer / OBS / SMN triggers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
from typing import Any, Dict, List, Optional
|
|
8
|
+
|
|
9
|
+
from ..config.schema import FunctionConfig, ServiceConfig
|
|
10
|
+
from ..runtime.adapters.base import envelope
|
|
11
|
+
from . import schedule as sched
|
|
12
|
+
from .base import HTTP, SCHEDULE, STORAGE, TOPIC, Issue, Provider, RenderContext
|
|
13
|
+
from .terraform_doc import TerraformDoc, escape, ref, tf_id
|
|
14
|
+
|
|
15
|
+
FUNCTION = "huaweicloud_fgs_function"
|
|
16
|
+
TRIGGER = "huaweicloud_fgs_trigger"
|
|
17
|
+
_AUTH = {"none": "NONE", "iam": "IAM", "key": "APP"}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def quartz_cron(ev: Any) -> str:
|
|
21
|
+
"""FunctionGraph cron: ``sec min hour dom month dow`` where dom or dow must be ``?``."""
|
|
22
|
+
minute, hour, dom, month, dow = sched.named_cron5(ev).split()
|
|
23
|
+
if dow == "*":
|
|
24
|
+
dow = "?"
|
|
25
|
+
elif dom == "*":
|
|
26
|
+
dom = "?"
|
|
27
|
+
return f"0 {minute} {hour} {dom} {month} {dow}"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def rate_expression(ev: Any) -> str:
|
|
31
|
+
value, unit = sched.parse_rate(ev.rate or "")
|
|
32
|
+
return f"{value}{unit[0]}"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _gateway_path(path: str) -> tuple[str, str]:
|
|
36
|
+
segments = [s for s in path.split("/") if s]
|
|
37
|
+
if segments and segments[-1].endswith("+}"):
|
|
38
|
+
return "/" + "/".join(segments[:-1]), "Prefix"
|
|
39
|
+
return "/" + "/".join(segments), "Exact"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class HuaweiProvider(Provider):
|
|
43
|
+
name = "huawei"
|
|
44
|
+
display_name = "Huawei Cloud FunctionGraph"
|
|
45
|
+
tier = "beta"
|
|
46
|
+
terraform_source = "huaweicloud/huaweicloud"
|
|
47
|
+
terraform_version = ">= 1.60.0"
|
|
48
|
+
terraform_local_name = "huaweicloud"
|
|
49
|
+
default_region = "cn-north-4"
|
|
50
|
+
capabilities = frozenset({HTTP, SCHEDULE, STORAGE, TOPIC})
|
|
51
|
+
max_name_length = 60
|
|
52
|
+
name_pattern = r"[^a-z0-9_-]"
|
|
53
|
+
runtimes = {"python3.9": "Python3.9", "python3.10": "Python3.10", "python3.12": "Python3.12"}
|
|
54
|
+
vendor_dependencies = True
|
|
55
|
+
max_timeout = 900
|
|
56
|
+
notes = (
|
|
57
|
+
"HTTP needs a dedicated APIG instance (options.apig_instance_id); timers, OBS and SMN triggers; "
|
|
58
|
+
"workflows run on the embedded engine."
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# ------------------------------------------------------------------ bundle
|
|
62
|
+
def shim_files(self, config: ServiceConfig) -> Dict[str, str]:
|
|
63
|
+
return {
|
|
64
|
+
"index.py": (
|
|
65
|
+
"# Generated by py-serverless-kit. FunctionGraph handler: index.handler\n"
|
|
66
|
+
"from py_serverless_kit.runtime.adapters.huawei import handler # noqa: F401\n"
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
def provider_block(self, ctx: RenderContext) -> Dict[str, Any]:
|
|
71
|
+
block: Dict[str, Any] = {"region": ctx.region}
|
|
72
|
+
for key in ("profile", "enterprise_project_id"):
|
|
73
|
+
if ctx.opt(key):
|
|
74
|
+
block[key] = ctx.opt(key)
|
|
75
|
+
return block
|
|
76
|
+
|
|
77
|
+
def physical_name(self, service: str, stage: str, logical: str) -> str:
|
|
78
|
+
name = super().physical_name(service, stage, logical)
|
|
79
|
+
return name if re.match(r"[a-z]", name) else f"f{name}"[: self.max_name_length]
|
|
80
|
+
|
|
81
|
+
def extra_validation(self, config: ServiceConfig) -> List[Issue]:
|
|
82
|
+
issues = []
|
|
83
|
+
has_http = any(fn.events_of("http") for fn in config.functions.values())
|
|
84
|
+
if has_http and not config.provider.options.get("apig_instance_id"):
|
|
85
|
+
issues.append(
|
|
86
|
+
Issue(
|
|
87
|
+
"error",
|
|
88
|
+
"HTTP events need a dedicated API Gateway instance: set provider.options.apig_instance_id",
|
|
89
|
+
"provider.options",
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
if config.provider.iam.statements:
|
|
93
|
+
issues.append(
|
|
94
|
+
Issue(
|
|
95
|
+
"warning",
|
|
96
|
+
"iam.statements are ignored on Huawei; set iam.role to an IAM agency name",
|
|
97
|
+
"provider.iam",
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
for name, fn in config.functions.items():
|
|
101
|
+
for ev in fn.events_of("http"):
|
|
102
|
+
if ev.auth not in _AUTH:
|
|
103
|
+
issues.append(
|
|
104
|
+
Issue(
|
|
105
|
+
"error",
|
|
106
|
+
f"http auth `{ev.auth}` is not supported on Huawei (none, iam, key)",
|
|
107
|
+
f"functions.{name}",
|
|
108
|
+
)
|
|
109
|
+
)
|
|
110
|
+
return issues
|
|
111
|
+
|
|
112
|
+
# ------------------------------------------------------------------ render
|
|
113
|
+
def render_resources(self, doc: TerraformDoc, ctx: RenderContext) -> None:
|
|
114
|
+
cfg = ctx.config
|
|
115
|
+
fn_out: Dict[str, Dict[str, Any]] = {}
|
|
116
|
+
gateway: Optional[Dict[str, str]] = None
|
|
117
|
+
for name, fn in ctx.function_items():
|
|
118
|
+
res = self._function(doc, ctx, name, fn)
|
|
119
|
+
out: Dict[str, Any] = {
|
|
120
|
+
"id": ref(res, "name"),
|
|
121
|
+
"arn": ref(res, "urn"),
|
|
122
|
+
"console": f"https://console.huaweicloud.com/functiongraph/?region={ctx.region}#/serverless/functions",
|
|
123
|
+
}
|
|
124
|
+
http_events = fn.events_of("http")
|
|
125
|
+
if http_events:
|
|
126
|
+
gateway = gateway or self._gateway(doc, ctx)
|
|
127
|
+
self._apis(doc, ctx, gateway, name, res, fn, http_events)
|
|
128
|
+
out["base_url"] = gateway["url"]
|
|
129
|
+
for i, ev in enumerate(fn.events_of("schedule")):
|
|
130
|
+
self._timer(doc, name, res, i, ev)
|
|
131
|
+
for ev in fn.events_of("storage"):
|
|
132
|
+
self._obs(doc, ctx, name, res, ev)
|
|
133
|
+
for ev in fn.events_of("topic"):
|
|
134
|
+
self._smn(doc, ctx, name, res, ev)
|
|
135
|
+
fn_out[name] = out
|
|
136
|
+
|
|
137
|
+
wf_out = {n: {"id": ref(FUNCTION, tf_id(f"{n}-orchestrator"), "name")} for n in sorted(cfg.workflows)}
|
|
138
|
+
doc.output(
|
|
139
|
+
"pskit",
|
|
140
|
+
{"functions": fn_out, "workflows": wf_out, "endpoint": gateway["url"] if gateway else None},
|
|
141
|
+
description="py-serverless-kit deployment summary",
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# ------------------------------------------------------------------ pieces
|
|
145
|
+
def _function(self, doc: TerraformDoc, ctx: RenderContext, name: str, fn: FunctionConfig) -> str:
|
|
146
|
+
cfg = ctx.config
|
|
147
|
+
body: Dict[str, Any] = {
|
|
148
|
+
"name": ctx.physical_name(name),
|
|
149
|
+
"app": ctx.opt("app", "default"),
|
|
150
|
+
"description": fn.description[:512],
|
|
151
|
+
"handler": "index.handler",
|
|
152
|
+
"runtime": self.runtime_id(cfg.runtime_of(fn)),
|
|
153
|
+
"memory_size": cfg.memory_of(fn),
|
|
154
|
+
"timeout": cfg.timeout_of(fn),
|
|
155
|
+
"code_type": "zip",
|
|
156
|
+
"code_filename": "artifact.zip",
|
|
157
|
+
"func_code": '${filebase64("${path.module}/artifact.zip")}',
|
|
158
|
+
"user_data": escape(json.dumps(ctx.environment(name, fn), sort_keys=True)),
|
|
159
|
+
"tags": ctx.tags(fn),
|
|
160
|
+
}
|
|
161
|
+
agency = fn.iam.role or cfg.provider.iam.role
|
|
162
|
+
if agency:
|
|
163
|
+
body["agency"] = agency
|
|
164
|
+
if fn.concurrency:
|
|
165
|
+
body["concurrency_num"] = fn.concurrency
|
|
166
|
+
body.update(fn.options) # escape hatch: vpc_id, network_id, depend_list, ...
|
|
167
|
+
return doc.resource(FUNCTION, name, body)
|
|
168
|
+
|
|
169
|
+
def _gateway(self, doc: TerraformDoc, ctx: RenderContext) -> Dict[str, str]:
|
|
170
|
+
instance = ctx.opt("apig_instance_id")
|
|
171
|
+
group = doc.resource(
|
|
172
|
+
"huaweicloud_apig_group",
|
|
173
|
+
"http",
|
|
174
|
+
{
|
|
175
|
+
"instance_id": instance,
|
|
176
|
+
"name": re.sub(r"[^A-Za-z0-9_]", "_", ctx.prefix)[:64],
|
|
177
|
+
"description": f"py-serverless-kit {ctx.service} ({ctx.stage})",
|
|
178
|
+
"domain_access_enabled": True,
|
|
179
|
+
},
|
|
180
|
+
)
|
|
181
|
+
env = doc.resource(
|
|
182
|
+
"huaweicloud_apig_environment",
|
|
183
|
+
"stage",
|
|
184
|
+
{"instance_id": instance, "name": ("pskit_" + re.sub(r"[^A-Za-z0-9_]", "_", ctx.stage))[:64]},
|
|
185
|
+
)
|
|
186
|
+
groups = doc.data("huaweicloud_apig_groups", "http", {"instance_id": instance, "group_id": ref(group, "id")})
|
|
187
|
+
return {
|
|
188
|
+
"instance": instance,
|
|
189
|
+
"group": group,
|
|
190
|
+
"env": env,
|
|
191
|
+
"url": "https://" + ref(groups, "groups[0].sl_domain"),
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
def _apis(
|
|
195
|
+
self,
|
|
196
|
+
doc: TerraformDoc,
|
|
197
|
+
ctx: RenderContext,
|
|
198
|
+
gw: Dict[str, str],
|
|
199
|
+
name: str,
|
|
200
|
+
fn_res: str,
|
|
201
|
+
fn: FunctionConfig,
|
|
202
|
+
events: List[Any],
|
|
203
|
+
) -> None:
|
|
204
|
+
for i, ev in enumerate(events):
|
|
205
|
+
path, matching = _gateway_path(ev.path)
|
|
206
|
+
body: Dict[str, Any] = {
|
|
207
|
+
"instance_id": gw["instance"],
|
|
208
|
+
"group_id": ref(gw["group"], "id"),
|
|
209
|
+
"name": re.sub(r"[^A-Za-z0-9_]", "_", f"{name}_{i}")[:64],
|
|
210
|
+
"type": "Public",
|
|
211
|
+
"request_protocol": "BOTH",
|
|
212
|
+
"request_method": ev.method,
|
|
213
|
+
"request_path": path,
|
|
214
|
+
"matching": matching,
|
|
215
|
+
"security_authentication": _AUTH.get(ev.auth, "NONE"),
|
|
216
|
+
"cors": ev.cors_config is not None,
|
|
217
|
+
"description": f"{ev.method} {ev.path} -> {name}",
|
|
218
|
+
"func_graph": {
|
|
219
|
+
"function_urn": ref(fn_res, "urn"),
|
|
220
|
+
"invocation_type": "sync",
|
|
221
|
+
"network_type": "NON-VPC",
|
|
222
|
+
"timeout": min(ctx.config.timeout_of(fn) * 1000, 600000),
|
|
223
|
+
},
|
|
224
|
+
}
|
|
225
|
+
params = re.findall(r"\{([A-Za-z_]\w*)\}", path)
|
|
226
|
+
if params:
|
|
227
|
+
body["request_params"] = [
|
|
228
|
+
{"name": p, "location": "PATH", "type": "STRING", "required": True, "passthrough": True}
|
|
229
|
+
for p in params
|
|
230
|
+
]
|
|
231
|
+
api = doc.resource("huaweicloud_apig_api", f"{name}-{i}", body)
|
|
232
|
+
doc.resource(
|
|
233
|
+
"huaweicloud_apig_api_publishment",
|
|
234
|
+
f"{name}-{i}",
|
|
235
|
+
{"instance_id": gw["instance"], "api_id": ref(api, "id"), "env_id": ref(gw["env"], "id")},
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
def _timer(self, doc: TerraformDoc, name: str, fn_res: str, i: int, ev: Any) -> None:
|
|
239
|
+
timer: Dict[str, Any] = {
|
|
240
|
+
"name": re.sub(r"[^A-Za-z0-9_-]", "-", ev.name or f"timer-{i}")[:64],
|
|
241
|
+
"schedule_type": "Rate" if ev.rate else "Cron",
|
|
242
|
+
"schedule": rate_expression(ev) if ev.rate else quartz_cron(ev),
|
|
243
|
+
"additional_information": escape(json.dumps(envelope("schedule", ev.input, source="huawei.timer"))),
|
|
244
|
+
}
|
|
245
|
+
doc.resource(
|
|
246
|
+
TRIGGER,
|
|
247
|
+
f"{name}-timer-{i}",
|
|
248
|
+
{
|
|
249
|
+
"function_urn": ref(fn_res, "urn"),
|
|
250
|
+
"type": "TIMER",
|
|
251
|
+
"status": "ACTIVE" if ev.enabled else "DISABLED",
|
|
252
|
+
"timer": timer,
|
|
253
|
+
},
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
def _obs(self, doc: TerraformDoc, ctx: RenderContext, name: str, fn_res: str, ev: Any) -> None:
|
|
257
|
+
label = f"b-{ev.bucket}"
|
|
258
|
+
body: Dict[str, Any] = {
|
|
259
|
+
"function_urn": ref(fn_res, "urn"),
|
|
260
|
+
"type": "OBS",
|
|
261
|
+
"status": "ACTIVE",
|
|
262
|
+
"obs": {
|
|
263
|
+
"bucket_name": ev.bucket,
|
|
264
|
+
"event_notification_name": re.sub(r"[^A-Za-z0-9_-]", "-", f"pskit-{name}")[:64],
|
|
265
|
+
"events": [{"created": "ObjectCreated", "deleted": "ObjectRemoved"}[e] for e in ev.events],
|
|
266
|
+
},
|
|
267
|
+
}
|
|
268
|
+
if ev.prefix:
|
|
269
|
+
body["obs"]["prefix"] = ev.prefix
|
|
270
|
+
if ev.suffix:
|
|
271
|
+
body["obs"]["suffix"] = ev.suffix
|
|
272
|
+
if not ev.existing:
|
|
273
|
+
if not doc.has_resource("huaweicloud_obs_bucket", label):
|
|
274
|
+
doc.resource(
|
|
275
|
+
"huaweicloud_obs_bucket", label, {"bucket": ev.bucket, "acl": "private", "tags": ctx.tags()}
|
|
276
|
+
)
|
|
277
|
+
body["depends_on"] = [f"huaweicloud_obs_bucket.{tf_id(label)}"]
|
|
278
|
+
doc.resource(TRIGGER, f"{name}-obs-{ev.bucket}", body)
|
|
279
|
+
|
|
280
|
+
def _smn(self, doc: TerraformDoc, ctx: RenderContext, name: str, fn_res: str, ev: Any) -> None:
|
|
281
|
+
label = f"t-{ev.name}"
|
|
282
|
+
if ev.existing:
|
|
283
|
+
topic_urn = ref(doc.data("huaweicloud_smn_topics", label, {"name": ev.name}), "topics[0].topic_urn")
|
|
284
|
+
else:
|
|
285
|
+
if not doc.has_resource("huaweicloud_smn_topic", label):
|
|
286
|
+
doc.resource("huaweicloud_smn_topic", label, {"name": ev.name, "tags": ctx.tags()})
|
|
287
|
+
topic_urn = ref("huaweicloud_smn_topic", tf_id(label), "topic_urn")
|
|
288
|
+
doc.resource(
|
|
289
|
+
TRIGGER,
|
|
290
|
+
f"{name}-smn-{ev.name}",
|
|
291
|
+
{"function_urn": ref(fn_res, "urn"), "type": "SMN", "status": "ACTIVE", "smn": {"topic_urn": topic_urn}},
|
|
292
|
+
)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""IBM Cloud: Code Engine functions (HTTP endpoints)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
6
|
+
|
|
7
|
+
from ..config.schema import ServiceConfig
|
|
8
|
+
from .base import HTTP, Issue, Provider, RenderContext
|
|
9
|
+
from .terraform_doc import TerraformDoc, escape, ref, tf_id
|
|
10
|
+
|
|
11
|
+
FUNCTION = "ibm_code_engine_function"
|
|
12
|
+
# Code Engine function CPU / memory combinations (vCPU, memory).
|
|
13
|
+
_SIZES: Tuple[Tuple[str, str, int], ...] = (
|
|
14
|
+
("0.125", "0.5G", 512),
|
|
15
|
+
("0.25", "1G", 1024),
|
|
16
|
+
("0.5", "2G", 2048),
|
|
17
|
+
("1", "4G", 4096),
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def size_for(memory_mb: int) -> Tuple[str, str]:
|
|
22
|
+
for cpu, mem, mb in _SIZES:
|
|
23
|
+
if memory_mb <= mb:
|
|
24
|
+
return cpu, mem
|
|
25
|
+
return _SIZES[-1][0], _SIZES[-1][1]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class IbmProvider(Provider):
|
|
29
|
+
name = "ibm"
|
|
30
|
+
display_name = "IBM Cloud Code Engine"
|
|
31
|
+
tier = "beta"
|
|
32
|
+
terraform_source = "IBM-Cloud/ibm"
|
|
33
|
+
terraform_version = ">= 1.70"
|
|
34
|
+
default_region = "us-south"
|
|
35
|
+
capabilities = frozenset({HTTP})
|
|
36
|
+
max_name_length = 63
|
|
37
|
+
runtimes = {"python3.11": "python-3.11"}
|
|
38
|
+
vendor_dependencies = True
|
|
39
|
+
max_timeout = 120
|
|
40
|
+
notes = (
|
|
41
|
+
"Every Code Engine function gets a public HTTPS endpoint; routes are matched by the pskit runtime. "
|
|
42
|
+
"No schedule/queue/storage/topic triggers (the Terraform provider has no function event "
|
|
43
|
+
"subscriptions). Code is uploaded inline as a base64 data URL, so keep bundles small or set "
|
|
44
|
+
"options.code_reference to a code bundle image. Auth: set IC_API_KEY."
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# ------------------------------------------------------------------ bundle
|
|
48
|
+
def shim_files(self, config: ServiceConfig) -> Dict[str, str]:
|
|
49
|
+
return {
|
|
50
|
+
"__main__.py": (
|
|
51
|
+
"# Generated by py-serverless-kit. Code Engine entrypoint: main(params)\n"
|
|
52
|
+
"from py_serverless_kit.runtime.adapters.ibm import main # noqa: F401\n"
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
def provider_block(self, ctx: RenderContext) -> Dict[str, Any]:
|
|
57
|
+
block: Dict[str, Any] = {"region": ctx.region}
|
|
58
|
+
if ctx.opt("resource_group"):
|
|
59
|
+
block["resource_group"] = ctx.opt("resource_group")
|
|
60
|
+
return block
|
|
61
|
+
|
|
62
|
+
def extra_validation(self, config: ServiceConfig) -> List[Issue]:
|
|
63
|
+
issues: List[Issue] = []
|
|
64
|
+
for name, fn in config.functions.items():
|
|
65
|
+
for ev in fn.events_of("http"):
|
|
66
|
+
if ev.auth != "none":
|
|
67
|
+
issues.append(
|
|
68
|
+
Issue(
|
|
69
|
+
"error",
|
|
70
|
+
f"auth `{ev.auth}` is not supported on Code Engine functions; "
|
|
71
|
+
"validate tokens in the handler",
|
|
72
|
+
f"functions.{name}",
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
if config.memory_of(fn) > _SIZES[-1][2]:
|
|
76
|
+
issues.append(
|
|
77
|
+
Issue("error", "Code Engine functions support at most 4096 MB", f"functions.{name}.memory")
|
|
78
|
+
)
|
|
79
|
+
return issues
|
|
80
|
+
|
|
81
|
+
# ------------------------------------------------------------------ render
|
|
82
|
+
def _project(self, doc: TerraformDoc, ctx: RenderContext) -> str:
|
|
83
|
+
if ctx.opt("project_id"):
|
|
84
|
+
return str(ctx.opt("project_id"))
|
|
85
|
+
body: Dict[str, Any] = {"name": ctx.prefix}
|
|
86
|
+
if ctx.opt("resource_group_id"):
|
|
87
|
+
body["resource_group_id"] = ctx.opt("resource_group_id")
|
|
88
|
+
return ref(doc.resource("ibm_code_engine_project", "project", body), "project_id")
|
|
89
|
+
|
|
90
|
+
def render_resources(self, doc: TerraformDoc, ctx: RenderContext) -> None:
|
|
91
|
+
cfg = ctx.config
|
|
92
|
+
project = self._project(doc, ctx)
|
|
93
|
+
code_ref = ctx.opt("code_reference") or 'data:text/plain;base64,${filebase64("${path.module}/artifact.zip")}'
|
|
94
|
+
fn_out: Dict[str, Dict[str, Any]] = {}
|
|
95
|
+
endpoint: Optional[str] = None
|
|
96
|
+
for name, fn in ctx.function_items():
|
|
97
|
+
cpu, mem = size_for(cfg.memory_of(fn))
|
|
98
|
+
env = [
|
|
99
|
+
{"type": "literal", "name": k, "value": escape(v)} for k, v in sorted(ctx.environment(name, fn).items())
|
|
100
|
+
]
|
|
101
|
+
body: Dict[str, Any] = {
|
|
102
|
+
"project_id": project,
|
|
103
|
+
"name": ctx.physical_name(name),
|
|
104
|
+
"runtime": self.runtime_id(cfg.runtime_of(fn)),
|
|
105
|
+
"code_reference": code_ref,
|
|
106
|
+
"code_binary": not ctx.opt("code_reference"),
|
|
107
|
+
"code_main": "main",
|
|
108
|
+
"scale_cpu_limit": cpu,
|
|
109
|
+
"scale_memory_limit": mem,
|
|
110
|
+
"scale_max_execution_time": cfg.timeout_of(fn),
|
|
111
|
+
"managed_domain_mappings": fn.options.get("visibility", ctx.opt("visibility", "local_public")),
|
|
112
|
+
"run_env_variables": env,
|
|
113
|
+
}
|
|
114
|
+
if fn.concurrency:
|
|
115
|
+
body["scale_concurrency"] = fn.concurrency
|
|
116
|
+
body.update({k: v for k, v in fn.options.items() if k != "visibility"})
|
|
117
|
+
res = doc.resource(FUNCTION, name, body)
|
|
118
|
+
out: Dict[str, Any] = {
|
|
119
|
+
"id": ref(res, "function_id"),
|
|
120
|
+
"url": ref(res, "endpoint"),
|
|
121
|
+
"console": f"https://cloud.ibm.com/codeengine/project/{ctx.region}/{project}/functions",
|
|
122
|
+
}
|
|
123
|
+
if fn.events_of("http"):
|
|
124
|
+
out["base_url"] = ref(res, "endpoint")
|
|
125
|
+
endpoint = endpoint or ref(res, "endpoint")
|
|
126
|
+
fn_out[name] = out
|
|
127
|
+
wf_out = {n: {"id": ref(FUNCTION, tf_id(f"{n}-orchestrator"), "endpoint")} for n in cfg.workflows}
|
|
128
|
+
doc.output(
|
|
129
|
+
"pskit",
|
|
130
|
+
{"functions": fn_out, "workflows": wf_out, "endpoint": endpoint},
|
|
131
|
+
description="py-serverless-kit deployment summary",
|
|
132
|
+
)
|