Pyjoyment 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.
- Pyjo/App/ASGI.py +735 -0
- Pyjo/App/CGI.py +773 -0
- Pyjo/App/FastCGI.py +631 -0
- Pyjo/App/Lambda.py +987 -0
- Pyjo/App/WSGI.py +427 -0
- Pyjo/App/__init__.py +409 -0
- Pyjo/Asset/File.py +377 -0
- Pyjo/Asset/Memory.py +235 -0
- Pyjo/Asset/__init__.py +200 -0
- Pyjo/Base.py +215 -0
- Pyjo/Cache.py +119 -0
- Pyjo/Collection.py +570 -0
- Pyjo/Content/MultiPart.py +325 -0
- Pyjo/Content/Single.py +193 -0
- Pyjo/Content/__init__.py +806 -0
- Pyjo/Cookie/Request.py +90 -0
- Pyjo/Cookie/Response.py +294 -0
- Pyjo/Cookie/__init__.py +143 -0
- Pyjo/DOM/CSS.py +1019 -0
- Pyjo/DOM/HTML.py +550 -0
- Pyjo/DOM/__init__.py +1329 -0
- Pyjo/Date.py +294 -0
- Pyjo/EventEmitter.py +522 -0
- Pyjo/Exception.py +494 -0
- Pyjo/File.py +959 -0
- Pyjo/GetOpt.py +605 -0
- Pyjo/Headers.py +760 -0
- Pyjo/HelloWorld.py +77 -0
- Pyjo/Home.py +109 -0
- Pyjo/IOLoop/ChildProcess.py +591 -0
- Pyjo/IOLoop/Client.py +736 -0
- Pyjo/IOLoop/Datagram.py +982 -0
- Pyjo/IOLoop/Messages.py +664 -0
- Pyjo/IOLoop/Server.py +458 -0
- Pyjo/IOLoop/Stdio.py +446 -0
- Pyjo/IOLoop/Stream.py +644 -0
- Pyjo/IOLoop/Subprocess.py +419 -0
- Pyjo/IOLoop/TLS.py +360 -0
- Pyjo/IOLoop/__init__.py +1036 -0
- Pyjo/IOLoop/certs/README.md +41 -0
- Pyjo/IOLoop/certs/ca.crt +20 -0
- Pyjo/IOLoop/certs/server.crt +21 -0
- Pyjo/IOLoop/certs/server.key +28 -0
- Pyjo/JSON/Pointer.py +135 -0
- Pyjo/JSON/RPC.py +772 -0
- Pyjo/JSON/Schema.py +2506 -0
- Pyjo/JSON/__init__.py +573 -0
- Pyjo/Loader.py +342 -0
- Pyjo/Log.py +723 -0
- Pyjo/Message/Request.py +725 -0
- Pyjo/Message/Response.py +476 -0
- Pyjo/Message/__init__.py +830 -0
- Pyjo/Parameters.py +472 -0
- Pyjo/Path.py +419 -0
- Pyjo/Promise.py +894 -0
- Pyjo/Prove.py +1305 -0
- Pyjo/Reactor/Asyncio.py +854 -0
- Pyjo/Reactor/Poll.py +439 -0
- Pyjo/Reactor/__init__.py +525 -0
- Pyjo/SSE.py +119 -0
- Pyjo/Server/ASGI.py +486 -0
- Pyjo/Server/CGI.py +259 -0
- Pyjo/Server/Daemon.py +1007 -0
- Pyjo/Server/FastCGI.py +517 -0
- Pyjo/Server/Hot/__init__.py +687 -0
- Pyjo/Server/Hot/__main__.py +5 -0
- Pyjo/Server/Lambda.py +641 -0
- Pyjo/Server/Prefork.py +917 -0
- Pyjo/Server/Prethread.py +394 -0
- Pyjo/Server/Reload/Backend/Poll.py +114 -0
- Pyjo/Server/Reload/Backend/__init__.py +126 -0
- Pyjo/Server/Reload/__init__.py +482 -0
- Pyjo/Server/Reload/__main__.py +5 -0
- Pyjo/Server/Stdio.py +228 -0
- Pyjo/Server/WSGI.py +349 -0
- Pyjo/Server/__init__.py +396 -0
- Pyjo/String/Bytes.py +532 -0
- Pyjo/String/HTML.py +210 -0
- Pyjo/String/Mixin.py +333 -0
- Pyjo/String/Template.py +470 -0
- Pyjo/String/Unicode.py +553 -0
- Pyjo/String/__init__.py +0 -0
- Pyjo/TOML.py +232 -0
- Pyjo/Template.py +1290 -0
- Pyjo/Test/Pyjo.py +1454 -0
- Pyjo/Test/__init__.py +1233 -0
- Pyjo/Test/pytest_plugin.py +305 -0
- Pyjo/Transaction/HTTP.py +374 -0
- Pyjo/Transaction/WebSocket.py +709 -0
- Pyjo/Transaction/__init__.py +429 -0
- Pyjo/URL/Template.py +459 -0
- Pyjo/URL/__init__.py +651 -0
- Pyjo/Upload.py +139 -0
- Pyjo/UserAgent/CookieJar.py +391 -0
- Pyjo/UserAgent/Proxy.py +138 -0
- Pyjo/UserAgent/Server.py +239 -0
- Pyjo/UserAgent/Transactor.py +766 -0
- Pyjo/UserAgent/__init__.py +1243 -0
- Pyjo/Util.py +3567 -0
- Pyjo/WebSocket.py +332 -0
- Pyjo/YAML.py +1167 -0
- Pyjo/__init__.py +441 -0
- Pyjo/__main__.py +35 -0
- Pyjo/py.typed +0 -0
- Pyjoyment/Command/Author/__init__.py +20 -0
- Pyjoyment/Command/Author/generate/__init__.py +106 -0
- Pyjoyment/Command/Author/generate/app.py +299 -0
- Pyjoyment/Command/Author/generate/dockerfile.py +281 -0
- Pyjoyment/Command/Author/generate/kubernetes.py +514 -0
- Pyjoyment/Command/Author/generate/lambda_zip.py +372 -0
- Pyjoyment/Command/Author/generate/lite_app.py +113 -0
- Pyjoyment/Command/Author/generate/plugin.py +267 -0
- Pyjoyment/Command/Author/generate/pyproject.py +123 -0
- Pyjoyment/Command/Author/inflate.py +101 -0
- Pyjoyment/Command/__init__.py +332 -0
- Pyjoyment/Command/asgi.py +92 -0
- Pyjoyment/Command/cgi.py +90 -0
- Pyjoyment/Command/daemon.py +160 -0
- Pyjoyment/Command/eval.py +128 -0
- Pyjoyment/Command/fastcgi.py +99 -0
- Pyjoyment/Command/get.py +305 -0
- Pyjoyment/Command/lambda.py +117 -0
- Pyjoyment/Command/prefork.py +181 -0
- Pyjoyment/Command/prethread.py +126 -0
- Pyjoyment/Command/routes.py +133 -0
- Pyjoyment/Command/stdio.py +92 -0
- Pyjoyment/Command/version.py +141 -0
- Pyjoyment/Command/wsgi.py +93 -0
- Pyjoyment/Commands.py +546 -0
- Pyjoyment/Controller.py +1307 -0
- Pyjoyment/HelloWorld.py +56 -0
- Pyjoyment/Lite.py +494 -0
- Pyjoyment/Plugin/AllowedHosts.py +205 -0
- Pyjoyment/Plugin/Config.py +248 -0
- Pyjoyment/Plugin/DefaultHelpers.py +1202 -0
- Pyjoyment/Plugin/EPLRenderer.py +134 -0
- Pyjoyment/Plugin/EPRenderer.py +212 -0
- Pyjoyment/Plugin/HeaderCondition.py +98 -0
- Pyjoyment/Plugin/Health.py +325 -0
- Pyjoyment/Plugin/JSONConfig.py +172 -0
- Pyjoyment/Plugin/Mount.py +223 -0
- Pyjoyment/Plugin/PyRenderer.py +209 -0
- Pyjoyment/Plugin/TOMLConfig.py +141 -0
- Pyjoyment/Plugin/TagHelpers.py +959 -0
- Pyjoyment/Plugin/YAMLConfig.py +178 -0
- Pyjoyment/Plugin/__init__.py +80 -0
- Pyjoyment/Plugins.py +215 -0
- Pyjoyment/Renderer.py +673 -0
- Pyjoyment/Routes/Match.py +251 -0
- Pyjoyment/Routes/Pattern.py +443 -0
- Pyjoyment/Routes/Route.py +827 -0
- Pyjoyment/Routes/__init__.py +419 -0
- Pyjoyment/Sessions.py +255 -0
- Pyjoyment/Static.py +421 -0
- Pyjoyment/Types.py +238 -0
- Pyjoyment/Validator/Validation.py +395 -0
- Pyjoyment/Validator/__init__.py +304 -0
- Pyjoyment/__init__.py +1083 -0
- Pyjoyment/__main__.py +5 -0
- Pyjoyment/py.typed +0 -0
- Pyjoyment/resources/public/favicon.ico +0 -0
- Pyjoyment/resources/public/favicon.svg +345 -0
- Pyjoyment/resources/public/pyjo/bootstrap/LICENSE +21 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.bundle.min.js.map +1 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.css +6 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.js +7 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.min.css.map +1 -0
- Pyjoyment/resources/public/pyjo/failunicat.png +0 -0
- Pyjoyment/resources/public/pyjo/failunicat.svg +50 -0
- Pyjoyment/resources/public/pyjo/highlight.js/LICENSE +29 -0
- Pyjoyment/resources/public/pyjo/highlight.js/highlight-mojo-dark.css +56 -0
- Pyjoyment/resources/public/pyjo/highlight.js/highlight.min.js +1262 -0
- Pyjoyment/resources/public/pyjo/highlight.js/pyjoyment.min.js +1 -0
- Pyjoyment/resources/public/pyjo/logo-black-4x.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-black-4x.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo-white-2x.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-white-2x.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo-white-4x.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-white-4x.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo-white.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-white.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo.png +0 -0
- Pyjoyment/resources/public/pyjo/logo.svg +346 -0
- Pyjoyment/resources/public/pyjo/notfound.png +0 -0
- Pyjoyment/resources/public/pyjo/notfound.svg +3 -0
- Pyjoyment/resources/public/pyjo/nounicat.png +0 -0
- Pyjoyment/resources/public/pyjo/nounicat.svg +5 -0
- Pyjoyment/resources/public/pyjo/pinstripe-dark.png +0 -0
- Pyjoyment/resources/public/pyjo/pinstripe-dark.svg +4 -0
- Pyjoyment/resources/public/pyjo/pinstripe-light.png +0 -0
- Pyjoyment/resources/public/pyjo/pinstripe-light.svg +4 -0
- Pyjoyment/resources/public/pyjo/pyjo.css +204 -0
- Pyjoyment/resources/templates/pyjo/debug.html.ep +346 -0
- Pyjoyment/resources/templates/pyjo/exception.html.ep +32 -0
- Pyjoyment/resources/templates/pyjo/not_found.html.ep +45 -0
- pyjoyment-0.1.0.dist-info/METADATA +376 -0
- pyjoyment-0.1.0.dist-info/RECORD +200 -0
- pyjoyment-0.1.0.dist-info/WHEEL +4 -0
- pyjoyment-0.1.0.dist-info/entry_points.txt +8 -0
- pyjoyment-0.1.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pyjoyment.Command.Author.generate.kubernetes - Kubernetes generator command
|
|
3
|
+
===========================================================================
|
|
4
|
+
::
|
|
5
|
+
|
|
6
|
+
Usage: APPLICATION generate kubernetes [OPTIONS] [-- SERVER OPTIONS]
|
|
7
|
+
|
|
8
|
+
./myapp.py generate kubernetes
|
|
9
|
+
python -m MyApp generate kubernetes
|
|
10
|
+
python -m MyApp generate kubernetes --image ghcr.io/me/myapp:1.0
|
|
11
|
+
python -m MyApp generate kubernetes --host-path
|
|
12
|
+
python -m MyApp generate kubernetes --graceful-timeout 25
|
|
13
|
+
python -m MyApp generate kubernetes --shutdown-delay 10
|
|
14
|
+
python -m MyApp generate kubernetes --no-dev
|
|
15
|
+
python -m MyApp generate kubernetes --server prefork -- --workers 2
|
|
16
|
+
|
|
17
|
+
Options:
|
|
18
|
+
--dev Install the dependency group "dev" of
|
|
19
|
+
the project in the pod of "dev", which
|
|
20
|
+
--no-dev leaves out, defaults to
|
|
21
|
+
installing it
|
|
22
|
+
-G, --graceful-timeout <seconds> Graceful timeout of the workers of the
|
|
23
|
+
server of "prod", defaults to the one of
|
|
24
|
+
the server
|
|
25
|
+
-h, --help Show this summary of available options
|
|
26
|
+
--host-path Mount the directory of the project in
|
|
27
|
+
the pod of "dev", in the place of a
|
|
28
|
+
ConfigMap
|
|
29
|
+
-i, --image <name> Image of "prod", the one of "generate
|
|
30
|
+
dockerfile", defaults to the name of the
|
|
31
|
+
application
|
|
32
|
+
-p, --port <port> Port that the server listens on,
|
|
33
|
+
defaults to 3000
|
|
34
|
+
-S, --shutdown-delay <seconds> Time that the server of "prod" keeps
|
|
35
|
+
serving after TERM, defaults to 5
|
|
36
|
+
-s, --server <name> Server of "dev", "daemon", "prefork" or
|
|
37
|
+
"reload", defaults to "reload" with
|
|
38
|
+
--host-path or "daemon"
|
|
39
|
+
|
|
40
|
+
:mod:`Pyjoyment.Command.Author.generate.kubernetes` generates the manifests
|
|
41
|
+
of an application for Kubernetes, as Kustomize has them, in ``kubernetes``: a
|
|
42
|
+
base, with a Deployment and a Service of the name of the application, and two
|
|
43
|
+
overlays, ``dev`` and ``prod``, which ``kubectl apply -k`` applies. ::
|
|
44
|
+
|
|
45
|
+
kubernetes/
|
|
46
|
+
base/
|
|
47
|
+
kustomization.yaml
|
|
48
|
+
deployment.yaml
|
|
49
|
+
probes.yaml
|
|
50
|
+
service.yaml
|
|
51
|
+
dev/
|
|
52
|
+
kustomization.yaml
|
|
53
|
+
deployment.yaml
|
|
54
|
+
files/
|
|
55
|
+
prod/
|
|
56
|
+
kustomization.yaml
|
|
57
|
+
deployment.yaml
|
|
58
|
+
|
|
59
|
+
The server listens on the port of the ``PORT`` environment variable, ``3000``
|
|
60
|
+
by default, as in the image of
|
|
61
|
+
:mod:`Pyjoyment.Command.Author.generate.dockerfile`, which the Service exposes
|
|
62
|
+
on the port ``80``. The probes of Kubernetes of an application with
|
|
63
|
+
:mod:`Pyjoyment.Plugin.Health` request it: the startup and the liveness its
|
|
64
|
+
liveness, ``/livez`` by default, and the readiness its readiness,
|
|
65
|
+
``/readyz``, which fails while the server stops gracefully, so that the
|
|
66
|
+
Service sends no more requests to the pod before its end. The probes of an
|
|
67
|
+
application without it connect to the port, which the server listens on once
|
|
68
|
+
it has started. The probes are a patch of their own, ``base/probes.yaml``, as
|
|
69
|
+
the application gets the plugin or loses it.
|
|
70
|
+
|
|
71
|
+
The overlay ``prod`` runs the image of ``--image``, which
|
|
72
|
+
:mod:`Pyjoyment.Command.Author.generate.dockerfile` builds, with the
|
|
73
|
+
pre-forking server of the image, in the operating mode ``production``.
|
|
74
|
+
Kubernetes takes a pod that it stops out of the endpoints of its Service at
|
|
75
|
+
the same time as it sends it ``TERM``, so the server keeps serving for the 5
|
|
76
|
+
seconds of ``--shutdown-delay``, which sets ``PYJO_SHUTDOWN_DELAY``, until the
|
|
77
|
+
proxies of the cluster know it, and ``terminationGracePeriodSeconds`` is 5
|
|
78
|
+
seconds above the delay and the 25 seconds that
|
|
79
|
+
:attr:`Pyjo.Server.Prefork.Pyjo_Server_Prefork.graceful_timeout` gives a
|
|
80
|
+
worker, or ``--graceful-timeout``, which sets ``PYJO_GRACEFUL_TIMEOUT``, so
|
|
81
|
+
that the server stops by itself before the kill of Kubernetes. Without
|
|
82
|
+
``--image``, the image is the one of the name of the application, as
|
|
83
|
+
``docker build -t myapp .`` tags it, which a cluster of Docker Desktop or
|
|
84
|
+
OrbStack takes from Docker.
|
|
85
|
+
|
|
86
|
+
The overlay ``dev`` runs the application in the image of uv with Python, of
|
|
87
|
+
the version that runs the generator, which installs the dependencies of the
|
|
88
|
+
application when the pod starts, as the image of
|
|
89
|
+
:mod:`Pyjoyment.Command.Author.generate.dockerfile` does when it is built, in
|
|
90
|
+
an ``emptyDir``, with the dependency group ``dev`` of a project, which
|
|
91
|
+
``--no-dev`` leaves out, and runs it in the operating mode ``development``,
|
|
92
|
+
without building an image, with the server of ``--server``: the command
|
|
93
|
+
``daemon``, ``prefork``, or ``pyjo-reload`` of :mod:`Pyjo.Server.Reload`,
|
|
94
|
+
with the options after ``--``. The application is a ConfigMap that ``configMapGenerator`` of Kustomize makes of a
|
|
95
|
+
copy of it in ``kubernetes/dev/files``, which the pod copies, as the files of a
|
|
96
|
+
ConfigMap are read-only. The application is the files of the current
|
|
97
|
+
directory, without hidden ones, ``__pycache__``, ``dist`` and ``kubernetes``,
|
|
98
|
+
as for :mod:`Pyjoyment.Command.Author.generate.lambda_zip`, and a ConfigMap
|
|
99
|
+
holds 1 MiB at most. A new ConfigMap of a changed application has a new name,
|
|
100
|
+
which rolls the Deployment out. ``kubernetes/dev/files`` is a build of the
|
|
101
|
+
application, as ``dist`` of
|
|
102
|
+
:mod:`Pyjoyment.Command.Author.generate.lambda_zip`, which goes into
|
|
103
|
+
``.gitignore``.
|
|
104
|
+
|
|
105
|
+
With ``--host-path``, the pod of ``dev`` mounts the directory of the project
|
|
106
|
+
in the place of the ConfigMap, as a cluster that sees the disk of the
|
|
107
|
+
machine, as the one of Docker Desktop or OrbStack, or of kind with
|
|
108
|
+
``extraMounts``, mounts it, and the server is ``pyjo-reload``, which restarts
|
|
109
|
+
the application when a file of it changes, where a ConfigMap changes with a
|
|
110
|
+
new pod and the server is ``daemon``.
|
|
111
|
+
|
|
112
|
+
The files of ``base`` and ``prod``, which are to be edited, are written once,
|
|
113
|
+
and the ones of ``dev`` and ``base/probes.yaml`` anew each time, as the
|
|
114
|
+
application changes, which a patch of its own in the place of the one of
|
|
115
|
+
``base/probes.yaml`` in ``base/kustomization.yaml`` keeps from changing the
|
|
116
|
+
probes.
|
|
117
|
+
|
|
118
|
+
This is a core command, that means it is always enabled and its code a good
|
|
119
|
+
example for learning to build new commands, you're welcome to fork it.
|
|
120
|
+
|
|
121
|
+
See :mod:`Pyjoyment.Commands` for a list of commands that are available by
|
|
122
|
+
default.
|
|
123
|
+
|
|
124
|
+
See also
|
|
125
|
+
--------
|
|
126
|
+
|
|
127
|
+
:mod:`Pyjoyment`, https://mojolicious.org.
|
|
128
|
+
|
|
129
|
+
It has no counterpart in Mojolicious.
|
|
130
|
+
|
|
131
|
+
Classes
|
|
132
|
+
-------
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
import Pyjo.Exception
|
|
136
|
+
import Pyjo.File
|
|
137
|
+
import Pyjoyment.Command
|
|
138
|
+
|
|
139
|
+
from Pyjo.Util import extract_usage, getopt
|
|
140
|
+
|
|
141
|
+
import json
|
|
142
|
+
import os
|
|
143
|
+
import re
|
|
144
|
+
import shlex
|
|
145
|
+
import shutil
|
|
146
|
+
import sys
|
|
147
|
+
|
|
148
|
+
from typing import Any
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
# The data of a ConfigMap, at most
|
|
152
|
+
_LIMIT = 1024 * 1024
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class Pyjoyment_Command_Author_generate_kubernetes(Pyjoyment.Command.object):
|
|
156
|
+
"""
|
|
157
|
+
:mod:`Pyjoyment.Command.Author.generate.kubernetes` inherits all
|
|
158
|
+
attributes and methods from :mod:`Pyjoyment.Command` and implements the
|
|
159
|
+
following new ones.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
description = 'Generate the manifests of Kubernetes'
|
|
163
|
+
"""::
|
|
164
|
+
|
|
165
|
+
description = kubernetes.description
|
|
166
|
+
kubernetes.description = 'Foo'
|
|
167
|
+
|
|
168
|
+
Short description of this command, used for the command list.
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
usage = extract_usage()
|
|
172
|
+
"""::
|
|
173
|
+
|
|
174
|
+
usage = kubernetes.usage
|
|
175
|
+
kubernetes.usage = 'Foo'
|
|
176
|
+
|
|
177
|
+
Usage information for this command, used for the help screen.
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
def run(self, *args: str) -> None:
|
|
181
|
+
"""::
|
|
182
|
+
|
|
183
|
+
kubernetes.run(*sys.argv[1:])
|
|
184
|
+
|
|
185
|
+
Run this command.
|
|
186
|
+
"""
|
|
187
|
+
arguments = list(args)
|
|
188
|
+
spec = {'dev!': None, 'G|graceful-timeout=i': None, 'host-path': None, 'i|image=s': None, 'p|port=i': None}
|
|
189
|
+
if not (opts := getopt(arguments, {**spec, 'S|shutdown-delay=i': None, 's|server=s': None})):
|
|
190
|
+
raise Pyjo.Exception.new(self.usage)
|
|
191
|
+
|
|
192
|
+
# The server of dev, which reloads the application of the directory of the project, where its files change,
|
|
193
|
+
# and the arguments after "--" for it
|
|
194
|
+
server: str = opts.server or ('reload' if opts.host_path else 'daemon')
|
|
195
|
+
if server not in ('daemon', 'prefork', 'reload'):
|
|
196
|
+
raise Pyjo.Exception.new(f'Unknown server: {server}')
|
|
197
|
+
|
|
198
|
+
# The name of the resources, which Kubernetes takes in lower case, with "-" in the place of "_", and the one of
|
|
199
|
+
# the image, as "docker build -t" tags it
|
|
200
|
+
moniker = self.app.moniker
|
|
201
|
+
name = re.sub(r'[^a-z0-9]+', '-', moniker.lower()).strip('-') or 'app'
|
|
202
|
+
|
|
203
|
+
# The probes of Pyjoyment.Plugin.Health, or of the port without it; helpers of a plugin are found at run time, so
|
|
204
|
+
# the application is Any
|
|
205
|
+
app: Any = self.app
|
|
206
|
+
health = app.renderer.get_helper('health.path') is not None
|
|
207
|
+
probes = {'livez': app.health.path('livez'), 'readyz': app.health.path('readyz')} if health else None
|
|
208
|
+
|
|
209
|
+
# The script of Pyjoyment.Lite or the package of the application, and the dependencies of the inline metadata
|
|
210
|
+
# of PEP 723 of the script, if it has them and there is no pyproject.toml, as generate dockerfile has them
|
|
211
|
+
exe = os.environ.get('PYJO_EXE')
|
|
212
|
+
script = Pyjo.File.path(exe).to_rel(self.app.home).to_str().replace(os.sep, '/') if exe else None
|
|
213
|
+
package = type(self.app).__module__
|
|
214
|
+
project = os.path.isfile(self.rel_file('pyproject.toml')) or not exe or not _metadata(Pyjo.File.path(exe))
|
|
215
|
+
lock = 'uv.lock' if project else f'{script}.lock'
|
|
216
|
+
port = opts.port or 3000
|
|
217
|
+
|
|
218
|
+
base = {'moniker': moniker, 'name': name, 'port': port, 'probes': probes}
|
|
219
|
+
self.render_to_rel_file('base_kustomization', 'kubernetes/base/kustomization.yaml', base)
|
|
220
|
+
self.render_to_rel_file('base_deployment', 'kubernetes/base/deployment.yaml', base)
|
|
221
|
+
self.render_to_rel_file('base_service', 'kubernetes/base/service.yaml', base)
|
|
222
|
+
# The probes anew each time, as the application gets or loses the plugin Health
|
|
223
|
+
self._write(self.rel_file('kubernetes/base/probes.yaml'), self.render_data('base_probes', base))
|
|
224
|
+
|
|
225
|
+
graceful: int | None = opts.graceful_timeout
|
|
226
|
+
delay: int = 5 if opts.shutdown_delay is None else opts.shutdown_delay
|
|
227
|
+
prod = {'delay': delay, 'graceful_timeout': graceful, 'image': opts.image, 'moniker': moniker, 'name': name}
|
|
228
|
+
self.render_to_rel_file('prod_kustomization', 'kubernetes/prod/kustomization.yaml', prod)
|
|
229
|
+
self.render_to_rel_file('prod_deployment', 'kubernetes/prod/deployment.yaml', prod)
|
|
230
|
+
|
|
231
|
+
# The command of dev: the dependencies in the emptyDir, with the group "dev" of a project unless --no-dev, and
|
|
232
|
+
# the server, with "python -m", which puts the application in the path, as the image of generate dockerfile
|
|
233
|
+
# does; pyjo-reload watches the package of an application too, as it watches the script, "lib" and "templates"
|
|
234
|
+
# by default
|
|
235
|
+
locked = ' --locked' if os.path.isfile(self.rel_file(lock)) else ''
|
|
236
|
+
if project:
|
|
237
|
+
sync = f'uv sync --no-install-project{locked}{" --no-dev" if opts.dev is False else ""}'
|
|
238
|
+
else:
|
|
239
|
+
sync = f'uv venv --allow-existing /uv/venv && uv sync --script {shlex.quote(str(script))} --active{locked}'
|
|
240
|
+
python = '/uv/venv/bin/python'
|
|
241
|
+
if server == 'reload':
|
|
242
|
+
watch = [] if script else ['-w', package, '-w', 'templates']
|
|
243
|
+
cmd = [python, '-m', 'Pyjo.Server.Reload', *watch, *arguments, script or f'{package}/__main__.py']
|
|
244
|
+
else:
|
|
245
|
+
cmd = [python, *([script] if script else ['-m', package]), server, *arguments]
|
|
246
|
+
command = f'{sync} && exec {shlex.join(cmd)}'
|
|
247
|
+
|
|
248
|
+
# The application in a ConfigMap of a copy of its files, or the directory of the project
|
|
249
|
+
files: list[tuple[str, str, str]] = []
|
|
250
|
+
dev = self.rel_file('kubernetes/dev')
|
|
251
|
+
copy = dev.child('files')
|
|
252
|
+
if os.path.isdir(copy):
|
|
253
|
+
copy.remove_tree()
|
|
254
|
+
if not opts.host_path:
|
|
255
|
+
size = 0
|
|
256
|
+
keys: set[str] = set()
|
|
257
|
+
for file in Pyjo.File.path().list_tree().each():
|
|
258
|
+
path = file.to_rel(Pyjo.File.path()).to_str().replace(os.sep, '/')
|
|
259
|
+
if path.split('/')[0] in ('dist', 'kubernetes') or '__pycache__' in path.split('/'):
|
|
260
|
+
continue
|
|
261
|
+
if (size := size + os.path.getsize(file)) > _LIMIT:
|
|
262
|
+
raise Pyjo.Exception.new(f'The application is larger than a ConfigMap holds: {_LIMIT} bytes')
|
|
263
|
+
key = _key(path, keys)
|
|
264
|
+
files.append((key, json.dumps(f'{key}=files/{path}'), json.dumps(path)))
|
|
265
|
+
destination = copy.child(*path.split('/'))
|
|
266
|
+
destination.dirname().make_path()
|
|
267
|
+
shutil.copyfile(file.to_str(), destination.to_str())
|
|
268
|
+
|
|
269
|
+
dev.make_path()
|
|
270
|
+
values = {
|
|
271
|
+
'command': json.dumps(['sh', '-c', command]),
|
|
272
|
+
'files': files,
|
|
273
|
+
'host_path': json.dumps(Pyjo.File.path().to_abs().to_str()) if opts.host_path else None,
|
|
274
|
+
'image': f'ghcr.io/astral-sh/uv:python{sys.version_info.major}.{sys.version_info.minor}-trixie-slim',
|
|
275
|
+
'name': name,
|
|
276
|
+
'project': project,
|
|
277
|
+
}
|
|
278
|
+
for file in ('kustomization', 'deployment'):
|
|
279
|
+
self._write(dev.child(f'{file}.yaml'), self.render_data(f'dev_{file}', values))
|
|
280
|
+
|
|
281
|
+
# A file that the generator writes anew each time, where write_file keeps one that exists
|
|
282
|
+
def _write(self, file: Pyjo.File.object, data: str) -> None:
|
|
283
|
+
file.spew(data)
|
|
284
|
+
self._loud(f' [write] {file}')
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
# Whether the script has the inline metadata of PEP 723
|
|
288
|
+
def _metadata(path: Pyjo.File.object) -> bool:
|
|
289
|
+
try:
|
|
290
|
+
return re.search(r'(?m)^# /// script\r?$', str(path.slurp('UTF-8') or '')) is not None
|
|
291
|
+
except OSError:
|
|
292
|
+
return False
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
# The key of a file in a ConfigMap, which has no "/", unique among the keys of the other files
|
|
296
|
+
def _key(path: str, keys: set[str]) -> str:
|
|
297
|
+
key = re.sub(r'[^-._a-zA-Z0-9]', '-', path.replace('/', '.')).lstrip('.') or 'file'
|
|
298
|
+
unique, n = key, 1
|
|
299
|
+
while unique in keys:
|
|
300
|
+
n += 1
|
|
301
|
+
unique = f'{key}-{n}'
|
|
302
|
+
keys.add(unique)
|
|
303
|
+
return unique
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
new = Pyjoyment_Command_Author_generate_kubernetes.new
|
|
307
|
+
object = Pyjoyment_Command_Author_generate_kubernetes
|
|
308
|
+
|
|
309
|
+
__DATA__ = r"""
|
|
310
|
+
@@ base_kustomization
|
|
311
|
+
apiVersion: kustomize.config.k8s.io/v1beta1
|
|
312
|
+
kind: Kustomization
|
|
313
|
+
labels:
|
|
314
|
+
- pairs:
|
|
315
|
+
app.kubernetes.io/name: <%= name %>
|
|
316
|
+
includeSelectors: true
|
|
317
|
+
resources:
|
|
318
|
+
- deployment.yaml
|
|
319
|
+
- service.yaml
|
|
320
|
+
patches:
|
|
321
|
+
- path: probes.yaml
|
|
322
|
+
|
|
323
|
+
@@ base_deployment
|
|
324
|
+
apiVersion: apps/v1
|
|
325
|
+
kind: Deployment
|
|
326
|
+
metadata:
|
|
327
|
+
name: <%= name %>
|
|
328
|
+
spec:
|
|
329
|
+
replicas: 1
|
|
330
|
+
template:
|
|
331
|
+
spec:
|
|
332
|
+
containers:
|
|
333
|
+
- name: <%= name %>
|
|
334
|
+
image: <%= moniker %>
|
|
335
|
+
imagePullPolicy: IfNotPresent
|
|
336
|
+
env:
|
|
337
|
+
- name: PORT
|
|
338
|
+
value: "<%= port %>"
|
|
339
|
+
ports:
|
|
340
|
+
- name: http
|
|
341
|
+
containerPort: <%= port %>
|
|
342
|
+
|
|
343
|
+
@@ base_probes
|
|
344
|
+
# Written by "generate kubernetes" anew each time, as the application has the plugin Health or not
|
|
345
|
+
apiVersion: apps/v1
|
|
346
|
+
kind: Deployment
|
|
347
|
+
metadata:
|
|
348
|
+
name: <%= name %>
|
|
349
|
+
spec:
|
|
350
|
+
template:
|
|
351
|
+
spec:
|
|
352
|
+
containers:
|
|
353
|
+
- name: <%= name %>
|
|
354
|
+
startupProbe:
|
|
355
|
+
% if probes:
|
|
356
|
+
httpGet:
|
|
357
|
+
path: <%= probes['livez'] %>
|
|
358
|
+
port: http
|
|
359
|
+
% else:
|
|
360
|
+
tcpSocket:
|
|
361
|
+
port: http
|
|
362
|
+
% end
|
|
363
|
+
periodSeconds: 1
|
|
364
|
+
failureThreshold: 30
|
|
365
|
+
livenessProbe:
|
|
366
|
+
% if probes:
|
|
367
|
+
httpGet:
|
|
368
|
+
path: <%= probes['livez'] %>
|
|
369
|
+
port: http
|
|
370
|
+
% else:
|
|
371
|
+
tcpSocket:
|
|
372
|
+
port: http
|
|
373
|
+
% end
|
|
374
|
+
readinessProbe:
|
|
375
|
+
% if probes:
|
|
376
|
+
httpGet:
|
|
377
|
+
path: <%= probes['readyz'] %>
|
|
378
|
+
port: http
|
|
379
|
+
% else:
|
|
380
|
+
tcpSocket:
|
|
381
|
+
port: http
|
|
382
|
+
% end
|
|
383
|
+
periodSeconds: 2
|
|
384
|
+
|
|
385
|
+
@@ base_service
|
|
386
|
+
apiVersion: v1
|
|
387
|
+
kind: Service
|
|
388
|
+
metadata:
|
|
389
|
+
name: <%= name %>
|
|
390
|
+
spec:
|
|
391
|
+
ports:
|
|
392
|
+
- name: http
|
|
393
|
+
port: 80
|
|
394
|
+
targetPort: http
|
|
395
|
+
|
|
396
|
+
@@ prod_kustomization
|
|
397
|
+
apiVersion: kustomize.config.k8s.io/v1beta1
|
|
398
|
+
kind: Kustomization
|
|
399
|
+
resources:
|
|
400
|
+
- ../base
|
|
401
|
+
patches:
|
|
402
|
+
- path: deployment.yaml
|
|
403
|
+
% if image:
|
|
404
|
+
images:
|
|
405
|
+
- name: <%= moniker %>
|
|
406
|
+
newName: <%= image %>
|
|
407
|
+
% end
|
|
408
|
+
|
|
409
|
+
@@ prod_deployment
|
|
410
|
+
apiVersion: apps/v1
|
|
411
|
+
kind: Deployment
|
|
412
|
+
metadata:
|
|
413
|
+
name: <%= name %>
|
|
414
|
+
spec:
|
|
415
|
+
replicas: 2
|
|
416
|
+
template:
|
|
417
|
+
spec:
|
|
418
|
+
terminationGracePeriodSeconds: <%= delay + (25 if graceful_timeout is None else graceful_timeout) + 5 %>
|
|
419
|
+
containers:
|
|
420
|
+
- name: <%= name %>
|
|
421
|
+
env:
|
|
422
|
+
- name: PYJO_MODE
|
|
423
|
+
value: production
|
|
424
|
+
% if graceful_timeout is not None:
|
|
425
|
+
- name: PYJO_GRACEFUL_TIMEOUT
|
|
426
|
+
value: "<%= graceful_timeout %>"
|
|
427
|
+
% end
|
|
428
|
+
% if delay:
|
|
429
|
+
- name: PYJO_SHUTDOWN_DELAY
|
|
430
|
+
value: "<%= delay %>"
|
|
431
|
+
% end
|
|
432
|
+
|
|
433
|
+
@@ dev_kustomization
|
|
434
|
+
# Written by "generate kubernetes" anew each time
|
|
435
|
+
apiVersion: kustomize.config.k8s.io/v1beta1
|
|
436
|
+
kind: Kustomization
|
|
437
|
+
resources:
|
|
438
|
+
- ../base
|
|
439
|
+
patches:
|
|
440
|
+
- path: deployment.yaml
|
|
441
|
+
% if not host_path:
|
|
442
|
+
configMapGenerator:
|
|
443
|
+
- name: <%= name %>-files
|
|
444
|
+
files:
|
|
445
|
+
% for _, source, _ in files:
|
|
446
|
+
- <%= source %>
|
|
447
|
+
% end
|
|
448
|
+
% end
|
|
449
|
+
|
|
450
|
+
@@ dev_deployment
|
|
451
|
+
# Written by "generate kubernetes" anew each time
|
|
452
|
+
apiVersion: apps/v1
|
|
453
|
+
kind: Deployment
|
|
454
|
+
metadata:
|
|
455
|
+
name: <%= name %>
|
|
456
|
+
spec:
|
|
457
|
+
template:
|
|
458
|
+
spec:
|
|
459
|
+
% if not host_path:
|
|
460
|
+
initContainers:
|
|
461
|
+
- name: app
|
|
462
|
+
image: <%= image %>
|
|
463
|
+
command: ["sh", "-c", "cp -RL /files/* /app"]
|
|
464
|
+
volumeMounts:
|
|
465
|
+
- name: files
|
|
466
|
+
mountPath: /files
|
|
467
|
+
- name: app
|
|
468
|
+
mountPath: /app
|
|
469
|
+
% end
|
|
470
|
+
containers:
|
|
471
|
+
- name: <%= name %>
|
|
472
|
+
image: <%= image %>
|
|
473
|
+
workingDir: /app
|
|
474
|
+
command: <%= command %>
|
|
475
|
+
env:
|
|
476
|
+
- name: PYJO_MODE
|
|
477
|
+
value: development
|
|
478
|
+
- name: UV_CACHE_DIR
|
|
479
|
+
value: /uv/cache
|
|
480
|
+
% if project:
|
|
481
|
+
- name: UV_PROJECT_ENVIRONMENT
|
|
482
|
+
value: /uv/venv
|
|
483
|
+
% else:
|
|
484
|
+
- name: VIRTUAL_ENV
|
|
485
|
+
value: /uv/venv
|
|
486
|
+
% end
|
|
487
|
+
startupProbe:
|
|
488
|
+
failureThreshold: 300
|
|
489
|
+
volumeMounts:
|
|
490
|
+
- name: app
|
|
491
|
+
mountPath: /app
|
|
492
|
+
- name: uv
|
|
493
|
+
mountPath: /uv
|
|
494
|
+
volumes:
|
|
495
|
+
% if host_path:
|
|
496
|
+
- name: app
|
|
497
|
+
hostPath:
|
|
498
|
+
path: <%= host_path %>
|
|
499
|
+
type: Directory
|
|
500
|
+
% else:
|
|
501
|
+
- name: files
|
|
502
|
+
configMap:
|
|
503
|
+
name: <%= name %>-files
|
|
504
|
+
items:
|
|
505
|
+
% for key, _, path in files:
|
|
506
|
+
- key: <%= key %>
|
|
507
|
+
path: <%= path %>
|
|
508
|
+
% end
|
|
509
|
+
- name: app
|
|
510
|
+
emptyDir: {}
|
|
511
|
+
% end
|
|
512
|
+
- name: uv
|
|
513
|
+
emptyDir: {}
|
|
514
|
+
"""
|