ominfra 0.0.0.dev51__py3-none-any.whl → 0.0.0.dev53__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.
ominfra/.manifests.json CHANGED
@@ -1 +1,17 @@
1
- []
1
+ [
2
+ {
3
+ "module": ".tailscale.cli",
4
+ "attr": "_CLI_MODULE",
5
+ "file": "ominfra/tailscale/cli.py",
6
+ "line": 104,
7
+ "value": {
8
+ "$omdev.cli.types.CliModule": {
9
+ "cmd_name": [
10
+ "tailscale",
11
+ "ts"
12
+ ],
13
+ "mod_name": "ominfra.tailscale.cli"
14
+ }
15
+ }
16
+ }
17
+ ]
ominfra/ssh.py CHANGED
@@ -172,9 +172,9 @@ async def _a_main() -> None:
172
172
  cfg = load_secrets()
173
173
 
174
174
  sc = SshConfig(
175
- host=cfg['ec2_ssh_host'],
176
- username=cfg['ec2_ssh_user'],
177
- key_file_path=cfg['ec2_ssh_key_file'],
175
+ host=cfg.get('ec2_ssh_host').reveal(),
176
+ username=cfg.get('ec2_ssh_user').reveal(),
177
+ key_file_path=cfg.get('ec2_ssh_key_file').reveal(),
178
178
  )
179
179
 
180
180
  for scr in [
File without changes
@@ -0,0 +1,113 @@
1
+ import re
2
+ import subprocess
3
+ import sys
4
+ import typing as ta
5
+
6
+ from omdev.cli import CliModule
7
+ from omlish import argparse as ap
8
+ from omlish import cached
9
+ from omlish import collections as col
10
+ from omlish import dataclasses as dc
11
+ from omlish import lang
12
+ from omlish import marshal as msh
13
+ from omlish.formats import json
14
+
15
+
16
+ ##
17
+
18
+
19
+ @dc.dataclass(frozen=True)
20
+ @msh.update_object_metadata(field_naming=msh.Naming.CAMEL, unknown_field='x')
21
+ class CliNode:
22
+ id: str = dc.xfield() | msh.update_field_metadata(name='ID')
23
+ public_key: str = dc.xfield()
24
+ host_name: str = dc.xfield()
25
+ dns_name: str | None = dc.xfield(None) | msh.update_field_metadata(name='DNSName')
26
+ tailscale_ips: ta.Sequence[str] | None = dc.xfield(None) | msh.update_field_metadata(name='TailscaleIPs')
27
+ allowed_ips: ta.Sequence[str] | None = dc.xfield(None) | msh.update_field_metadata(name='AllowedPs')
28
+ tags: ta.Sequence[str] | None = None
29
+
30
+ x: ta.Mapping[str, ta.Any] | None = None
31
+
32
+
33
+ @dc.dataclass(frozen=True)
34
+ @msh.update_object_metadata(field_naming=msh.Naming.CAMEL, unknown_field='x')
35
+ class CliStatus:
36
+ version: str | None = None
37
+ backend_state: str | None = None
38
+ tailscale_ips: ta.Sequence[str] | None = dc.xfield(None) | msh.update_field_metadata(name='TailscaleIPs')
39
+ self: CliNode | None = None
40
+ peers: ta.Mapping[str, CliNode] | None = dc.xfield(None) | msh.update_field_metadata(name='Peer')
41
+
42
+ @cached.property
43
+ def nodes(self) -> ta.Sequence[CliNode]:
44
+ return [
45
+ *([self.self] if self.self is not None else []),
46
+ *(self.peers.values() if self.peers else []),
47
+ ]
48
+
49
+ @cached.property
50
+ def nodes_by_host_name(self) -> ta.Mapping[str, CliNode]:
51
+ return col.make_map(((n.host_name, n) for n in self.nodes if n.host_name), strict=True)
52
+
53
+ x: ta.Mapping[str, ta.Any] | None = None
54
+
55
+
56
+ ##
57
+
58
+
59
+ _IP_V4_PAT = re.compile(r'\d{1,3}(\.\d{1,3}){3}')
60
+
61
+
62
+ class Cli(ap.Cli):
63
+ @lang.cached_function
64
+ def bin(self) -> str:
65
+ if sys.platform == 'darwin':
66
+ return '/Applications/Tailscale.app/Contents/MacOS/Tailscale'
67
+ else:
68
+ return 'tailscale'
69
+
70
+ @ap.command(name='bin')
71
+ def bin_cmd(self) -> None:
72
+ print(self.bin())
73
+
74
+ def status(self) -> CliStatus:
75
+ stdout = subprocess.check_output([
76
+ self.bin(),
77
+ 'status',
78
+ '--json',
79
+ ])
80
+ return msh.unmarshal(json.loads(stdout.decode()), CliStatus)
81
+
82
+ @ap.command(
83
+ ap.arg('name', nargs='?'),
84
+ name='status',
85
+ )
86
+ def status_cmd(self) -> None:
87
+ status = self.status()
88
+ out: ta.Any
89
+ if (name := self.args.name):
90
+ out = status.nodes_by_host_name[name]
91
+ else:
92
+ out = status
93
+ print(json.dumps_pretty(msh.marshal(out)))
94
+
95
+ @ap.command(
96
+ ap.arg('name'),
97
+ )
98
+ def ip(self) -> None:
99
+ node = self.status().nodes_by_host_name[self.args.name]
100
+ ips = [i for i in node.tailscale_ips or () if _IP_V4_PAT.fullmatch(i)]
101
+ print(ips[0])
102
+
103
+
104
+ # @omlish-manifest
105
+ _CLI_MODULE = CliModule(['tailscale', 'ts'], __name__)
106
+
107
+
108
+ def _main() -> None:
109
+ Cli()()
110
+
111
+
112
+ if __name__ == '__main__':
113
+ _main()
@@ -12,7 +12,6 @@ unique server ids:
12
12
  """
13
13
  import json
14
14
  import pprint
15
- import typing as ta
16
15
 
17
16
  import urllib3
18
17
 
@@ -20,13 +19,14 @@ from omdev.secrets import load_secrets
20
19
  from omlish import check
21
20
  from omlish import dataclasses as dc
22
21
  from omlish import lang
22
+ from omlish import secrets as sec
23
23
 
24
24
 
25
25
  ##
26
26
 
27
27
 
28
28
  @lang.cached_function
29
- def _get_secrets() -> dict[str, ta.Any]:
29
+ def _get_secrets() -> sec.Secrets:
30
30
  return load_secrets()
31
31
 
32
32
 
@@ -72,9 +72,9 @@ def get_ec2_servers() -> list[Ec2Server]:
72
72
  cfg = _get_secrets()
73
73
  import boto3
74
74
  session = boto3.Session(
75
- aws_access_key_id=cfg['aws_access_key_id'],
76
- aws_secret_access_key=cfg['aws_secret_access_key'],
77
- region_name=cfg['aws_region'],
75
+ aws_access_key_id=cfg.get('aws_access_key_id').reveal(),
76
+ aws_secret_access_key=cfg.get('aws_secret_access_key').reveal(),
77
+ region_name=cfg.get('aws_region').reveal(),
78
78
  )
79
79
  ec2 = session.client('ec2')
80
80
  resp = ec2.describe_instances()
@@ -103,9 +103,9 @@ def get_rds_instances() -> list[RdsInstance]:
103
103
  cfg = _get_secrets()
104
104
  import boto3
105
105
  session = boto3.Session(
106
- aws_access_key_id=cfg['aws_access_key_id'],
107
- aws_secret_access_key=cfg['aws_secret_access_key'],
108
- region_name=cfg['aws_region'],
106
+ aws_access_key_id=cfg.get('aws_access_key_id').reveal(),
107
+ aws_secret_access_key=cfg.get('aws_secret_access_key').reveal(),
108
+ region_name=cfg.get('aws_region').reveal(),
109
109
  )
110
110
  rds = session.client('rds')
111
111
  resp = rds.describe_db_instances()
@@ -132,13 +132,17 @@ class GcpServer(Server):
132
132
 
133
133
  def get_gcp_servers() -> list[GcpServer]:
134
134
  cfg = _get_secrets()
135
+ creds = cfg.try_get('gcp_oauth2').reveal()
136
+
135
137
  from google.oauth2 import service_account
136
- credentials = service_account.Credentials.from_service_account_info(cfg['gcp_oauth2'])
138
+ credentials = service_account.Credentials.from_service_account_info(json.loads(creds))
139
+
137
140
  from google.cloud import compute_v1
138
141
  instance_client = compute_v1.InstancesClient(credentials=credentials)
139
142
  request = compute_v1.AggregatedListInstancesRequest()
140
- request.project = cfg['gcp_project_id']
143
+ request.project = cfg.get('gcp_project_id').reveal()
141
144
  request.max_results = 50
145
+
142
146
  out: list[GcpServer] = []
143
147
  for zone, response in instance_client.aggregated_list(request=request):
144
148
  for instance in (response.instances or []):
@@ -148,6 +152,7 @@ def get_gcp_servers() -> list[GcpServer]:
148
152
  id=instance.name,
149
153
  zone=zone,
150
154
  ))
155
+
151
156
  return out
152
157
 
153
158
 
@@ -160,7 +165,7 @@ class RunpodServer(Server):
160
165
 
161
166
 
162
167
  def get_runpod_servers() -> list[RunpodServer]:
163
- api_key = _get_secrets()['runpod_api_key']
168
+ api_key = _get_secrets().get('runpod_api_key').reveal()
164
169
  query = 'query Pods { myself { pods { id runtime { ports { ip isIpPublic privatePort publicPort type } } } } }'
165
170
  resp = urllib3.request(
166
171
  'POST',
@@ -190,7 +195,7 @@ class LambdaLabsServer(Server):
190
195
 
191
196
 
192
197
  def get_lambda_labs_servers() -> list[LambdaLabsServer]:
193
- api_key = _get_secrets()['lambda_labs_api_key']
198
+ api_key = _get_secrets().get('lambda_labs_api_key').reveal()
194
199
  resp = urllib3.request(
195
200
  'GET',
196
201
  'https://cloud.lambdalabs.com/api/v1/instances',
@@ -217,7 +222,7 @@ class DigitalOceanServer(Server):
217
222
 
218
223
 
219
224
  def get_digital_ocean_servers() -> list[DigitalOceanServer]:
220
- api_key = _get_secrets()['digital_ocean_api_key']
225
+ api_key = _get_secrets().get('digital_ocean_api_key').reveal()
221
226
  resp = urllib3.request(
222
227
  'GET',
223
228
  'https://api.digitalocean.com/v2/droplets',
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ominfra
3
- Version: 0.0.0.dev51
3
+ Version: 0.0.0.dev53
4
4
  Summary: ominfra
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -12,8 +12,8 @@ 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: omdev ==0.0.0.dev51
16
- Requires-Dist: omlish ==0.0.0.dev51
15
+ Requires-Dist: omdev ==0.0.0.dev53
16
+ Requires-Dist: omlish ==0.0.0.dev53
17
17
  Provides-Extra: all
18
18
  Requires-Dist: paramiko ~=3.5 ; extra == 'all'
19
19
  Requires-Dist: asyncssh ~=2.17 ; (python_version < "3.13") and extra == 'all'
@@ -1,8 +1,8 @@
1
- ominfra/.manifests.json,sha256=N1F-Xz3GaBn2H1p7uKzhkhKCQV8QVR0t76XD6wmFtXA,3
1
+ ominfra/.manifests.json,sha256=f_sjpvGcLg_qmHDgUvtQ1gPdNfomJNPm75nEKJZziOY,310
2
2
  ominfra/__about__.py,sha256=iYpUXgOtMUoc5leWMmABMGn81LS_nLzDLZU198aCwWk,730
3
3
  ominfra/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  ominfra/cmds.py,sha256=E0AfnvEmnKntXWvmLW5L05_NeDpBET1VBXn7vV6EwBQ,2083
5
- ominfra/ssh.py,sha256=0GH_dpjCG0vWCT2Lh2v6pB-Y4IRsVNXfA5wDsZDvyKk,5392
5
+ ominfra/ssh.py,sha256=jQpc4WvkMckIfk4vILda8zFaeharRqc_6wxW50b0OjQ,5431
6
6
  ominfra/clouds/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  ominfra/clouds/aws/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  ominfra/clouds/aws/auth.py,sha256=EW3lK1U0hnjXkTn1KWJeuv9GG0ibbKdvgLD0P6HJtwo,5502
@@ -39,11 +39,13 @@ ominfra/pyremote/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
39
39
  ominfra/pyremote/_runcommands.py,sha256=gxmbQYUwi51ekiVtwciY18_8Vx7pzgeIycAgaZeTxlQ,26342
40
40
  ominfra/pyremote/bootstrap.py,sha256=RvMO3YGaN1E4sgUi1JEtiPak8cjvqtc_vRCq1yqbeZg,3370
41
41
  ominfra/pyremote/runcommands.py,sha256=bviS0_TDIoZVAe4h-_iavbvJtVSFu8lnk7fQ5iasCWE,1571
42
+ ominfra/tailscale/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
+ ominfra/tailscale/cli.py,sha256=Ltg6RVFsMLLPjLzoGwM6sxjmwjEVEYHAdrqmCc4N1HM,3174
42
44
  ominfra/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
- ominfra/tools/listresources.py,sha256=4LhqZcBHVIAME0ulYKjPJV1tVKKBevPSGKaJXbxTBbI,5917
44
- ominfra-0.0.0.dev51.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
45
- ominfra-0.0.0.dev51.dist-info/METADATA,sha256=A6r4CSN9px9sae8PEU0eFUq7hq7dRdiezumk1qfU0KM,799
46
- ominfra-0.0.0.dev51.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
47
- ominfra-0.0.0.dev51.dist-info/entry_points.txt,sha256=kgecQ2MgGrM9qK744BoKS3tMesaC3yjLnl9pa5CRczg,37
48
- ominfra-0.0.0.dev51.dist-info/top_level.txt,sha256=E-b2OHkk_AOBLXHYZQ2EOFKl-_6uOGd8EjeG-Zy6h_w,8
49
- ominfra-0.0.0.dev51.dist-info/RECORD,,
45
+ ominfra/tools/listresources.py,sha256=L4t5rszm9ulcdWyr7n48_R9d5Etg4S2a4WQhlbHDtnQ,6106
46
+ ominfra-0.0.0.dev53.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
47
+ ominfra-0.0.0.dev53.dist-info/METADATA,sha256=NJm_tslqMbWChDqviFPBsJJjxr8uFmGRn2ugq38yT4A,799
48
+ ominfra-0.0.0.dev53.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
49
+ ominfra-0.0.0.dev53.dist-info/entry_points.txt,sha256=kgecQ2MgGrM9qK744BoKS3tMesaC3yjLnl9pa5CRczg,37
50
+ ominfra-0.0.0.dev53.dist-info/top_level.txt,sha256=E-b2OHkk_AOBLXHYZQ2EOFKl-_6uOGd8EjeG-Zy6h_w,8
51
+ ominfra-0.0.0.dev53.dist-info/RECORD,,