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,211 @@
|
|
|
1
|
+
"""DigitalOcean Functions.
|
|
2
|
+
|
|
3
|
+
The DigitalOcean Terraform provider has no resource for Functions, so pskit
|
|
4
|
+
renders a standard ``doctl serverless`` project (``do_project/project.yml`` +
|
|
5
|
+
``packages/<service>-<stage>/<fn>/``) and deploys it from a ``terraform_data``
|
|
6
|
+
resource with ``local-exec`` provisioners. Terraform therefore tracks *when*
|
|
7
|
+
to redeploy (artifact hash) while ``doctl`` does the upload.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import hashlib
|
|
13
|
+
import shutil
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, Dict, List, Optional
|
|
16
|
+
|
|
17
|
+
import yaml
|
|
18
|
+
|
|
19
|
+
from ..config.schema import ServiceConfig
|
|
20
|
+
from ..runtime.adapters.base import envelope
|
|
21
|
+
from . import schedule as sched
|
|
22
|
+
from .base import HTTP, SCHEDULE, Issue, Provider, RenderContext
|
|
23
|
+
from .terraform_doc import TerraformDoc
|
|
24
|
+
|
|
25
|
+
PROJECT_DIR = "do_project"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class DigitalOceanProvider(Provider):
|
|
29
|
+
name = "digitalocean"
|
|
30
|
+
display_name = "DigitalOcean Functions"
|
|
31
|
+
tier = "beta"
|
|
32
|
+
terraform_source = "terraform.io/builtin/terraform" # terraform_data + doctl, no cloud provider plugin
|
|
33
|
+
default_region = ""
|
|
34
|
+
capabilities = frozenset({HTTP, SCHEDULE})
|
|
35
|
+
max_name_length = 63
|
|
36
|
+
runtimes = {"python3.9": "python:3.9", "python3.11": "python:3.11"}
|
|
37
|
+
vendor_dependencies = True
|
|
38
|
+
max_timeout = 900
|
|
39
|
+
notes = (
|
|
40
|
+
"Deployed with `doctl serverless deploy` (doctl must be installed and authenticated: "
|
|
41
|
+
"`doctl auth init`). Requires options.namespace (fn-...); set options.api_host to get URLs. "
|
|
42
|
+
"Supports http (web: raw, public) and schedule triggers only. Runtimes: python3.9 / python3.11."
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# ------------------------------------------------------------------ terraform header
|
|
46
|
+
def init_doc(self, ctx: RenderContext) -> TerraformDoc:
|
|
47
|
+
doc = TerraformDoc()
|
|
48
|
+
doc.required_version(ctx.config.provider.terraform.required_version)
|
|
49
|
+
state = ctx.config.provider.state
|
|
50
|
+
if state.backend:
|
|
51
|
+
doc.backend(state.backend, state.config)
|
|
52
|
+
return doc
|
|
53
|
+
|
|
54
|
+
def provider_block(self, ctx: RenderContext) -> Optional[Dict[str, Any]]:
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
def region(self, config: ServiceConfig) -> str:
|
|
58
|
+
return config.provider.region or str(config.provider.options.get("region", ""))
|
|
59
|
+
|
|
60
|
+
# ------------------------------------------------------------------ bundle
|
|
61
|
+
def shim_files(self, config: ServiceConfig) -> Dict[str, str]:
|
|
62
|
+
return {
|
|
63
|
+
"__main__.py": (
|
|
64
|
+
"# Generated by py-serverless-kit. DigitalOcean entrypoint: main(event, context)\n"
|
|
65
|
+
"from py_serverless_kit.runtime.adapters.digitalocean import main # noqa: F401\n"
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
def extra_validation(self, config: ServiceConfig) -> List[Issue]:
|
|
70
|
+
issues: List[Issue] = []
|
|
71
|
+
if not config.provider.options.get("namespace"):
|
|
72
|
+
issues.append(
|
|
73
|
+
Issue(
|
|
74
|
+
"error",
|
|
75
|
+
"options.namespace is required (see `doctl serverless namespaces list`)",
|
|
76
|
+
"provider.options.namespace",
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
if shutil.which("doctl") is None:
|
|
80
|
+
issues.append(
|
|
81
|
+
Issue(
|
|
82
|
+
"warning",
|
|
83
|
+
"`doctl` is not on PATH; deploy will fail until it is installed and "
|
|
84
|
+
"authenticated (`doctl auth init`)",
|
|
85
|
+
"provider",
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
for name, fn in config.functions.items():
|
|
89
|
+
mem = config.memory_of(fn)
|
|
90
|
+
if not 128 <= mem <= 1024:
|
|
91
|
+
issues.append(
|
|
92
|
+
Issue(
|
|
93
|
+
"error",
|
|
94
|
+
f"memory {mem} MB is outside DigitalOcean's 128-1024 MB range",
|
|
95
|
+
f"functions.{name}.memory",
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
for ev in fn.events_of("http"):
|
|
99
|
+
if ev.auth != "none":
|
|
100
|
+
issues.append(
|
|
101
|
+
Issue(
|
|
102
|
+
"error",
|
|
103
|
+
f"auth `{ev.auth}` is not supported; DigitalOcean web functions "
|
|
104
|
+
"are public - validate tokens in the handler",
|
|
105
|
+
f"functions.{name}",
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
return issues
|
|
109
|
+
|
|
110
|
+
# ------------------------------------------------------------------ render
|
|
111
|
+
def package_name(self, ctx: RenderContext) -> str:
|
|
112
|
+
return ctx.prefix
|
|
113
|
+
|
|
114
|
+
def project_spec(self, ctx: RenderContext) -> Dict[str, Any]:
|
|
115
|
+
cfg = ctx.config
|
|
116
|
+
functions = []
|
|
117
|
+
for name, fn in ctx.function_items():
|
|
118
|
+
spec: Dict[str, Any] = {
|
|
119
|
+
"name": name,
|
|
120
|
+
"runtime": self.runtime_id(cfg.runtime_of(fn)),
|
|
121
|
+
"main": "main",
|
|
122
|
+
"web": "raw" if fn.events_of("http") else False,
|
|
123
|
+
"limits": {"timeout": cfg.timeout_of(fn) * 1000, "memory": cfg.memory_of(fn)},
|
|
124
|
+
"environment": ctx.environment(name, fn),
|
|
125
|
+
}
|
|
126
|
+
triggers = []
|
|
127
|
+
for i, ev in enumerate(fn.events_of("schedule")):
|
|
128
|
+
if not ev.enabled:
|
|
129
|
+
continue
|
|
130
|
+
triggers.append(
|
|
131
|
+
{
|
|
132
|
+
"name": f"{name}-{ev.name or i}",
|
|
133
|
+
"sourceType": "scheduler",
|
|
134
|
+
"sourceDetails": {
|
|
135
|
+
"cron": sched.to_cron5(ev),
|
|
136
|
+
"withBody": envelope("schedule", ev.input, source="digitalocean.scheduler"),
|
|
137
|
+
},
|
|
138
|
+
}
|
|
139
|
+
)
|
|
140
|
+
if triggers:
|
|
141
|
+
spec["triggers"] = triggers
|
|
142
|
+
spec.update(fn.options)
|
|
143
|
+
functions.append(spec)
|
|
144
|
+
return {"packages": [{"name": self.package_name(ctx), "functions": functions}]}
|
|
145
|
+
|
|
146
|
+
def write_project(self, ctx: RenderContext) -> str:
|
|
147
|
+
"""Materialise the doctl project next to main.tf.json; return a content hash."""
|
|
148
|
+
root = ctx.work_dir / PROJECT_DIR
|
|
149
|
+
if root.exists():
|
|
150
|
+
shutil.rmtree(root)
|
|
151
|
+
spec = self.project_spec(ctx)
|
|
152
|
+
build = ctx.artifact.build_dir
|
|
153
|
+
pkg = self.package_name(ctx)
|
|
154
|
+
for fn in spec["packages"][0]["functions"]:
|
|
155
|
+
dest = root / "packages" / pkg / fn["name"]
|
|
156
|
+
if build is not None and Path(build).is_dir():
|
|
157
|
+
shutil.copytree(build, dest)
|
|
158
|
+
else: # pragma: no cover - build_dir is always set by the packager
|
|
159
|
+
dest.mkdir(parents=True)
|
|
160
|
+
text = yaml.safe_dump(spec, sort_keys=False)
|
|
161
|
+
(root / "project.yml").write_text(text, "utf-8")
|
|
162
|
+
return hashlib.sha256(text.encode("utf-8")).hexdigest()
|
|
163
|
+
|
|
164
|
+
def render_resources(self, doc: TerraformDoc, ctx: RenderContext) -> None:
|
|
165
|
+
cfg = ctx.config
|
|
166
|
+
namespace = str(ctx.opt("namespace", ""))
|
|
167
|
+
pkg = self.package_name(ctx)
|
|
168
|
+
project_hash = self.write_project(ctx)
|
|
169
|
+
doc.resource(
|
|
170
|
+
"terraform_data",
|
|
171
|
+
"deploy",
|
|
172
|
+
{
|
|
173
|
+
"triggers_replace": [ctx.artifact.sha256, project_hash],
|
|
174
|
+
"input": {"namespace": namespace, "package": pkg},
|
|
175
|
+
"provisioner": [
|
|
176
|
+
{
|
|
177
|
+
"local-exec": {
|
|
178
|
+
"working_dir": "${path.module}",
|
|
179
|
+
"command": f"doctl serverless connect {namespace} && doctl serverless deploy {PROJECT_DIR}",
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"local-exec": {
|
|
184
|
+
"when": "destroy",
|
|
185
|
+
"command": "doctl serverless connect ${self.input.namespace} && "
|
|
186
|
+
"doctl serverless undeploy --packages ${self.input.package}",
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
)
|
|
192
|
+
api_host = str(ctx.opt("api_host", "")).rstrip("/")
|
|
193
|
+
fn_out: Dict[str, Dict[str, Any]] = {}
|
|
194
|
+
endpoint: Optional[str] = None
|
|
195
|
+
for name, fn in ctx.function_items():
|
|
196
|
+
url = f"{api_host}/api/v1/web/{namespace}/{pkg}/{name}" if api_host and fn.events_of("http") else None
|
|
197
|
+
out: Dict[str, Any] = {
|
|
198
|
+
"id": f"{pkg}/{name}",
|
|
199
|
+
"url": url,
|
|
200
|
+
"console": "https://cloud.digitalocean.com/functions",
|
|
201
|
+
}
|
|
202
|
+
if url:
|
|
203
|
+
out["base_url"] = url
|
|
204
|
+
endpoint = endpoint or f"{api_host}/api/v1/web/{namespace}/{pkg}"
|
|
205
|
+
fn_out[name] = out
|
|
206
|
+
wf_out = {n: {"id": f"{pkg}/{n}-orchestrator"} for n in cfg.workflows}
|
|
207
|
+
doc.output(
|
|
208
|
+
"pskit",
|
|
209
|
+
{"functions": fn_out, "workflows": wf_out, "endpoint": endpoint},
|
|
210
|
+
description="py-serverless-kit deployment summary",
|
|
211
|
+
)
|