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,417 @@
|
|
|
1
|
+
"""Azure: Linux Function App (Consumption plan, Python v2 programming model).
|
|
2
|
+
|
|
3
|
+
One Function App hosts every function of a service. The bundle carries a
|
|
4
|
+
generated ``function_app.py`` that registers a decorated wrapper per trigger:
|
|
5
|
+
|
|
6
|
+
* ``http`` -> ``@app.route`` (``host.json`` sets ``routePrefix`` to ``""`` so the
|
|
7
|
+
configured paths are served as-is, without Azure's default ``/api`` prefix);
|
|
8
|
+
* ``schedule`` -> ``@app.timer_trigger`` (NCRONTAB, always UTC on Linux Consumption);
|
|
9
|
+
* ``queue`` -> Storage Queue in the app's storage account;
|
|
10
|
+
* ``storage`` -> Blob trigger on a container in the app's storage account (created only);
|
|
11
|
+
* ``topic`` -> Service Bus topic + one subscription per function.
|
|
12
|
+
|
|
13
|
+
Dependencies are installed by the remote Oryx build (``requirements.txt``).
|
|
14
|
+
Workflows run on the embedded engine (an ``<name>-orchestrator`` HTTP function).
|
|
15
|
+
|
|
16
|
+
``azurerm`` >= 4 requires a subscription id: set ``provider.options.subscription_id``
|
|
17
|
+
or the ``ARM_SUBSCRIPTION_ID`` environment variable.
|
|
18
|
+
|
|
19
|
+
Options: ``subscription_id``, ``resource_group`` (existing), ``plan_sku`` (default Y1),
|
|
20
|
+
``app_insights`` (default true), ``storage_connection`` (app-setting name for
|
|
21
|
+
``existing`` queues / buckets in another account), ``servicebus_namespace_id`` and
|
|
22
|
+
``servicebus_connection`` (for ``existing`` topics).
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import json
|
|
28
|
+
import re
|
|
29
|
+
from typing import Any, Dict, List, Optional
|
|
30
|
+
|
|
31
|
+
from ..config.schema import ServiceConfig
|
|
32
|
+
from . import schedule as sched
|
|
33
|
+
from .base import HTTP, QUEUE, SCHEDULE, STORAGE, TOPIC, Issue, Provider, RenderContext, stable_suffix
|
|
34
|
+
from .terraform_doc import TerraformDoc, escape, ref, tf_id
|
|
35
|
+
|
|
36
|
+
APP = "azurerm_linux_function_app"
|
|
37
|
+
_PER_FUNCTION_ENV = ("PSKIT_FUNCTION", "PSKIT_HANDLER")
|
|
38
|
+
_STORAGE_NAME = re.compile(r"^[a-z0-9](?!.*--)[a-z0-9-]{1,61}[a-z0-9]$")
|
|
39
|
+
DEFAULT_CONNECTION = "AzureWebJobsStorage"
|
|
40
|
+
SERVICEBUS_SETTING = "PSKIT_SERVICEBUS"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def py_ident(text: str) -> str:
|
|
44
|
+
ident = re.sub(r"\W", "_", text)
|
|
45
|
+
return ident if re.match(r"[A-Za-z]", ident) else "f_" + ident
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def azure_route(path: str) -> str:
|
|
49
|
+
route = path.strip("/")
|
|
50
|
+
return re.sub(r"\{(\w+)\+\}", r"{*\1}", route)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _hms(seconds: int) -> str:
|
|
54
|
+
return f"{seconds // 3600:02d}:{seconds % 3600 // 60:02d}:{seconds % 60:02d}"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class AzureProvider(Provider):
|
|
58
|
+
name = "azure"
|
|
59
|
+
display_name = "Azure Functions"
|
|
60
|
+
tier = "stable"
|
|
61
|
+
terraform_source = "hashicorp/azurerm"
|
|
62
|
+
terraform_version = ">= 4.9"
|
|
63
|
+
terraform_local_name = "azurerm"
|
|
64
|
+
default_region = "eastus"
|
|
65
|
+
capabilities = frozenset({HTTP, SCHEDULE, QUEUE, STORAGE, TOPIC})
|
|
66
|
+
max_name_length = 60
|
|
67
|
+
name_pattern = r"[^a-z0-9-]"
|
|
68
|
+
runtimes = {f"python3.{v}": f"3.{v}" for v in (9, 10, 11, 12)}
|
|
69
|
+
vendor_dependencies = False
|
|
70
|
+
extra_requirements = ["azure-functions"]
|
|
71
|
+
max_timeout = 600
|
|
72
|
+
notes = "Linux Consumption Function App, Python v2 model. Workflows use the embedded engine."
|
|
73
|
+
|
|
74
|
+
# ------------------------------------------------------------------ naming
|
|
75
|
+
def app_name(self, service: str, stage: str) -> str:
|
|
76
|
+
base = self.physical_name(service, stage, "")
|
|
77
|
+
return f"{base[:51].rstrip('-')}-{stable_suffix(service, stage, 'func', length=8)}"
|
|
78
|
+
|
|
79
|
+
# ------------------------------------------------------------------ bundle
|
|
80
|
+
def shim_files(self, config: ServiceConfig) -> Dict[str, str]:
|
|
81
|
+
opts = config.provider.options
|
|
82
|
+
lines = [
|
|
83
|
+
"# Generated by py-serverless-kit. Do not edit - regenerate with `pskit package`.",
|
|
84
|
+
"import azure.functions as func",
|
|
85
|
+
"",
|
|
86
|
+
"from py_serverless_kit.runtime.adapters import azure as _pskit",
|
|
87
|
+
"",
|
|
88
|
+
"app = func.FunctionApp(http_auth_level=func.AuthLevel.ANONYMOUS)",
|
|
89
|
+
"",
|
|
90
|
+
]
|
|
91
|
+
used: set = set()
|
|
92
|
+
|
|
93
|
+
def unique(base: str) -> str:
|
|
94
|
+
ident = py_ident(base)
|
|
95
|
+
candidate, n = ident, 1
|
|
96
|
+
while candidate.lower() in used:
|
|
97
|
+
n += 1
|
|
98
|
+
candidate = f"{ident}_{n}"
|
|
99
|
+
used.add(candidate.lower())
|
|
100
|
+
return candidate
|
|
101
|
+
|
|
102
|
+
for name, fn in sorted(config.functions.items()):
|
|
103
|
+
for i, ev in enumerate(fn.events):
|
|
104
|
+
c = ev.config
|
|
105
|
+
ident = unique(f"{name}_{ev.type}_{i}")
|
|
106
|
+
lines.append("")
|
|
107
|
+
lines.append(f'@app.function_name(name="{ident}")')
|
|
108
|
+
if ev.type == "http":
|
|
109
|
+
auth = "FUNCTION" if c.auth == "key" else "ANONYMOUS"
|
|
110
|
+
methods = "" if c.method == "ANY" else f", methods=[{c.method!r}]"
|
|
111
|
+
lines.append(
|
|
112
|
+
f"@app.route(route={azure_route(c.path)!r}{methods}, auth_level=func.AuthLevel.{auth})"
|
|
113
|
+
)
|
|
114
|
+
lines.append(f"def {ident}(req: func.HttpRequest) -> func.HttpResponse:")
|
|
115
|
+
lines.append(f" return _pskit.http(req, {name!r})")
|
|
116
|
+
elif ev.type == "schedule":
|
|
117
|
+
lines.append(
|
|
118
|
+
f"@app.timer_trigger(schedule={sched.ncrontab(c)!r}, arg_name='timer', run_on_startup=False)"
|
|
119
|
+
)
|
|
120
|
+
lines.append(f"def {ident}(timer: func.TimerRequest) -> None:")
|
|
121
|
+
lines.append(f" _pskit.timer(timer, {name!r}, {c.input!r})")
|
|
122
|
+
elif ev.type == "queue":
|
|
123
|
+
conn = opts.get("storage_connection", DEFAULT_CONNECTION) if c.existing else DEFAULT_CONNECTION
|
|
124
|
+
lines.append(f"@app.queue_trigger(arg_name='msg', queue_name={c.name!r}, connection={conn!r})")
|
|
125
|
+
lines.append(f"def {ident}(msg: func.QueueMessage) -> None:")
|
|
126
|
+
lines.append(f" _pskit.queue(msg, {name!r})")
|
|
127
|
+
elif ev.type == "storage":
|
|
128
|
+
conn = opts.get("storage_connection", DEFAULT_CONNECTION) if c.existing else DEFAULT_CONNECTION
|
|
129
|
+
path = f"{c.bucket}/{c.prefix or ''}{{name}}{c.suffix or ''}"
|
|
130
|
+
lines.append(f"@app.blob_trigger(arg_name='blob', path={path!r}, connection={conn!r})")
|
|
131
|
+
lines.append(f"def {ident}(blob: func.InputStream) -> None:")
|
|
132
|
+
lines.append(f" _pskit.blob(blob, {name!r}, {c.bucket!r})")
|
|
133
|
+
elif ev.type == "topic":
|
|
134
|
+
conn = opts.get("servicebus_connection", SERVICEBUS_SETTING) if c.existing else SERVICEBUS_SETTING
|
|
135
|
+
lines.append(
|
|
136
|
+
f"@app.service_bus_topic_trigger(arg_name='msg', topic_name={c.name!r}, "
|
|
137
|
+
f"subscription_name={self.subscription_name(name)!r}, connection={conn!r})"
|
|
138
|
+
)
|
|
139
|
+
lines.append(f"def {ident}(msg: func.ServiceBusMessage) -> None:")
|
|
140
|
+
lines.append(f" _pskit.service_bus(msg, {name!r})")
|
|
141
|
+
if not fn.events:
|
|
142
|
+
# Invoke-only functions (workflow tasks, invoke()) get a private POST route.
|
|
143
|
+
ident = unique(f"{name}_invoke")
|
|
144
|
+
lines.append("")
|
|
145
|
+
lines.append(f'@app.function_name(name="{ident}")')
|
|
146
|
+
route = "_pskit/invoke/" + name
|
|
147
|
+
lines.append(f"@app.route(route={route!r}, methods=['POST'], auth_level=func.AuthLevel.FUNCTION)")
|
|
148
|
+
lines.append(f"def {ident}(req: func.HttpRequest) -> func.HttpResponse:")
|
|
149
|
+
lines.append(f" return _pskit.http(req, {name!r})")
|
|
150
|
+
timeout = max([config.timeout_of(fn) for fn in config.functions.values()] or [config.provider.timeout])
|
|
151
|
+
host = {
|
|
152
|
+
"version": "2.0",
|
|
153
|
+
"extensionBundle": {"id": "Microsoft.Azure.Functions.ExtensionBundle", "version": "[4.*, 5.0.0)"},
|
|
154
|
+
"extensions": {"http": {"routePrefix": ""}},
|
|
155
|
+
"functionTimeout": _hms(min(timeout, self.max_timeout)),
|
|
156
|
+
"logging": {"logLevel": {"default": "Information"}},
|
|
157
|
+
}
|
|
158
|
+
return {"function_app.py": "\n".join(lines) + "\n", "host.json": json.dumps(host, indent=2) + "\n"}
|
|
159
|
+
|
|
160
|
+
@staticmethod
|
|
161
|
+
def subscription_name(function: str) -> str:
|
|
162
|
+
return re.sub(r"[^A-Za-z0-9_.-]", "-", function)[:50]
|
|
163
|
+
|
|
164
|
+
# ------------------------------------------------------------------ validation
|
|
165
|
+
def provider_block(self, ctx: RenderContext) -> Dict[str, Any]:
|
|
166
|
+
block: Dict[str, Any] = {"features": {}}
|
|
167
|
+
if ctx.opt("subscription_id"):
|
|
168
|
+
block["subscription_id"] = ctx.opt("subscription_id")
|
|
169
|
+
return block
|
|
170
|
+
|
|
171
|
+
def extra_validation(self, config: ServiceConfig) -> List[Issue]:
|
|
172
|
+
issues: List[Issue] = []
|
|
173
|
+
opts = config.provider.options
|
|
174
|
+
env_seen: Dict[str, str] = {}
|
|
175
|
+
if config.provider.iam.statements:
|
|
176
|
+
issues.append(Issue("warning", "iam.statements are AWS-style and ignored on Azure", "provider.iam"))
|
|
177
|
+
for name, fn in config.functions.items():
|
|
178
|
+
where = f"functions.{name}"
|
|
179
|
+
for key, value in fn.environment.items():
|
|
180
|
+
if key in env_seen and env_seen[key] != value:
|
|
181
|
+
issues.append(
|
|
182
|
+
Issue(
|
|
183
|
+
"warning",
|
|
184
|
+
f"environment {key} differs between functions; Azure app "
|
|
185
|
+
"settings are shared by the whole Function App (last one wins)",
|
|
186
|
+
where,
|
|
187
|
+
)
|
|
188
|
+
)
|
|
189
|
+
env_seen[key] = value
|
|
190
|
+
for ev in fn.events_of("http"):
|
|
191
|
+
if ev.auth in ("iam", "jwt"):
|
|
192
|
+
issues.append(
|
|
193
|
+
Issue(
|
|
194
|
+
"error",
|
|
195
|
+
f"http auth {ev.auth!r} is not supported on Azure; use none or key "
|
|
196
|
+
"(or put API Management / Easy Auth in front)",
|
|
197
|
+
where,
|
|
198
|
+
)
|
|
199
|
+
)
|
|
200
|
+
for ev in fn.events_of("schedule"):
|
|
201
|
+
if ev.timezone.upper() != "UTC":
|
|
202
|
+
issues.append(Issue("warning", "Linux Consumption timers run in UTC; timezone is ignored", where))
|
|
203
|
+
for ev in fn.events_of("queue"):
|
|
204
|
+
if not _STORAGE_NAME.match(ev.name):
|
|
205
|
+
issues.append(
|
|
206
|
+
Issue("error", f"queue name {ev.name!r} must be 3-63 lowercase letters, digits, dashes", where)
|
|
207
|
+
)
|
|
208
|
+
if ev.fifo:
|
|
209
|
+
issues.append(Issue("warning", "Storage Queues are not FIFO; queue.fifo is ignored", where))
|
|
210
|
+
if ev.dead_letter:
|
|
211
|
+
issues.append(
|
|
212
|
+
Issue(
|
|
213
|
+
"warning",
|
|
214
|
+
"Storage Queues move poison messages to '<queue>-poison'; dead_letter is ignored",
|
|
215
|
+
where,
|
|
216
|
+
)
|
|
217
|
+
)
|
|
218
|
+
for ev in fn.events_of("storage"):
|
|
219
|
+
if not _STORAGE_NAME.match(ev.bucket):
|
|
220
|
+
issues.append(
|
|
221
|
+
Issue(
|
|
222
|
+
"error",
|
|
223
|
+
f"container name {ev.bucket!r} must be 3-63 lowercase letters, digits, dashes",
|
|
224
|
+
where,
|
|
225
|
+
)
|
|
226
|
+
)
|
|
227
|
+
if "deleted" in ev.events:
|
|
228
|
+
issues.append(Issue("error", "Azure blob triggers only fire on created/updated blobs", where))
|
|
229
|
+
for ev in fn.events_of("topic"):
|
|
230
|
+
if ev.existing and not (opts.get("servicebus_namespace_id") and opts.get("servicebus_connection")):
|
|
231
|
+
issues.append(
|
|
232
|
+
Issue(
|
|
233
|
+
"error",
|
|
234
|
+
"existing topics need options.servicebus_namespace_id and "
|
|
235
|
+
"options.servicebus_connection (name of an app setting)",
|
|
236
|
+
where,
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
if ev.filter:
|
|
240
|
+
issues.append(
|
|
241
|
+
Issue("warning", "topic.filter is not applied on Azure Service Bus subscriptions", where)
|
|
242
|
+
)
|
|
243
|
+
return issues
|
|
244
|
+
|
|
245
|
+
# ------------------------------------------------------------------ render
|
|
246
|
+
def render_resources(self, doc: TerraformDoc, ctx: RenderContext) -> None:
|
|
247
|
+
cfg = ctx.config
|
|
248
|
+
loc = ctx.region
|
|
249
|
+
tags = ctx.tags()
|
|
250
|
+
if ctx.opt("resource_group"):
|
|
251
|
+
rg = doc.data("azurerm_resource_group", "rg", {"name": ctx.opt("resource_group")})
|
|
252
|
+
else:
|
|
253
|
+
rg = doc.resource(
|
|
254
|
+
"azurerm_resource_group", "rg", {"name": f"{ctx.prefix}-rg", "location": loc, "tags": tags}
|
|
255
|
+
)
|
|
256
|
+
rg_name = ref(rg, "name")
|
|
257
|
+
|
|
258
|
+
sa_base = re.sub(r"[^a-z0-9]", "", ctx.prefix.lower())[:16] or "pskit"
|
|
259
|
+
sa = doc.resource(
|
|
260
|
+
"azurerm_storage_account",
|
|
261
|
+
"app",
|
|
262
|
+
{
|
|
263
|
+
"name": sa_base + stable_suffix(ctx.prefix, loc, length=8),
|
|
264
|
+
"resource_group_name": rg_name,
|
|
265
|
+
"location": loc,
|
|
266
|
+
"account_tier": "Standard",
|
|
267
|
+
"account_replication_type": "LRS",
|
|
268
|
+
"min_tls_version": "TLS1_2",
|
|
269
|
+
"tags": tags,
|
|
270
|
+
},
|
|
271
|
+
)
|
|
272
|
+
plan = doc.resource(
|
|
273
|
+
"azurerm_service_plan",
|
|
274
|
+
"plan",
|
|
275
|
+
{
|
|
276
|
+
"name": f"{ctx.prefix}-plan",
|
|
277
|
+
"resource_group_name": rg_name,
|
|
278
|
+
"location": loc,
|
|
279
|
+
"os_type": "Linux",
|
|
280
|
+
"sku_name": ctx.opt("plan_sku", "Y1"),
|
|
281
|
+
"tags": tags,
|
|
282
|
+
},
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
site_config: Dict[str, Any] = {
|
|
286
|
+
"application_stack": {"python_version": self.runtime_id(cfg.provider.runtime)},
|
|
287
|
+
}
|
|
288
|
+
if ctx.opt("app_insights", True):
|
|
289
|
+
law = doc.resource(
|
|
290
|
+
"azurerm_log_analytics_workspace",
|
|
291
|
+
"logs",
|
|
292
|
+
{
|
|
293
|
+
"name": f"{ctx.prefix}-logs",
|
|
294
|
+
"resource_group_name": rg_name,
|
|
295
|
+
"location": loc,
|
|
296
|
+
"sku": "PerGB2018",
|
|
297
|
+
"retention_in_days": max(30, cfg.provider.log_retention_days or 30),
|
|
298
|
+
"tags": tags,
|
|
299
|
+
},
|
|
300
|
+
)
|
|
301
|
+
ai = doc.resource(
|
|
302
|
+
"azurerm_application_insights",
|
|
303
|
+
"ai",
|
|
304
|
+
{
|
|
305
|
+
"name": f"{ctx.prefix}-ai",
|
|
306
|
+
"resource_group_name": rg_name,
|
|
307
|
+
"location": loc,
|
|
308
|
+
"workspace_id": ref(law, "id"),
|
|
309
|
+
"application_type": "web",
|
|
310
|
+
"tags": tags,
|
|
311
|
+
},
|
|
312
|
+
)
|
|
313
|
+
site_config["application_insights_connection_string"] = ref(ai, "connection_string")
|
|
314
|
+
cors = [ev.cors_config for fn in cfg.functions.values() for ev in fn.events_of("http") if ev.cors_config]
|
|
315
|
+
if cors:
|
|
316
|
+
site_config["cors"] = {
|
|
317
|
+
"allowed_origins": sorted({o for c in cors for o in c.origins}),
|
|
318
|
+
"support_credentials": any(c.allow_credentials for c in cors),
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
settings: Dict[str, str] = {
|
|
322
|
+
"SCM_DO_BUILD_DURING_DEPLOYMENT": "true",
|
|
323
|
+
"ENABLE_ORYX_BUILD": "true",
|
|
324
|
+
"AzureWebJobsFeatureFlags": "EnableWorkerIndexing",
|
|
325
|
+
}
|
|
326
|
+
for name, fn in ctx.function_items():
|
|
327
|
+
for key, value in ctx.environment(name, fn).items():
|
|
328
|
+
if key not in _PER_FUNCTION_ENV:
|
|
329
|
+
settings[key] = escape(value)
|
|
330
|
+
|
|
331
|
+
# queues / containers / service bus
|
|
332
|
+
for _name, fn in ctx.function_items():
|
|
333
|
+
for ev in fn.events_of("queue"):
|
|
334
|
+
if not ev.existing and not doc.has_resource("azurerm_storage_queue", f"q-{ev.name}"):
|
|
335
|
+
doc.resource(
|
|
336
|
+
"azurerm_storage_queue", f"q-{ev.name}", {"name": ev.name, "storage_account_id": ref(sa, "id")}
|
|
337
|
+
)
|
|
338
|
+
for ev in fn.events_of("storage"):
|
|
339
|
+
if not ev.existing and not doc.has_resource("azurerm_storage_container", f"b-{ev.bucket}"):
|
|
340
|
+
doc.resource(
|
|
341
|
+
"azurerm_storage_container",
|
|
342
|
+
f"b-{ev.bucket}",
|
|
343
|
+
{"name": ev.bucket, "storage_account_id": ref(sa, "id"), "container_access_type": "private"},
|
|
344
|
+
)
|
|
345
|
+
ns: Optional[str] = None
|
|
346
|
+
for name, fn in ctx.function_items():
|
|
347
|
+
for ev in fn.events_of("topic"):
|
|
348
|
+
label = f"t-{ev.name}"
|
|
349
|
+
if ev.existing:
|
|
350
|
+
topic = doc.data(
|
|
351
|
+
"azurerm_servicebus_topic",
|
|
352
|
+
label,
|
|
353
|
+
{"name": ev.name, "namespace_id": ctx.opt("servicebus_namespace_id")},
|
|
354
|
+
)
|
|
355
|
+
else:
|
|
356
|
+
if ns is None:
|
|
357
|
+
ns = doc.resource(
|
|
358
|
+
"azurerm_servicebus_namespace",
|
|
359
|
+
"bus",
|
|
360
|
+
{
|
|
361
|
+
"name": f"{ctx.prefix[:40].rstrip('-')}-{stable_suffix(ctx.prefix, 'bus', length=6)}",
|
|
362
|
+
"resource_group_name": rg_name,
|
|
363
|
+
"location": loc,
|
|
364
|
+
"sku": "Standard",
|
|
365
|
+
"tags": tags,
|
|
366
|
+
},
|
|
367
|
+
)
|
|
368
|
+
settings[SERVICEBUS_SETTING] = ref(ns, "default_primary_connection_string")
|
|
369
|
+
if not doc.has_resource("azurerm_servicebus_topic", label):
|
|
370
|
+
doc.resource(
|
|
371
|
+
"azurerm_servicebus_topic", label, {"name": ev.name, "namespace_id": ref(ns, "id")}
|
|
372
|
+
)
|
|
373
|
+
topic = f"azurerm_servicebus_topic.{tf_id(label)}"
|
|
374
|
+
doc.resource(
|
|
375
|
+
"azurerm_servicebus_subscription",
|
|
376
|
+
f"{name}-{ev.name}",
|
|
377
|
+
{"name": self.subscription_name(name), "topic_id": ref(topic, "id"), "max_delivery_count": 10},
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
app_name = self.app_name(ctx.service, ctx.stage)
|
|
381
|
+
body: Dict[str, Any] = {
|
|
382
|
+
"name": app_name,
|
|
383
|
+
"resource_group_name": rg_name,
|
|
384
|
+
"location": loc,
|
|
385
|
+
"service_plan_id": ref(plan, "id"),
|
|
386
|
+
"storage_account_name": ref(sa, "name"),
|
|
387
|
+
"storage_account_access_key": ref(sa, "primary_access_key"),
|
|
388
|
+
"functions_extension_version": "~4",
|
|
389
|
+
"https_only": True,
|
|
390
|
+
"zip_deploy_file": "${path.module}/artifact.zip",
|
|
391
|
+
"app_settings": settings,
|
|
392
|
+
"site_config": site_config,
|
|
393
|
+
"tags": tags,
|
|
394
|
+
}
|
|
395
|
+
app = doc.resource(APP, "app", body)
|
|
396
|
+
host = "https://" + ref(app, "default_hostname")
|
|
397
|
+
|
|
398
|
+
fn_out: Dict[str, Dict[str, Any]] = {}
|
|
399
|
+
for name, fn in ctx.function_items():
|
|
400
|
+
out: Dict[str, Any] = {
|
|
401
|
+
"id": f"{app_name}/{name}",
|
|
402
|
+
"console": "https://portal.azure.com/#resource" + ref(app, "id") + "/functions",
|
|
403
|
+
}
|
|
404
|
+
http_events = fn.events_of("http")
|
|
405
|
+
if http_events:
|
|
406
|
+
out["base_url"] = host
|
|
407
|
+
out["url"] = host + "/" + azure_route(http_events[0].path)
|
|
408
|
+
fn_out[name] = out
|
|
409
|
+
wf_out = {
|
|
410
|
+
name: {"id": f"{app_name}/{name}-orchestrator", "url": host + f"/workflows/{name}"}
|
|
411
|
+
for name in sorted(cfg.workflows)
|
|
412
|
+
}
|
|
413
|
+
doc.output(
|
|
414
|
+
"pskit",
|
|
415
|
+
{"functions": fn_out, "workflows": wf_out, "endpoint": host},
|
|
416
|
+
description="py-serverless-kit deployment summary",
|
|
417
|
+
)
|