ominfra 0.0.0.dev9__tar.gz → 0.0.0.dev11__tar.gz

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.
Files changed (55) hide show
  1. {ominfra-0.0.0.dev9/ominfra.egg-info → ominfra-0.0.0.dev11}/PKG-INFO +5 -2
  2. ominfra-0.0.0.dev11/README.rst +5 -0
  3. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/__about__.py +1 -1
  4. ominfra-0.0.0.dev11/ominfra/clouds/aws/auth.py +222 -0
  5. ominfra-0.0.0.dev11/ominfra/pyremote/__init__.py +0 -0
  6. ominfra-0.0.0.dev11/ominfra/tools/__init__.py +0 -0
  7. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11/ominfra.egg-info}/PKG-INFO +5 -2
  8. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra.egg-info/SOURCES.txt +3 -0
  9. ominfra-0.0.0.dev11/ominfra.egg-info/requires.txt +13 -0
  10. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/pyproject.toml +7 -3
  11. ominfra-0.0.0.dev9/README.rst +0 -1
  12. ominfra-0.0.0.dev9/ominfra.egg-info/requires.txt +0 -7
  13. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/LICENSE +0 -0
  14. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/MANIFEST.in +0 -0
  15. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/__init__.py +0 -0
  16. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/bootstrap/__init__.py +0 -0
  17. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/bootstrap/bootstrap.py +0 -0
  18. {ominfra-0.0.0.dev9/ominfra/deploy → ominfra-0.0.0.dev11/ominfra/clouds}/__init__.py +0 -0
  19. {ominfra-0.0.0.dev9/ominfra/deploy/executor/concerns → ominfra-0.0.0.dev11/ominfra/clouds/aws}/__init__.py +0 -0
  20. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/cmds.py +0 -0
  21. {ominfra-0.0.0.dev9/ominfra/pyremote → ominfra-0.0.0.dev11/ominfra/deploy}/__init__.py +0 -0
  22. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/deploy/_executor.py +0 -0
  23. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/deploy/configs.py +0 -0
  24. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/deploy/executor/__init__.py +0 -0
  25. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/deploy/executor/base.py +0 -0
  26. {ominfra-0.0.0.dev9/ominfra/tools → ominfra-0.0.0.dev11/ominfra/deploy/executor/concerns}/__init__.py +0 -0
  27. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/deploy/executor/concerns/dirs.py +0 -0
  28. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/deploy/executor/concerns/nginx.py +0 -0
  29. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/deploy/executor/concerns/repo.py +0 -0
  30. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/deploy/executor/concerns/supervisor.py +0 -0
  31. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/deploy/executor/concerns/systemd.py +0 -0
  32. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/deploy/executor/concerns/user.py +0 -0
  33. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/deploy/executor/concerns/venv.py +0 -0
  34. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/deploy/executor/main.py +0 -0
  35. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/deploy/poly/__init__.py +0 -0
  36. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/deploy/poly/_main.py +0 -0
  37. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/deploy/poly/base.py +0 -0
  38. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/deploy/poly/configs.py +0 -0
  39. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/deploy/poly/deploy.py +0 -0
  40. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/deploy/poly/main.py +0 -0
  41. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/deploy/poly/nginx.py +0 -0
  42. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/deploy/poly/repo.py +0 -0
  43. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/deploy/poly/runtime.py +0 -0
  44. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/deploy/poly/site.py +0 -0
  45. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/deploy/poly/supervisor.py +0 -0
  46. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/deploy/poly/venv.py +0 -0
  47. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/deploy/remote.py +0 -0
  48. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/pyremote/_runcommands.py +0 -0
  49. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/pyremote/bootstrap.py +0 -0
  50. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/pyremote/runcommands.py +0 -0
  51. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/ssh.py +0 -0
  52. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra/tools/listresources.py +0 -0
  53. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra.egg-info/dependency_links.txt +0 -0
  54. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/ominfra.egg-info/top_level.txt +0 -0
  55. {ominfra-0.0.0.dev9 → ominfra-0.0.0.dev11}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ominfra
3
- Version: 0.0.0.dev9
3
+ Version: 0.0.0.dev11
4
4
  Summary: ominfra
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -12,7 +12,10 @@ Classifier: Operating System :: OS Independent
12
12
  Classifier: Operating System :: POSIX
13
13
  Requires-Python: >=3.12
14
14
  License-File: LICENSE
15
- Requires-Dist: omlish==0.0.0.dev9
15
+ Requires-Dist: omlish==0.0.0.dev11
16
+ Provides-Extra: all
17
+ Requires-Dist: paramiko>=3.4; extra == "all"
18
+ Requires-Dist: asyncssh>=2.16; python_version < "3.13" and extra == "all"
16
19
  Provides-Extra: ssh
17
20
  Requires-Dist: paramiko>=3.4; extra == "ssh"
18
21
  Requires-Dist: asyncssh>=2.16; python_version < "3.13" and extra == "ssh"
@@ -0,0 +1,5 @@
1
+ *omlish*
2
+
3
+ Packages installable from git via:
4
+ ::
5
+ pip install 'git+https://github.com/wrmsr/omlish@master#subdirectory=.pkg/<pkg>'
@@ -15,7 +15,7 @@ class Project(ProjectBase):
15
15
  'ssh': [
16
16
  'paramiko >= 3.4', # !! LGPL
17
17
 
18
- "asyncssh >= 2.16; python_version < '3.13'", # cffi
18
+ 'asyncssh >= 2.16; python_version < "3.13"', # cffi
19
19
  ],
20
20
  }
21
21
 
@@ -0,0 +1,222 @@
1
+ """
2
+ https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html
3
+
4
+ TODO:
5
+ - https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html
6
+ - boto / s3transfer upload_fileobj doesn't stream either lol - eagerly calcs Content-MD5
7
+ - sts tokens
8
+ - !! fix canonical_qs - sort params
9
+ """
10
+ import dataclasses as dc
11
+ import datetime
12
+ import hashlib
13
+ import hmac
14
+ import typing as ta
15
+ import urllib.parse
16
+
17
+ from omlish import check
18
+
19
+
20
+ ##
21
+
22
+
23
+ HttpMap: ta.TypeAlias = ta.Mapping[str, ta.Sequence[str]]
24
+
25
+
26
+ def make_http_map(*kvs: tuple[str, str]) -> HttpMap:
27
+ out: dict[str, list[str]] = {}
28
+ for k, v in kvs:
29
+ out.setdefault(k, []).append(v)
30
+ return out
31
+
32
+
33
+ #
34
+
35
+ @dc.dataclass(frozen=True)
36
+ class Credentials:
37
+ access_key: str
38
+ secret_key: str = dc.field(repr=False)
39
+
40
+
41
+ @dc.dataclass(frozen=True)
42
+ class Request:
43
+ method: str
44
+ url: str
45
+ headers: HttpMap = dc.field(default_factory=dict)
46
+ payload: bytes = b''
47
+
48
+
49
+ ##
50
+
51
+
52
+ def _host_from_url(url: str) -> str:
53
+ url_parts = urllib.parse.urlsplit(url)
54
+ host = check.non_empty_str(url_parts.hostname)
55
+ default_ports = {
56
+ 'http': 80,
57
+ 'https': 443,
58
+ }
59
+ if url_parts.port is not None:
60
+ if url_parts.port != default_ports.get(url_parts.scheme):
61
+ host = '%s:%d' % (host, url_parts.port)
62
+ return host
63
+
64
+
65
+ def _as_bytes(data: str | bytes) -> bytes:
66
+ return data if isinstance(data, bytes) else data.encode('utf-8')
67
+
68
+
69
+ def _sha256(data: str | bytes) -> str:
70
+ return hashlib.sha256(_as_bytes(data)).hexdigest()
71
+
72
+
73
+ def _sha256_sign(key: bytes, msg: str | bytes) -> bytes:
74
+ return hmac.new(key, _as_bytes(msg), hashlib.sha256).digest()
75
+
76
+
77
+ def _sha256_sign_hex(key: bytes, msg: str | bytes) -> str:
78
+ return hmac.new(key, _as_bytes(msg), hashlib.sha256).hexdigest()
79
+
80
+
81
+ _EMPTY_SHA256 = _sha256(b'')
82
+
83
+ _ISO8601 = '%Y%m%dT%H%M%SZ'
84
+
85
+ _SIGNED_HEADERS_BLACKLIST = frozenset([
86
+ 'expect',
87
+ 'user-agent',
88
+ 'x-amzn-trace-id',
89
+ ])
90
+
91
+
92
+ def _lower_case_http_map(d: HttpMap) -> HttpMap:
93
+ o: dict[str, list[str]] = {}
94
+ for k, vs in d.items():
95
+ o.setdefault(k.lower(), []).extend(vs)
96
+ return o
97
+
98
+
99
+ class V4AwsSigner:
100
+ def __init__(
101
+ self,
102
+ creds: Credentials,
103
+ region_name: str,
104
+ service_name: str,
105
+ ) -> None:
106
+ super().__init__()
107
+ self._creds = creds
108
+ self._region_name = region_name
109
+ self._service_name = service_name
110
+
111
+ def _validate_request(self, req: Request) -> None:
112
+ check.non_empty_str(req.method)
113
+ check.equal(req.method.upper(), req.method)
114
+ for k, vs in req.headers.items():
115
+ check.equal(k.strip(), k)
116
+ for v in vs:
117
+ check.equal(v.strip(), v)
118
+
119
+ def sign(
120
+ self,
121
+ req: Request,
122
+ *,
123
+ sign_payload: bool = False,
124
+ utcnow: datetime.datetime | None = None,
125
+ ) -> HttpMap:
126
+ self._validate_request(req)
127
+
128
+ #
129
+
130
+ if utcnow is None:
131
+ utcnow = datetime.datetime.utcnow() # noqa
132
+ req_dt = utcnow.strftime(_ISO8601)
133
+
134
+ #
135
+
136
+ parsed_url = urllib.parse.urlsplit(req.url)
137
+ canon_uri = parsed_url.path
138
+ canon_qs = parsed_url.query
139
+
140
+ #
141
+
142
+ headers_to_sign = {
143
+ k: v
144
+ for k, v in _lower_case_http_map(req.headers).items()
145
+ if k not in _SIGNED_HEADERS_BLACKLIST
146
+ }
147
+
148
+ if 'host' not in headers_to_sign:
149
+ headers_to_sign['host'] = [_host_from_url(req.url)]
150
+
151
+ headers_to_sign['x-amz-date'] = [req_dt]
152
+
153
+ hashed_payload = _sha256(req.payload) if req.payload else _EMPTY_SHA256
154
+ if sign_payload:
155
+ headers_to_sign['x-amz-content-sha256'] = [hashed_payload]
156
+
157
+ sorted_header_names = sorted(headers_to_sign)
158
+ canon_headers = ''.join([
159
+ ':'.join((k, ','.join(headers_to_sign[k]))) + '\n'
160
+ for k in sorted_header_names
161
+ ])
162
+ signed_headers = ';'.join(sorted_header_names)
163
+
164
+ #
165
+
166
+ canon_req = '\n'.join([
167
+ req.method,
168
+ canon_uri,
169
+ canon_qs,
170
+ canon_headers,
171
+ signed_headers,
172
+ hashed_payload,
173
+ ])
174
+
175
+ #
176
+
177
+ algorithm = 'AWS4-HMAC-SHA256'
178
+ scope_parts = [
179
+ req_dt[:8],
180
+ self._region_name,
181
+ self._service_name,
182
+ 'aws4_request',
183
+ ]
184
+ scope = '/'.join(scope_parts)
185
+ hashed_canon_req = _sha256(canon_req)
186
+ string_to_sign = '\n'.join([
187
+ algorithm,
188
+ req_dt,
189
+ scope,
190
+ hashed_canon_req,
191
+ ])
192
+
193
+ #
194
+
195
+ key = self._creds.secret_key
196
+ key_date = _sha256_sign(f'AWS4{key}'.encode('utf-8'), req_dt[:8]) # noqa
197
+ key_region = _sha256_sign(key_date, self._region_name)
198
+ key_service = _sha256_sign(key_region, self._service_name)
199
+ key_signing = _sha256_sign(key_service, 'aws4_request')
200
+ sig = _sha256_sign_hex(key_signing, string_to_sign)
201
+
202
+ #
203
+
204
+ cred_scope = '/'.join([
205
+ self._creds.access_key,
206
+ *scope_parts,
207
+ ])
208
+ auth = f'{algorithm} ' + ', '.join([
209
+ f'Credential={cred_scope}',
210
+ f'SignedHeaders={signed_headers}',
211
+ f'Signature={sig}',
212
+ ])
213
+
214
+ #
215
+
216
+ out = {
217
+ 'Authorization': [auth],
218
+ 'X-Amz-Date': [req_dt],
219
+ }
220
+ if sign_payload:
221
+ out['X-Amz-Content-SHA256'] = [hashed_payload]
222
+ return out
File without changes
File without changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ominfra
3
- Version: 0.0.0.dev9
3
+ Version: 0.0.0.dev11
4
4
  Summary: ominfra
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -12,7 +12,10 @@ Classifier: Operating System :: OS Independent
12
12
  Classifier: Operating System :: POSIX
13
13
  Requires-Python: >=3.12
14
14
  License-File: LICENSE
15
- Requires-Dist: omlish==0.0.0.dev9
15
+ Requires-Dist: omlish==0.0.0.dev11
16
+ Provides-Extra: all
17
+ Requires-Dist: paramiko>=3.4; extra == "all"
18
+ Requires-Dist: asyncssh>=2.16; python_version < "3.13" and extra == "all"
16
19
  Provides-Extra: ssh
17
20
  Requires-Dist: paramiko>=3.4; extra == "ssh"
18
21
  Requires-Dist: asyncssh>=2.16; python_version < "3.13" and extra == "ssh"
@@ -13,6 +13,9 @@ ominfra.egg-info/requires.txt
13
13
  ominfra.egg-info/top_level.txt
14
14
  ominfra/bootstrap/__init__.py
15
15
  ominfra/bootstrap/bootstrap.py
16
+ ominfra/clouds/__init__.py
17
+ ominfra/clouds/aws/__init__.py
18
+ ominfra/clouds/aws/auth.py
16
19
  ominfra/deploy/__init__.py
17
20
  ominfra/deploy/_executor.py
18
21
  ominfra/deploy/configs.py
@@ -0,0 +1,13 @@
1
+ omlish==0.0.0.dev11
2
+
3
+ [all]
4
+ paramiko>=3.4
5
+
6
+ [all:python_version < "3.13"]
7
+ asyncssh>=2.16
8
+
9
+ [ssh]
10
+ paramiko>=3.4
11
+
12
+ [ssh:python_version < "3.13"]
13
+ asyncssh>=2.16
@@ -12,7 +12,7 @@ authors = [
12
12
  urls = {source = 'https://github.com/wrmsr/omlish'}
13
13
  license = {text = 'BSD-3-Clause'}
14
14
  requires-python = '>=3.12'
15
- version = '0.0.0.dev9'
15
+ version = '0.0.0.dev11'
16
16
  classifiers = [
17
17
  'License :: OSI Approved :: BSD License',
18
18
  'Development Status :: 2 - Pre-Alpha',
@@ -22,13 +22,17 @@ classifiers = [
22
22
  ]
23
23
  description = 'ominfra'
24
24
  dependencies = [
25
- 'omlish == 0.0.0.dev9',
25
+ 'omlish == 0.0.0.dev11',
26
26
  ]
27
27
 
28
28
  [project.optional-dependencies]
29
+ all = [
30
+ 'paramiko >= 3.4',
31
+ 'asyncssh >= 2.16; python_version < "3.13"',
32
+ ]
29
33
  ssh = [
30
34
  'paramiko >= 3.4',
31
- "asyncssh >= 2.16; python_version < '3.13'",
35
+ 'asyncssh >= 2.16; python_version < "3.13"',
32
36
  ]
33
37
 
34
38
  [tool.setuptools]
@@ -1 +0,0 @@
1
- *omlish*
@@ -1,7 +0,0 @@
1
- omlish==0.0.0.dev9
2
-
3
- [ssh]
4
- paramiko>=3.4
5
-
6
- [ssh:python_version < "3.13"]
7
- asyncssh>=2.16
File without changes
File without changes
File without changes