osism 0.20250701.0__py3-none-any.whl → 0.20250804.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.
- osism/api.py +3 -3
- osism/commands/baremetal.py +202 -2
- osism/commands/compose.py +1 -1
- osism/commands/console.py +95 -7
- osism/commands/container.py +1 -1
- osism/commands/log.py +1 -1
- osism/commands/manage.py +0 -251
- osism/commands/netbox.py +33 -10
- osism/commands/sonic.py +1168 -0
- osism/tasks/__init__.py +45 -7
- osism/tasks/ansible.py +1 -3
- osism/tasks/conductor/__init__.py +2 -2
- osism/tasks/conductor/config.py +3 -3
- osism/tasks/conductor/ironic.py +28 -8
- osism/tasks/conductor/sonic/config_generator.py +38 -14
- osism/tasks/conductor/sonic/interface.py +10 -0
- osism/tasks/conductor/utils.py +9 -2
- osism/tasks/netbox.py +3 -7
- osism/tasks/reconciler.py +3 -7
- osism/utils/__init__.py +62 -1
- osism/utils/ssh.py +250 -0
- {osism-0.20250701.0.dist-info → osism-0.20250804.0.dist-info}/METADATA +6 -6
- {osism-0.20250701.0.dist-info → osism-0.20250804.0.dist-info}/RECORD +29 -27
- {osism-0.20250701.0.dist-info → osism-0.20250804.0.dist-info}/entry_points.txt +15 -1
- osism-0.20250804.0.dist-info/pbr.json +1 -0
- osism-0.20250701.0.dist-info/pbr.json +0 -1
- {osism-0.20250701.0.dist-info → osism-0.20250804.0.dist-info}/WHEEL +0 -0
- {osism-0.20250701.0.dist-info → osism-0.20250804.0.dist-info}/licenses/AUTHORS +0 -0
- {osism-0.20250701.0.dist-info → osism-0.20250804.0.dist-info}/licenses/LICENSE +0 -0
- {osism-0.20250701.0.dist-info → osism-0.20250804.0.dist-info}/top_level.txt +0 -0
osism/tasks/__init__.py
CHANGED
@@ -1,12 +1,10 @@
|
|
1
1
|
# SPDX-License-Identifier: Apache-2.0
|
2
2
|
|
3
|
-
import logging
|
4
3
|
import os
|
5
4
|
import re
|
6
5
|
import subprocess
|
7
6
|
|
8
7
|
from loguru import logger
|
9
|
-
from pottery import Redlock
|
10
8
|
|
11
9
|
from osism import utils
|
12
10
|
|
@@ -77,10 +75,8 @@ def run_ansible_in_environment(
|
|
77
75
|
|
78
76
|
# NOTE: Consider arguments in the future
|
79
77
|
if locking:
|
80
|
-
|
81
|
-
lock = Redlock(
|
78
|
+
lock = utils.create_redlock(
|
82
79
|
key=f"lock-ansible-{environment}-{role}",
|
83
|
-
masters={utils.redis},
|
84
80
|
auto_release_time=auto_release_time,
|
85
81
|
)
|
86
82
|
|
@@ -195,9 +191,8 @@ def run_command(
|
|
195
191
|
command_env.update(env)
|
196
192
|
|
197
193
|
if locking:
|
198
|
-
lock =
|
194
|
+
lock = utils.create_redlock(
|
199
195
|
key=f"lock-{command}",
|
200
|
-
masters={utils.redis},
|
201
196
|
auto_release_time=auto_release_time,
|
202
197
|
)
|
203
198
|
|
@@ -243,6 +238,49 @@ def handle_task(t, wait=True, format="log", timeout=3600):
|
|
243
238
|
f"osism wait --output --live --delay 2 {t.task_id}"
|
244
239
|
)
|
245
240
|
return 1
|
241
|
+
except KeyboardInterrupt:
|
242
|
+
logger.info(f"\nTask {t.task_id} interrupted by user (CTRL+C)")
|
243
|
+
|
244
|
+
# Prompt user for task revocation in interactive mode using prompt-toolkit
|
245
|
+
try:
|
246
|
+
from prompt_toolkit import prompt
|
247
|
+
|
248
|
+
# Use prompt-toolkit for better UX with yes/no options and default
|
249
|
+
response = (
|
250
|
+
prompt(
|
251
|
+
"Do you want to revoke the running task? [y/N]: ", default="n"
|
252
|
+
)
|
253
|
+
.strip()
|
254
|
+
.lower()
|
255
|
+
)
|
256
|
+
|
257
|
+
if response in ["y", "yes"]:
|
258
|
+
logger.info(f"Revoking task {t.task_id}...")
|
259
|
+
if utils.revoke_task(t.task_id):
|
260
|
+
logger.info(f"Task {t.task_id} has been revoked")
|
261
|
+
else:
|
262
|
+
logger.error(f"Failed to revoke task {t.task_id}")
|
263
|
+
else:
|
264
|
+
logger.info(f"Task {t.task_id} continues running in background")
|
265
|
+
logger.info(
|
266
|
+
"Use this command to continue waiting for this task: "
|
267
|
+
f"osism wait --output --live --delay 2 {t.task_id}"
|
268
|
+
)
|
269
|
+
except KeyboardInterrupt:
|
270
|
+
# Handle second CTRL+C during prompt
|
271
|
+
logger.info(f"\nTask {t.task_id} continues running in background")
|
272
|
+
logger.info(
|
273
|
+
"Use this command to continue waiting for this task: "
|
274
|
+
f"osism wait --output --live --delay 2 {t.task_id}"
|
275
|
+
)
|
276
|
+
except EOFError:
|
277
|
+
# Handle EOF (e.g., when input is not available)
|
278
|
+
logger.info(f"Task {t.task_id} continues running in background")
|
279
|
+
logger.info(
|
280
|
+
"Use this command to continue waiting for this task: "
|
281
|
+
f"osism wait --output --live --delay 2 {t.task_id}"
|
282
|
+
)
|
283
|
+
return 1
|
246
284
|
|
247
285
|
else:
|
248
286
|
if format == "log":
|
osism/tasks/ansible.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# SPDX-License-Identifier: Apache-2.0
|
2
2
|
|
3
3
|
from celery import Celery
|
4
|
-
from pottery import Redlock
|
5
4
|
|
6
5
|
from osism import settings, utils
|
7
6
|
from osism.tasks import Config, run_ansible_in_environment
|
@@ -12,9 +11,8 @@ app.config_from_object(Config)
|
|
12
11
|
|
13
12
|
@app.on_after_configure.connect
|
14
13
|
def setup_periodic_tasks(sender, **kwargs):
|
15
|
-
lock =
|
14
|
+
lock = utils.create_redlock(
|
16
15
|
key="lock_osism_tasks_ansible_setup_periodic_tasks",
|
17
|
-
masters={utils.redis},
|
18
16
|
)
|
19
17
|
if settings.GATHER_FACTS_SCHEDULE > 0 and lock.acquire(timeout=10):
|
20
18
|
sender.add_periodic_task(
|
@@ -44,8 +44,8 @@ def sync_netbox(self, force_update=False):
|
|
44
44
|
|
45
45
|
|
46
46
|
@app.task(bind=True, name="osism.tasks.conductor.sync_ironic")
|
47
|
-
def sync_ironic(self, force_update=False):
|
48
|
-
_sync_ironic(self.request.id, get_ironic_parameters, force_update)
|
47
|
+
def sync_ironic(self, node_name=None, force_update=False):
|
48
|
+
_sync_ironic(self.request.id, get_ironic_parameters, node_name, force_update)
|
49
49
|
|
50
50
|
|
51
51
|
@app.task(bind=True, name="osism.tasks.conductor.sync_sonic")
|
osism/tasks/conductor/config.py
CHANGED
@@ -30,7 +30,7 @@ def get_configuration():
|
|
30
30
|
"image_source"
|
31
31
|
]
|
32
32
|
if not validators.uuid(image_source) and not validators.url(
|
33
|
-
image_source
|
33
|
+
image_source, simple_host=True
|
34
34
|
):
|
35
35
|
result = openstack.image_get(image_source)
|
36
36
|
if result:
|
@@ -46,7 +46,7 @@ def get_configuration():
|
|
46
46
|
"deploy_kernel"
|
47
47
|
]
|
48
48
|
if not validators.uuid(deploy_kernel) and not validators.url(
|
49
|
-
deploy_kernel
|
49
|
+
deploy_kernel, simple_host=True
|
50
50
|
):
|
51
51
|
result = openstack.image_get(deploy_kernel)
|
52
52
|
if result:
|
@@ -63,7 +63,7 @@ def get_configuration():
|
|
63
63
|
"deploy_ramdisk"
|
64
64
|
]
|
65
65
|
if not validators.uuid(deploy_ramdisk) and not validators.url(
|
66
|
-
deploy_ramdisk
|
66
|
+
deploy_ramdisk, simple_host=True
|
67
67
|
):
|
68
68
|
result = openstack.image_get(deploy_ramdisk)
|
69
69
|
if result:
|
osism/tasks/conductor/ironic.py
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
import json
|
4
4
|
|
5
5
|
import jinja2
|
6
|
-
from pottery import Redlock
|
7
6
|
|
8
7
|
from osism import utils as osism_utils
|
9
8
|
from osism.tasks import netbox, openstack
|
@@ -128,19 +127,41 @@ def _prepare_node_attributes(device, get_ironic_parameters):
|
|
128
127
|
return node_attributes
|
129
128
|
|
130
129
|
|
131
|
-
def sync_ironic(request_id, get_ironic_parameters, force_update=False):
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
130
|
+
def sync_ironic(request_id, get_ironic_parameters, node_name=None, force_update=False):
|
131
|
+
if node_name:
|
132
|
+
osism_utils.push_task_output(
|
133
|
+
request_id,
|
134
|
+
f"Starting NetBox device synchronisation with ironic for node {node_name}\n",
|
135
|
+
)
|
136
|
+
else:
|
137
|
+
osism_utils.push_task_output(
|
138
|
+
request_id,
|
139
|
+
"Starting NetBox device synchronisation with ironic\n",
|
140
|
+
)
|
136
141
|
devices = set()
|
137
142
|
nb_device_query_list = get_nb_device_query_list_ironic()
|
138
143
|
for nb_device_query in nb_device_query_list:
|
139
144
|
devices |= set(netbox.get_devices(**nb_device_query))
|
140
145
|
|
146
|
+
# Filter devices by node_name if specified
|
147
|
+
if node_name:
|
148
|
+
devices = {dev for dev in devices if dev.name == node_name}
|
149
|
+
if not devices:
|
150
|
+
osism_utils.push_task_output(
|
151
|
+
request_id,
|
152
|
+
f"Node {node_name} not found in NetBox\n",
|
153
|
+
)
|
154
|
+
osism_utils.finish_task_output(request_id, rc=1)
|
155
|
+
return
|
156
|
+
|
141
157
|
# NOTE: Find nodes in Ironic which are no longer present in NetBox and remove them
|
142
158
|
device_names = {dev.name for dev in devices}
|
143
159
|
nodes = openstack.baremetal_node_list()
|
160
|
+
|
161
|
+
# Filter nodes by node_name if specified
|
162
|
+
if node_name:
|
163
|
+
nodes = [node for node in nodes if node["Name"] == node_name]
|
164
|
+
|
144
165
|
for node in nodes:
|
145
166
|
osism_utils.push_task_output(
|
146
167
|
request_id, f"Looking for {node['Name']} in NetBox\n"
|
@@ -180,9 +201,8 @@ def sync_ironic(request_id, get_ironic_parameters, force_update=False):
|
|
180
201
|
if interface.enabled and not interface.mgmt_only and interface.mac_address
|
181
202
|
]
|
182
203
|
|
183
|
-
lock =
|
204
|
+
lock = osism_utils.create_redlock(
|
184
205
|
key=f"lock_osism_tasks_conductor_sync_ironic-{device.name}",
|
185
|
-
masters={osism_utils.redis},
|
186
206
|
auto_release_time=600,
|
187
207
|
)
|
188
208
|
if lock.acquire(timeout=120):
|
@@ -201,6 +201,9 @@ def generate_sonic_config(device, hwsku, device_as_mapping=None):
|
|
201
201
|
# Add NTP server configuration (device-specific)
|
202
202
|
_add_ntp_configuration(config, device)
|
203
203
|
|
204
|
+
# Add DNS server configuration (device-specific)
|
205
|
+
_add_dns_configuration(config, device)
|
206
|
+
|
204
207
|
# Add VLAN configuration
|
205
208
|
_add_vlan_configuration(config, vlan_info, netbox_interfaces, device)
|
206
209
|
|
@@ -694,18 +697,20 @@ def _determine_peer_type(local_device, connected_device, device_as_mapping=None)
|
|
694
697
|
return "external" # Default to external on error
|
695
698
|
|
696
699
|
|
697
|
-
def
|
698
|
-
"""Get
|
700
|
+
def _get_metalbox_ip_for_device(device):
|
701
|
+
"""Get Metalbox IP for a SONiC device based on OOB connection.
|
699
702
|
|
700
703
|
Returns the IP address of the metalbox device interface that is connected to the
|
701
704
|
OOB switch. If VLANs are used, returns the IP of the VLAN interface where the
|
702
705
|
SONiC switch management interface (eth0) has access.
|
703
706
|
|
707
|
+
This IP is used for both NTP and DNS services.
|
708
|
+
|
704
709
|
Args:
|
705
710
|
device: SONiC device object
|
706
711
|
|
707
712
|
Returns:
|
708
|
-
str: IP address of the
|
713
|
+
str: IP address of the Metalbox or None if not found
|
709
714
|
"""
|
710
715
|
try:
|
711
716
|
# Get the OOB IP configuration for this SONiC device
|
@@ -726,7 +731,7 @@ def _get_ntp_server_for_device(device):
|
|
726
731
|
metalbox_devices = utils.nb.dcim.devices.filter(role="metalbox")
|
727
732
|
|
728
733
|
for metalbox in metalbox_devices:
|
729
|
-
logger.debug(f"Checking metalbox device {metalbox.name} for
|
734
|
+
logger.debug(f"Checking metalbox device {metalbox.name} for services")
|
730
735
|
|
731
736
|
# Get all interfaces on this metalbox
|
732
737
|
interfaces = utils.nb.dcim.interfaces.filter(device_id=metalbox.id)
|
@@ -765,7 +770,7 @@ def _get_ntp_server_for_device(device):
|
|
765
770
|
else "interface"
|
766
771
|
)
|
767
772
|
logger.info(
|
768
|
-
f"Found
|
773
|
+
f"Found Metalbox {ip_only} on {metalbox.name} "
|
769
774
|
f"{interface_type} {interface.name} for SONiC device {device.name}"
|
770
775
|
)
|
771
776
|
return ip_only
|
@@ -773,11 +778,11 @@ def _get_ntp_server_for_device(device):
|
|
773
778
|
# Skip non-IPv4 addresses
|
774
779
|
continue
|
775
780
|
|
776
|
-
logger.warning(f"No suitable
|
781
|
+
logger.warning(f"No suitable Metalbox found for SONiC device {device.name}")
|
777
782
|
return None
|
778
783
|
|
779
784
|
except Exception as e:
|
780
|
-
logger.warning(f"Could not determine
|
785
|
+
logger.warning(f"Could not determine Metalbox IP for device {device.name}: {e}")
|
781
786
|
return None
|
782
787
|
|
783
788
|
|
@@ -846,19 +851,17 @@ def _add_ntp_configuration(config, device):
|
|
846
851
|
metalbox device interface connected to the OOB switch.
|
847
852
|
"""
|
848
853
|
try:
|
849
|
-
# Get the
|
850
|
-
|
854
|
+
# Get the Metalbox IP for this device
|
855
|
+
metalbox_ip = _get_metalbox_ip_for_device(device)
|
851
856
|
|
852
|
-
if
|
857
|
+
if metalbox_ip:
|
853
858
|
# Add single NTP server configuration
|
854
|
-
config["NTP_SERVER"][
|
859
|
+
config["NTP_SERVER"][metalbox_ip] = {
|
855
860
|
"maxpoll": "10",
|
856
861
|
"minpoll": "6",
|
857
862
|
"prefer": "false",
|
858
863
|
}
|
859
|
-
logger.info(
|
860
|
-
f"Added NTP server {ntp_server_ip} to SONiC device {device.name}"
|
861
|
-
)
|
864
|
+
logger.info(f"Added NTP server {metalbox_ip} to SONiC device {device.name}")
|
862
865
|
else:
|
863
866
|
logger.warning(f"No NTP server found for SONiC device {device.name}")
|
864
867
|
|
@@ -873,6 +876,27 @@ def clear_ntp_cache():
|
|
873
876
|
logger.debug("Cleared NTP servers cache")
|
874
877
|
|
875
878
|
|
879
|
+
def _add_dns_configuration(config, device):
|
880
|
+
"""Add DNS_NAMESERVER configuration to device config.
|
881
|
+
|
882
|
+
Each SONiC switch gets exactly one DNS server - the IP address of the
|
883
|
+
metalbox device interface connected to the OOB switch.
|
884
|
+
"""
|
885
|
+
try:
|
886
|
+
# Get the Metalbox IP for this device
|
887
|
+
metalbox_ip = _get_metalbox_ip_for_device(device)
|
888
|
+
|
889
|
+
if metalbox_ip:
|
890
|
+
# Add single DNS server configuration
|
891
|
+
config["DNS_NAMESERVER"][metalbox_ip] = {}
|
892
|
+
logger.info(f"Added DNS server {metalbox_ip} to SONiC device {device.name}")
|
893
|
+
else:
|
894
|
+
logger.warning(f"No DNS server found for SONiC device {device.name}")
|
895
|
+
|
896
|
+
except Exception as e:
|
897
|
+
logger.warning(f"Could not add DNS configuration to device {device.name}: {e}")
|
898
|
+
|
899
|
+
|
876
900
|
def clear_all_caches():
|
877
901
|
"""Clear all caches in config_generator module."""
|
878
902
|
clear_ntp_cache()
|
@@ -419,6 +419,16 @@ def _extract_port_number_from_alias(alias):
|
|
419
419
|
if not alias:
|
420
420
|
return None
|
421
421
|
|
422
|
+
# Try to extract number from Eth54(Port54) format first
|
423
|
+
paren_match = re.search(r"Eth(\d+)\(Port(\d+)\)", alias)
|
424
|
+
if paren_match:
|
425
|
+
port_number = int(paren_match.group(1))
|
426
|
+
logger.debug(
|
427
|
+
f"Extracted port number {port_number} from Eth(Port) alias '{alias}'"
|
428
|
+
)
|
429
|
+
return port_number
|
430
|
+
|
431
|
+
# Fallback to number at end of alias
|
422
432
|
match = re.search(r"(\d+)$", alias)
|
423
433
|
if match:
|
424
434
|
port_number = int(match.group(1))
|
osism/tasks/conductor/utils.py
CHANGED
@@ -75,8 +75,15 @@ def get_vault():
|
|
75
75
|
)
|
76
76
|
]
|
77
77
|
)
|
78
|
-
except
|
79
|
-
|
78
|
+
except ValueError as exc:
|
79
|
+
# Handle specific vault password configuration errors
|
80
|
+
logger.error(f"Vault password configuration error: {exc}")
|
81
|
+
logger.error("Please check your vault password setup in Redis")
|
82
|
+
vault = VaultLib()
|
83
|
+
except Exception as exc:
|
84
|
+
# Handle other errors (file access, decryption, etc.)
|
85
|
+
logger.error(f"Unable to get vault secret: {exc}")
|
86
|
+
logger.error("Dropping encrypted entries")
|
80
87
|
vault = VaultLib()
|
81
88
|
return vault
|
82
89
|
|
osism/tasks/netbox.py
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
from celery import Celery
|
4
4
|
from loguru import logger
|
5
|
-
from pottery import Redlock
|
6
5
|
|
7
6
|
from osism import settings, utils
|
8
7
|
from osism.tasks import Config, run_command
|
@@ -30,9 +29,8 @@ def run(self, action, arguments):
|
|
30
29
|
def set_maintenance(self, device_name, state=True):
|
31
30
|
"""Set the maintenance state for a device in the NetBox."""
|
32
31
|
|
33
|
-
lock =
|
32
|
+
lock = utils.create_redlock(
|
34
33
|
key=f"lock_osism_tasks_netbox_set_maintenance_{device_name}",
|
35
|
-
masters={utils.redis},
|
36
34
|
auto_release_time=60,
|
37
35
|
)
|
38
36
|
if lock.acquire(timeout=20):
|
@@ -59,9 +57,8 @@ def set_maintenance(self, device_name, state=True):
|
|
59
57
|
def set_provision_state(self, device_name, state):
|
60
58
|
"""Set the provision state for a device in the NetBox."""
|
61
59
|
|
62
|
-
lock =
|
60
|
+
lock = utils.create_redlock(
|
63
61
|
key=f"lock_osism_tasks_netbox_set_provision_state_{device_name}",
|
64
|
-
masters={utils.redis},
|
65
62
|
auto_release_time=60,
|
66
63
|
)
|
67
64
|
if lock.acquire(timeout=20):
|
@@ -89,9 +86,8 @@ def set_provision_state(self, device_name, state):
|
|
89
86
|
def set_power_state(self, device_name, state):
|
90
87
|
"""Set the provision state for a device in the NetBox."""
|
91
88
|
|
92
|
-
lock =
|
89
|
+
lock = utils.create_redlock(
|
93
90
|
key=f"lock_osism_tasks_netbox_set_provision_state_{device_name}",
|
94
|
-
masters={utils.redis},
|
95
91
|
auto_release_time=60,
|
96
92
|
)
|
97
93
|
if lock.acquire(timeout=20):
|
osism/tasks/reconciler.py
CHANGED
@@ -6,7 +6,6 @@ import subprocess
|
|
6
6
|
|
7
7
|
from celery import Celery
|
8
8
|
from loguru import logger
|
9
|
-
from pottery import Redlock
|
10
9
|
|
11
10
|
from osism import settings, utils
|
12
11
|
from osism.tasks import Config
|
@@ -17,9 +16,8 @@ app.config_from_object(Config)
|
|
17
16
|
|
18
17
|
@app.on_after_configure.connect
|
19
18
|
def setup_periodic_tasks(sender, **kwargs):
|
20
|
-
lock =
|
19
|
+
lock = utils.create_redlock(
|
21
20
|
key="lock_osism_tasks_reconciler_setup_periodic_tasks",
|
22
|
-
masters={utils.redis},
|
23
21
|
)
|
24
22
|
if settings.INVENTORY_RECONCILER_SCHEDULE > 0 and lock.acquire(timeout=10):
|
25
23
|
sender.add_periodic_task(
|
@@ -29,9 +27,8 @@ def setup_periodic_tasks(sender, **kwargs):
|
|
29
27
|
|
30
28
|
@app.task(bind=True, name="osism.tasks.reconciler.run")
|
31
29
|
def run(self, publish=True, flush_cache=False):
|
32
|
-
lock =
|
30
|
+
lock = utils.create_redlock(
|
33
31
|
key="lock_osism_tasks_reconciler_run",
|
34
|
-
masters={utils.redis},
|
35
32
|
auto_release_time=60,
|
36
33
|
)
|
37
34
|
|
@@ -64,9 +61,8 @@ def run(self, publish=True, flush_cache=False):
|
|
64
61
|
|
65
62
|
@app.task(bind=True, name="osism.tasks.reconciler.run_on_change")
|
66
63
|
def run_on_change(self):
|
67
|
-
lock =
|
64
|
+
lock = utils.create_redlock(
|
68
65
|
key="lock_osism_tasks_reconciler_run_on_change",
|
69
|
-
masters={utils.redis},
|
70
66
|
auto_release_time=60,
|
71
67
|
)
|
72
68
|
|
osism/utils/__init__.py
CHANGED
@@ -2,11 +2,13 @@
|
|
2
2
|
|
3
3
|
import time
|
4
4
|
import os
|
5
|
+
from contextlib import redirect_stdout, redirect_stderr
|
5
6
|
from cryptography.fernet import Fernet
|
6
7
|
import keystoneauth1
|
7
8
|
from loguru import logger
|
8
9
|
import openstack
|
9
10
|
import pynetbox
|
11
|
+
from pottery import Redlock
|
10
12
|
from redis import Redis
|
11
13
|
import urllib3
|
12
14
|
import yaml
|
@@ -107,8 +109,18 @@ def get_ansible_vault_password():
|
|
107
109
|
f = Fernet(key)
|
108
110
|
|
109
111
|
encrypted_ansible_vault_password = redis.get("ansible_vault_password")
|
112
|
+
if encrypted_ansible_vault_password is None:
|
113
|
+
raise ValueError("Ansible vault password is not set in Redis")
|
114
|
+
|
110
115
|
ansible_vault_password = f.decrypt(encrypted_ansible_vault_password)
|
111
|
-
|
116
|
+
password = ansible_vault_password.decode("utf-8")
|
117
|
+
|
118
|
+
if not password or password.strip() == "":
|
119
|
+
raise ValueError(
|
120
|
+
"Ansible vault password is empty or contains only whitespace"
|
121
|
+
)
|
122
|
+
|
123
|
+
return password
|
112
124
|
except Exception as exc:
|
113
125
|
logger.error("Unable to get ansible vault password")
|
114
126
|
raise exc
|
@@ -181,3 +193,52 @@ def finish_task_output(task_id, rc=None):
|
|
181
193
|
if rc:
|
182
194
|
redis.xadd(task_id, {"type": "rc", "content": rc})
|
183
195
|
redis.xadd(task_id, {"type": "action", "content": "quit"})
|
196
|
+
|
197
|
+
|
198
|
+
def revoke_task(task_id):
|
199
|
+
"""
|
200
|
+
Revoke a running Celery task.
|
201
|
+
|
202
|
+
Args:
|
203
|
+
task_id (str): The ID of the task to revoke
|
204
|
+
|
205
|
+
Returns:
|
206
|
+
bool: True if revocation was successful, False otherwise
|
207
|
+
"""
|
208
|
+
try:
|
209
|
+
from celery import Celery
|
210
|
+
from osism.tasks import Config
|
211
|
+
|
212
|
+
app = Celery("task")
|
213
|
+
app.config_from_object(Config)
|
214
|
+
app.control.revoke(task_id, terminate=True)
|
215
|
+
return True
|
216
|
+
except Exception as e:
|
217
|
+
logger.error(f"Failed to revoke task {task_id}: {e}")
|
218
|
+
return False
|
219
|
+
|
220
|
+
|
221
|
+
def create_redlock(key, auto_release_time=3600):
|
222
|
+
"""
|
223
|
+
Create a Redlock instance with output suppression during initialization.
|
224
|
+
|
225
|
+
Args:
|
226
|
+
key (str): The lock key
|
227
|
+
auto_release_time (int): Auto release time in seconds (default: 3600)
|
228
|
+
|
229
|
+
Returns:
|
230
|
+
Redlock: The configured Redlock instance
|
231
|
+
"""
|
232
|
+
import logging
|
233
|
+
|
234
|
+
# Permanently suppress pottery logger output
|
235
|
+
pottery_logger = logging.getLogger("pottery")
|
236
|
+
pottery_logger.setLevel(logging.CRITICAL)
|
237
|
+
|
238
|
+
with open(os.devnull, "w") as devnull:
|
239
|
+
with redirect_stdout(devnull), redirect_stderr(devnull):
|
240
|
+
return Redlock(
|
241
|
+
key=key,
|
242
|
+
masters={redis},
|
243
|
+
auto_release_time=auto_release_time,
|
244
|
+
)
|