py-pve-cloud 3.2.0rc0__tar.gz → 3.2.2__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 (36) hide show
  1. {py_pve_cloud-3.2.0rc0/src/py_pve_cloud.egg-info → py_pve_cloud-3.2.2}/PKG-INFO +2 -2
  2. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/pyproject.toml +2 -2
  3. py_pve_cloud-3.2.2/src/pve_cloud/_version.py +1 -0
  4. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/cli/pvcli.py +13 -4
  5. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/cli/pvclu.py +5 -0
  6. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/cli/pxrpc.py +201 -47
  7. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/lib/inventory.py +3 -1
  8. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2/src/py_pve_cloud.egg-info}/PKG-INFO +2 -2
  9. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/py_pve_cloud.egg-info/requires.txt +1 -1
  10. py_pve_cloud-3.2.0rc0/src/pve_cloud/_version.py +0 -1
  11. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/LICENSE.md +0 -0
  12. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/README.md +0 -0
  13. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/setup.cfg +0 -0
  14. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/lib/ssh.py +0 -0
  15. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/orm/alchemy.py +0 -0
  16. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/orm/alembic.ini +0 -0
  17. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/orm/migrations/env.py +0 -0
  18. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/orm/migrations/versions/04398db10434_external_cp_extra_sans.py +0 -0
  19. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/orm/migrations/versions/0ad803c51325_machine_type_refactor.py +0 -0
  20. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/orm/migrations/versions/24a548bfce3e_len_rules_enforcements.py +0 -0
  21. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/orm/migrations/versions/27724e407e2b_proxy_fqdn.py +0 -0
  22. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/orm/migrations/versions/3c95509a5de9_fix.py +0 -0
  23. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/orm/migrations/versions/7496c3f3b2e8_fix_secrets_constraint.py +0 -0
  24. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/orm/migrations/versions/7868bcd05006_migrate_old.py +0 -0
  25. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/orm/migrations/versions/7dea8c4ee39f_init.py +0 -0
  26. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/orm/migrations/versions/944a8fd5d5bc_ext_ctrl_plns.py +0 -0
  27. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/orm/migrations/versions/9de11ef8ba39_vm_vars_and_cloud_secrets.py +0 -0
  28. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/orm/migrations/versions/d9b711555be8_ext_control_plane.py +0 -0
  29. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/orm/migrations/versions/e60b9cc63413_ingress_generic.py +0 -0
  30. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/orm/migrations/versions/e6ae1e138c1f_change_scope_cloud.py +0 -0
  31. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/orm/migrations/versions/ee4924ac4f60_secrets_plural_type.py +0 -0
  32. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/orm/migrations/versions/fdcb5aa33b76_slop_firewall_seperation.py +0 -0
  33. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/py_pve_cloud.egg-info/SOURCES.txt +0 -0
  34. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/py_pve_cloud.egg-info/dependency_links.txt +0 -0
  35. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/py_pve_cloud.egg-info/entry_points.txt +0 -0
  36. {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/py_pve_cloud.egg-info/top_level.txt +0 -0
@@ -1,10 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: py-pve-cloud
3
- Version: 3.2.0rc0
3
+ Version: 3.2.2
4
4
  Author-email: Tobias Huebner <tobias.huebner@vmzberlin.com>
5
5
  License-Expression: GPL-3.0-or-later
6
6
  License-File: LICENSE.md
7
- Requires-Dist: PyYAML==6.0.3
8
7
  Requires-Dist: psycopg2-binary==2.9.11
9
8
  Requires-Dist: SQLAlchemy==2.0.45
10
9
  Requires-Dist: alembic==1.18.0
@@ -14,4 +13,5 @@ Requires-Dist: dnspython==2.8.0
14
13
  Requires-Dist: rpyc==6.0.2
15
14
  Requires-Dist: fabric==3.2.3
16
15
  Requires-Dist: asyncssh==2.22.0
16
+ Requires-Dist: pve-cloud-schemas<3.2.0,>=3.1.7
17
17
  Dynamic: license-file
@@ -8,7 +8,6 @@ authors = [{ name = "Tobias Huebner", email = "tobias.huebner@vmzberlin.com" }]
8
8
  license = "GPL-3.0-or-later"
9
9
  license-files = ["LICENSE.md"]
10
10
  dependencies = [
11
- "PyYAML==6.0.3",
12
11
  "psycopg2-binary==2.9.11",
13
12
  "SQLAlchemy==2.0.45",
14
13
  "alembic==1.18.0",
@@ -17,7 +16,8 @@ dependencies = [
17
16
  "dnspython==2.8.0",
18
17
  "rpyc==6.0.2",
19
18
  "fabric==3.2.3",
20
- "asyncssh==2.22.0"
19
+ "asyncssh==2.22.0",
20
+ "pve-cloud-schemas>=3.1.7,<3.2.0"
21
21
  ]
22
22
  dynamic = ["version"]
23
23
 
@@ -0,0 +1 @@
1
+ __version__ = "3.2.2"
@@ -4,11 +4,12 @@ from types import SimpleNamespace
4
4
 
5
5
  import yaml
6
6
  from proxmoxer import ProxmoxAPI
7
- from pve_cloud_schemas.validate import validate_cloud_dyn_inv
7
+ from pve_cloud_schemas.validate import (validate_cloud_dyn_inv,
8
+ validate_cluster_vars)
8
9
 
9
10
  from pve_cloud.cli.pvclu import (get_ssh_master_kubeconfig,
10
11
  get_ssh_remote_master_kubeconfig)
11
- from pve_cloud.cli.pxrpc import launch_pxrpc
12
+ from pve_cloud.cli.pxrpc import PxServiceEnum, launch_pxrpc
12
13
  from pve_cloud.lib.inventory import (get_cloud_domain, get_cluster_vars,
13
14
  get_online_pve_host, get_pve_inventory,
14
15
  get_target_cluster)
@@ -109,12 +110,19 @@ def connect_remote_cluster(args):
109
110
  jump_host = args.jump_hosts.split(",")[0]
110
111
 
111
112
  with launch_pxrpc(
112
- jump_host, args.pve_host, init_venv=True, local_pypi_ip=args.local_pypi_ip
113
+ jump_host,
114
+ args.pve_host,
115
+ init_venv=True,
116
+ local_pypi_ip=args.local_pypi_ip,
117
+ service=PxServiceEnum.PROXMOXER,
113
118
  ) as (pxrpc, pve_host):
114
119
 
115
120
  def read_cluster_vars():
116
121
  result = pve_host.run("cat /etc/pve/cloud/cluster_vars.yaml")
117
- return yaml.safe_load(result.stdout.strip())
122
+ cluster_vars = yaml.safe_load(result.stdout.strip())
123
+ validate_cluster_vars(cluster_vars)
124
+
125
+ return cluster_vars
118
126
 
119
127
  init_funcs = SimpleNamespace(
120
128
  cluster_vars=read_cluster_vars,
@@ -196,6 +204,7 @@ def print_kubeconfig(args):
196
204
  _, stdout, _ = pve_ssh.exec_command("cat /etc/pve/cloud/cluster_vars.yaml")
197
205
 
198
206
  cluster_vars = yaml.safe_load(stdout.read().decode("utf-8"))
207
+ validate_cluster_vars(cluster_vars)
199
208
 
200
209
  print(get_ssh_master_kubeconfig(cluster_vars, inventory["stack_name"]))
201
210
 
@@ -6,6 +6,8 @@ import dns.resolver
6
6
  import paramiko
7
7
  import yaml
8
8
  from fabric import Connection
9
+ from pve_cloud_schemas.validate import (validate_cloud_dyn_inv,
10
+ validate_cluster_vars)
9
11
 
10
12
  from pve_cloud.cli.pxrpc import launch_pxrpc
11
13
  from pve_cloud.lib.inventory import (get_cloud_domain, get_online_pve_host,
@@ -30,6 +32,7 @@ def get_ssh_remote_master_kubeconfig(stack_name, external_san, jump_host, pve_ho
30
32
 
31
33
  result = pve_host_conn.run("cat /etc/pve/cloud/cluster_vars.yaml")
32
34
  cluster_vars = yaml.safe_load(result.stdout.strip())
35
+ validate_cluster_vars(cluster_vars)
33
36
 
34
37
  ddns_ips = pxrpc.resolve_k8s_master(
35
38
  [cluster_vars["bind_master_ip"], cluster_vars["bind_slave_ip"]],
@@ -95,6 +98,7 @@ def get_ssh_master_kubeconfig(cluster_vars, stack_name):
95
98
  return yaml.safe_dump(admin_conf)
96
99
 
97
100
 
101
+ # todo: rename function and implement rsync terraform cache mirroring like in terraform e2e suite
98
102
  def export_pg_conn_str(args):
99
103
  if not args.target_pve and not args.cloud_domain:
100
104
  raise RuntimeError("Neither --target-pve nor --cloud-domain was specified.")
@@ -119,6 +123,7 @@ def export_pg_conn_str(args):
119
123
  with connect_host(pve_host, jump_host=jump_host) as ssh:
120
124
  _, stdout, _ = ssh.exec_command("cat /etc/pve/cloud/cluster_vars.yaml")
121
125
  cluster_vars = yaml.safe_load(stdout.read().decode("utf-8"))
126
+ validate_cluster_vars(cluster_vars)
122
127
 
123
128
  _, stdout, _ = ssh.exec_command("cat /etc/pve/cloud/secrets/patroni.pass")
124
129
  patroni_pass = stdout.read().decode("utf-8").strip()
@@ -2,6 +2,7 @@ import asyncio
2
2
  import json
3
3
  import multiprocessing
4
4
  import os
5
+ import re
5
6
  import signal
6
7
  import socket
7
8
  import subprocess
@@ -9,13 +10,18 @@ import sys
9
10
  import time
10
11
  from concurrent.futures import ProcessPoolExecutor
11
12
  from contextlib import asynccontextmanager, contextmanager
13
+ from enum import StrEnum
12
14
 
15
+ import dns.rcode
13
16
  import dns.resolver
17
+ import dns.tsigkeyring
18
+ import dns.update
14
19
  import pve_cloud._version as pxc_version
15
20
  import rpyc
16
21
  import yaml
17
22
  from fabric import Connection
18
23
  from proxmoxer import ProxmoxAPI
24
+ from pve_cloud_schemas.validate import validate_cluster_vars
19
25
  from sqlalchemy import create_engine, delete, select
20
26
  from sqlalchemy.exc import IntegrityError
21
27
  from sqlalchemy.orm import Session
@@ -23,6 +29,13 @@ from sqlalchemy.orm import Session
23
29
  from pve_cloud.orm.alchemy import (AcmeX509, ProxmoxCloudSecrets,
24
30
  VirtualMachineVars)
25
31
 
32
+
33
+ # services need to implement the shutdown function
34
+ class PxServiceEnum(StrEnum):
35
+ PXRPC = "PXRPC"
36
+ PROXMOXER = "PROXMOXER"
37
+
38
+
26
39
  # initialized / launched by pvcli connect_remote_cluster
27
40
  # it is launched on a proxmox host
28
41
 
@@ -43,6 +56,43 @@ def shutdown_handler(signum, frame):
43
56
  os.killpg(os.getpgrp(), signal.SIGKILL)
44
57
 
45
58
 
59
+ @rpyc.service
60
+ class RemoteProxmoxApi(rpyc.Service):
61
+
62
+ def __init__(self):
63
+ super().__init__()
64
+
65
+ # init services required by most functions
66
+ self.proxmox = ProxmoxAPI("127.0.0.1", user="root", backend="ssh_paramiko")
67
+
68
+ # rpyc doesnt have a clean shutdown methodology
69
+ # this is the cleanest i found without triggerin eof on the clients side
70
+ @rpyc.exposed
71
+ def shutdown(self):
72
+ os.kill(MASTER_PID, signal.SIGTERM) # this triggers the shutdown handler
73
+
74
+ @rpyc.exposed
75
+ def get_pve_cluster_name(self):
76
+ # try get the cluster name
77
+ cluster_name = None
78
+ status_resp = self.proxmox.cluster.status.get()
79
+ for entry in status_resp:
80
+ if entry["id"] == "cluster":
81
+ cluster_name = entry["name"]
82
+ break
83
+
84
+ return cluster_name
85
+
86
+ @rpyc.exposed
87
+ def get_nodes(self):
88
+ return self.proxmox.nodes.get()
89
+
90
+ @rpyc.exposed
91
+ def get_node_network(self, node_name):
92
+ return self.proxmox.nodes(node_name).network.get()
93
+
94
+
95
+ @rpyc.service
46
96
  class PxrpcService(rpyc.Service):
47
97
 
48
98
  # functions for constructor
@@ -54,6 +104,7 @@ class PxrpcService(rpyc.Service):
54
104
  check=True,
55
105
  )
56
106
  cluster_vars = yaml.safe_load(result_vars.stdout)
107
+ validate_cluster_vars(cluster_vars)
57
108
 
58
109
  return cluster_vars
59
110
 
@@ -69,43 +120,49 @@ class PxrpcService(rpyc.Service):
69
120
 
70
121
  return f"postgresql+psycopg2://postgres:{patroni_pass}@{cluster_vars['pve_haproxy_floating_ip_internal']}:5000/pve_cloud?sslmode=disable"
71
122
 
72
- def __init__(self):
73
- super().__init__()
123
+ def get_internal_bind_key(self):
124
+ result_key = subprocess.run(
125
+ ["cat", "/etc/pve/cloud/secrets/internal.key"],
126
+ capture_output=True,
127
+ text=True,
128
+ check=True,
129
+ )
74
130
 
75
- # init services required by most functions
76
- self.proxmox = ProxmoxAPI("127.0.0.1", user="root", backend="ssh_paramiko")
77
- self.cluster_vars = self.get_cluster_vars()
78
- self.patroni_cstr = self.get_pg_conn_str(
79
- self.cluster_vars
80
- ) # we need postgres for almost anything
131
+ return re.search(r'secret\s+"([^"]+)";', result_key.stdout).group(1)
132
+
133
+ def __init__(self, cluster_vars=None, patroni_cstr=None, internal_bind_key=None):
134
+ super().__init__()
81
135
 
82
- def exposed_e2e_return(self):
136
+ if cluster_vars:
137
+ self.cluster_vars = cluster_vars
138
+ else:
139
+ self.cluster_vars = self.get_cluster_vars()
140
+
141
+ if internal_bind_key:
142
+ self.internal_bind_key = internal_bind_key
143
+ else:
144
+ self.internal_bind_key = self.get_internal_bind_key()
145
+
146
+ if patroni_cstr:
147
+ self.patroni_cstr = patroni_cstr
148
+ else:
149
+ self.patroni_cstr = self.get_pg_conn_str(
150
+ self.cluster_vars
151
+ ) # we need postgres for almost anything
152
+
153
+ @rpyc.exposed
154
+ def e2e_return(self):
83
155
  print(f"e2e return on pid {os.getpid()}")
84
156
  return self.patroni_cstr
85
157
 
86
- def exposed_get_pve_cluster_name(self):
87
- # try get the cluster name
88
- cluster_name = None
89
- status_resp = self.proxmox.cluster.status.get()
90
- for entry in status_resp:
91
- if entry["id"] == "cluster":
92
- cluster_name = entry["name"]
93
- break
94
-
95
- return cluster_name
96
-
97
- def exposed_get_nodes(self):
98
- return self.proxmox.nodes.get()
99
-
100
- def exposed_get_node_network(self, node_name):
101
- return self.proxmox.nodes(node_name).network.get()
102
-
103
158
  # rpyc doesnt have a clean shutdown methodology
104
159
  # this is the cleanest i found without triggerin eof on the clients side
105
- def exposed_shutdown(self):
160
+ @rpyc.exposed
161
+ def shutdown(self):
106
162
  os.kill(MASTER_PID, signal.SIGTERM) # this triggers the shutdown handler
107
163
 
108
- def exposed_resolve_k8s_master(self, nameservers, a_rs_hostname):
164
+ @rpyc.exposed
165
+ def resolve_k8s_master(self, nameservers, a_rs_hostname):
109
166
  resolver = dns.resolver.Resolver()
110
167
  resolver.nameservers = nameservers
111
168
  ddns_answer = resolver.resolve(a_rs_hostname)
@@ -114,7 +171,8 @@ class PxrpcService(rpyc.Service):
114
171
 
115
172
  return ddns_ips
116
173
 
117
- def exposed_e2e_inject_cert(self, stack_fqdn, record0):
174
+ @rpyc.exposed
175
+ def e2e_inject_cert(self, stack_fqdn, record0):
118
176
  engine = create_engine(self.patroni_cstr)
119
177
 
120
178
  # update certs and mirror pull secret
@@ -129,7 +187,8 @@ class PxrpcService(rpyc.Service):
129
187
  session.merge(copy_cert)
130
188
  session.commit()
131
189
 
132
- def exposed_inject_cloud_secret(
190
+ @rpyc.exposed
191
+ def inject_cloud_secret(
133
192
  self, cloud_domain, secret_name, secret_data_json, secret_type
134
193
  ):
135
194
  engine = create_engine(self.patroni_cstr)
@@ -151,7 +210,31 @@ class PxrpcService(rpyc.Service):
151
210
  session.rollback()
152
211
  return False
153
212
 
154
- def exposed_delete_cloud_secret(self, cloud_domain, secret_name):
213
+ @rpyc.exposed
214
+ def merge_cloud_secret(
215
+ self, cloud_domain, secret_name, secret_data_json, secret_type
216
+ ):
217
+ engine = create_engine(self.patroni_cstr)
218
+
219
+ with Session(engine) as session:
220
+ try:
221
+ session.merge(
222
+ ProxmoxCloudSecrets(
223
+ cloud_domain=cloud_domain,
224
+ secret_name=secret_name,
225
+ secret_data=json.loads(secret_data_json),
226
+ secret_type=secret_type,
227
+ )
228
+ )
229
+ session.commit()
230
+ return True
231
+
232
+ except IntegrityError as e:
233
+ session.rollback()
234
+ return False
235
+
236
+ @rpyc.exposed
237
+ def delete_cloud_secret(self, cloud_domain, secret_name):
155
238
 
156
239
  engine = create_engine(self.patroni_cstr)
157
240
 
@@ -164,7 +247,8 @@ class PxrpcService(rpyc.Service):
164
247
  result = session.execute(stmt)
165
248
  session.commit()
166
249
 
167
- def exposed_get_cloud_secret(self, cloud_domain, secret_name):
250
+ @rpyc.exposed
251
+ def get_cloud_secret(self, cloud_domain, secret_name):
168
252
 
169
253
  engine = create_engine(self.patroni_cstr)
170
254
 
@@ -181,7 +265,8 @@ class PxrpcService(rpyc.Service):
181
265
  secret_data_json = json.dumps(record.secret_data)
182
266
  return secret_data_json
183
267
 
184
- def exposed_get_cloud_secrets(self, cloud_domain, secret_type):
268
+ @rpyc.exposed
269
+ def get_cloud_secrets(self, cloud_domain, secret_type):
185
270
  engine = create_engine(self.patroni_cstr)
186
271
 
187
272
  with Session(engine) as session:
@@ -196,7 +281,8 @@ class PxrpcService(rpyc.Service):
196
281
  )
197
282
  return secrets_json
198
283
 
199
- def exposed_get_vm_vars_blake(self, blake_ids_json, cloud_domain):
284
+ @rpyc.exposed
285
+ def get_vm_vars_blake(self, blake_ids_json, cloud_domain):
200
286
  engine = create_engine(self.patroni_cstr)
201
287
 
202
288
  blake_ids = json.loads(blake_ids_json)
@@ -211,7 +297,8 @@ class PxrpcService(rpyc.Service):
211
297
  vars_json = json.dumps({entry.blake_id: entry.vm_vars for entry in records})
212
298
  return vars_json
213
299
 
214
- def exposed_get_dns_a_record(self, host):
300
+ @rpyc.exposed
301
+ def get_dns_a_record(self, host):
215
302
  # get nameservers of the cloud (global in all cluster vars defined)
216
303
  resolver = dns.resolver.Resolver()
217
304
  resolver.nameservers = [
@@ -225,9 +312,42 @@ class PxrpcService(rpyc.Service):
225
312
  except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN):
226
313
  return "[]"
227
314
 
228
- def exposed_create_external_acme_tls(
229
- self, stack_fqdn, cert_config_json, ec_csr_json
230
- ):
315
+ @rpyc.exposed
316
+ def create_cname_record(self, zone, name, cname, ttl):
317
+ keyring = dns.tsigkeyring.from_text({"internal.": self.internal_bind_key})
318
+
319
+ update = dns.update.Update(zone, keyring=keyring)
320
+ update.add(name, ttl, "CNAME", cname)
321
+
322
+ try:
323
+ response = dns.query.tcp(update, self.cluster_vars["bind_master_ip"])
324
+
325
+ if response.rcode() != dns.rcode.NOERROR:
326
+ return False, dns.rcode.to_text(response.rcode())
327
+
328
+ return True, None
329
+ except Exception as e:
330
+ return False, str(e)
331
+
332
+ @rpyc.exposed
333
+ def delete_cname_record(self, zone, name):
334
+ keyring = dns.tsigkeyring.from_text({"internal.": self.internal_bind_key})
335
+
336
+ update = dns.update.Update(zone, keyring=keyring)
337
+ update.delete(name, "CNAME")
338
+
339
+ try:
340
+ response = dns.query.tcp(update, self.cluster_vars["bind_master_ip"])
341
+
342
+ if response.rcode() != dns.rcode.NOERROR:
343
+ return False, dns.rcode.to_text(response.rcode())
344
+
345
+ return True, None
346
+ except Exception as e:
347
+ return False, str(e)
348
+
349
+ @rpyc.exposed
350
+ def create_external_acme_tls(self, stack_fqdn, cert_config_json, ec_csr_json):
231
351
  engine = create_engine(self.patroni_cstr)
232
352
  with Session(engine) as session:
233
353
 
@@ -240,7 +360,8 @@ class PxrpcService(rpyc.Service):
240
360
  )
241
361
  session.commit()
242
362
 
243
- def exposed_delete_external_acme_tls(self, stack_fqdn):
363
+ @rpyc.exposed
364
+ def delete_external_acme_tls(self, stack_fqdn):
244
365
  engine = create_engine(self.patroni_cstr)
245
366
  with Session(engine) as session:
246
367
  stmt = delete(AcmeX509).where(
@@ -254,17 +375,25 @@ class PxrpcService(rpyc.Service):
254
375
  def main():
255
376
  from rpyc.utils.server import ForkingServer, ThreadedServer
256
377
 
257
- print("launching on", sys.argv[1])
378
+ print("launching on", sys.argv[1], sys.argv[2], sys.argv[3])
258
379
 
259
380
  # register handler in master thread / process
260
381
  signal.signal(signal.SIGTERM, shutdown_handler)
261
382
 
383
+ service_to_launch = None
384
+ match PxServiceEnum(sys.argv[3]):
385
+ # here custom inits could take place (using classpartial / constructor)
386
+ case PxServiceEnum.PXRPC:
387
+ service_to_launch = PxrpcService
388
+ case PxServiceEnum.PROXMOXER:
389
+ service_to_launch = RemoteProxmoxApi
390
+
262
391
  # launch it threaded for sync (easy tasks no paralellism)
263
392
  if sys.argv[2] == "THREADED":
264
- pxrpc_server = ThreadedServer(PxrpcService, port=int(sys.argv[1]))
393
+ pxrpc_server = ThreadedServer(service_to_launch, port=int(sys.argv[1]))
265
394
  pxrpc_server.start()
266
395
  elif sys.argv[2] == "FORKING": # forking launch for paralellism
267
- pxrpc_server = ForkingServer(PxrpcService, port=int(sys.argv[1]))
396
+ pxrpc_server = ForkingServer(service_to_launch, port=int(sys.argv[1]))
268
397
  pxrpc_server.start()
269
398
  else:
270
399
  raise RuntimeError("Unknown / missing 2 string arg THREADED/FORKING")
@@ -272,7 +401,12 @@ def main():
272
401
 
273
402
  @contextmanager
274
403
  def _launch_pxrpc_base(
275
- jump_host, pve_host, rpyc_server_type, init_venv=False, local_pypi_ip=None
404
+ jump_host: str,
405
+ pve_host: str,
406
+ rpyc_server_type: str,
407
+ rpyc_service: PxServiceEnum,
408
+ init_venv: bool = False,
409
+ local_pypi_ip: str = None,
276
410
  ):
277
411
  with Connection(host=jump_host, user="root") as jump_host_conn:
278
412
  with Connection(
@@ -317,8 +451,10 @@ def _launch_pxrpc_base(
317
451
 
318
452
  # run detached pxrpc server - we use this to execute python code remotely on the jump host
319
453
  # pkill -f pxrpc to cleanup
454
+ pxrpc_cmd = f"export PYTHONUNBUFFERED=1; /root/.pxc-venv/bin/pxrpc {open_port_remote} {rpyc_server_type} {rpyc_service} >> /var/log/pxrpc-{open_port_remote}.log 2>&1"
455
+ print("pxrpc cmd", pxrpc_cmd)
320
456
  pve_host_conn.run(
321
- f"export PYTHONUNBUFFERED=1; /root/.pxc-venv/bin/pxrpc {open_port_remote} {rpyc_server_type} >> /var/log/pxrpc-{open_port_remote}.log 2>&1",
457
+ pxrpc_cmd,
322
458
  disown=True,
323
459
  )
324
460
 
@@ -353,11 +489,18 @@ class RPyCConnectionWrapper:
353
489
 
354
490
  # client launch contextmanagers:
355
491
  @contextmanager
356
- def launch_pxrpc(jump_host, pve_host, init_venv=False, local_pypi_ip=None):
492
+ def launch_pxrpc(
493
+ jump_host: str,
494
+ pve_host: str,
495
+ init_venv: bool = False,
496
+ local_pypi_ip: str = None,
497
+ service: PxServiceEnum = PxServiceEnum.PXRPC,
498
+ ):
357
499
  with _launch_pxrpc_base(
358
500
  jump_host,
359
501
  pve_host,
360
502
  "THREADED",
503
+ service,
361
504
  init_venv=init_venv,
362
505
  local_pypi_ip=local_pypi_ip,
363
506
  ) as (local_open_port, pve_host_conn):
@@ -426,9 +569,20 @@ class AsyncRPyCPoolWrapper:
426
569
 
427
570
 
428
571
  @asynccontextmanager
429
- async def launch_pxrpc_async(jump_host, pve_host, init_venv=False, local_pypi_ip=None):
572
+ async def launch_pxrpc_async(
573
+ jump_host: str,
574
+ pve_host: str,
575
+ init_venv: bool = False,
576
+ local_pypi_ip: str = None,
577
+ service: PxServiceEnum = PxServiceEnum.PXRPC,
578
+ ):
430
579
  with _launch_pxrpc_base(
431
- jump_host, pve_host, "FORKING", init_venv=init_venv, local_pypi_ip=local_pypi_ip
580
+ jump_host,
581
+ pve_host,
582
+ "FORKING",
583
+ service,
584
+ init_venv=init_venv,
585
+ local_pypi_ip=local_pypi_ip,
432
586
  ) as (local_open_port, _):
433
587
 
434
588
  NUM_WORKERS = 4
@@ -7,7 +7,8 @@ import paramiko
7
7
  import pve_cloud._version
8
8
  import yaml
9
9
  from proxmoxer import ProxmoxAPI
10
- from pve_cloud_schemas.validate import validate_cloud_dyn_inv
10
+ from pve_cloud_schemas.validate import (validate_cloud_dyn_inv,
11
+ validate_cluster_vars)
11
12
 
12
13
  from pve_cloud.lib.ssh import check_ssh_open, connect_host
13
14
 
@@ -288,5 +289,6 @@ def get_cluster_vars(pve_host, jump_host=None):
288
289
  _, stdout, _ = ssh.exec_command("cat /etc/pve/cloud/cluster_vars.yaml")
289
290
 
290
291
  cluster_vars = yaml.safe_load(stdout.read().decode("utf-8"))
292
+ validate_cluster_vars(cluster_vars)
291
293
 
292
294
  return cluster_vars
@@ -1,10 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: py-pve-cloud
3
- Version: 3.2.0rc0
3
+ Version: 3.2.2
4
4
  Author-email: Tobias Huebner <tobias.huebner@vmzberlin.com>
5
5
  License-Expression: GPL-3.0-or-later
6
6
  License-File: LICENSE.md
7
- Requires-Dist: PyYAML==6.0.3
8
7
  Requires-Dist: psycopg2-binary==2.9.11
9
8
  Requires-Dist: SQLAlchemy==2.0.45
10
9
  Requires-Dist: alembic==1.18.0
@@ -14,4 +13,5 @@ Requires-Dist: dnspython==2.8.0
14
13
  Requires-Dist: rpyc==6.0.2
15
14
  Requires-Dist: fabric==3.2.3
16
15
  Requires-Dist: asyncssh==2.22.0
16
+ Requires-Dist: pve-cloud-schemas<3.2.0,>=3.1.7
17
17
  Dynamic: license-file
@@ -1,4 +1,3 @@
1
- PyYAML==6.0.3
2
1
  psycopg2-binary==2.9.11
3
2
  SQLAlchemy==2.0.45
4
3
  alembic==1.18.0
@@ -8,3 +7,4 @@ dnspython==2.8.0
8
7
  rpyc==6.0.2
9
8
  fabric==3.2.3
10
9
  asyncssh==2.22.0
10
+ pve-cloud-schemas<3.2.0,>=3.1.7
@@ -1 +0,0 @@
1
- __version__ = "3.2.0rc0"
File without changes
File without changes
File without changes