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.
- {py_pve_cloud-3.2.0rc0/src/py_pve_cloud.egg-info → py_pve_cloud-3.2.2}/PKG-INFO +2 -2
- {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/pyproject.toml +2 -2
- py_pve_cloud-3.2.2/src/pve_cloud/_version.py +1 -0
- {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/cli/pvcli.py +13 -4
- {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/cli/pvclu.py +5 -0
- {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/cli/pxrpc.py +201 -47
- {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/lib/inventory.py +3 -1
- {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2/src/py_pve_cloud.egg-info}/PKG-INFO +2 -2
- {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/py_pve_cloud.egg-info/requires.txt +1 -1
- py_pve_cloud-3.2.0rc0/src/pve_cloud/_version.py +0 -1
- {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/LICENSE.md +0 -0
- {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/README.md +0 -0
- {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/setup.cfg +0 -0
- {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/lib/ssh.py +0 -0
- {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/orm/alchemy.py +0 -0
- {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/orm/alembic.ini +0 -0
- {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/orm/migrations/env.py +0 -0
- {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
- {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
- {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
- {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/orm/migrations/versions/27724e407e2b_proxy_fqdn.py +0 -0
- {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/orm/migrations/versions/3c95509a5de9_fix.py +0 -0
- {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
- {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/orm/migrations/versions/7868bcd05006_migrate_old.py +0 -0
- {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/orm/migrations/versions/7dea8c4ee39f_init.py +0 -0
- {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
- {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
- {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
- {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/pve_cloud/orm/migrations/versions/e60b9cc63413_ingress_generic.py +0 -0
- {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
- {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
- {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
- {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/py_pve_cloud.egg-info/SOURCES.txt +0 -0
- {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/py_pve_cloud.egg-info/dependency_links.txt +0 -0
- {py_pve_cloud-3.2.0rc0 → py_pve_cloud-3.2.2}/src/py_pve_cloud.egg-info/entry_points.txt +0 -0
- {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.
|
|
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,
|
|
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
|
-
|
|
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
|
|
73
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
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
|
-
|
|
160
|
+
@rpyc.exposed
|
|
161
|
+
def shutdown(self):
|
|
106
162
|
os.kill(MASTER_PID, signal.SIGTERM) # this triggers the shutdown handler
|
|
107
163
|
|
|
108
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
229
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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,
|
|
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.
|
|
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 +0,0 @@
|
|
|
1
|
-
__version__ = "3.2.0rc0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|