osism 0.20250505.0__py3-none-any.whl → 0.20250514.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 +1 -1
- osism/commands/apply.py +1 -1
- osism/commands/configuration.py +4 -2
- osism/commands/manage.py +1 -1
- osism/commands/netbox.py +72 -23
- osism/commands/reconciler.py +4 -2
- osism/commands/validate.py +3 -3
- osism/settings.py +3 -3
- osism/tasks/conductor.py +136 -97
- osism/tasks/reconciler.py +0 -30
- osism/utils/__init__.py +17 -0
- {osism-0.20250505.0.dist-info → osism-0.20250514.0.dist-info}/METADATA +7 -8
- {osism-0.20250505.0.dist-info → osism-0.20250514.0.dist-info}/RECORD +19 -19
- {osism-0.20250505.0.dist-info → osism-0.20250514.0.dist-info}/WHEEL +1 -1
- {osism-0.20250505.0.dist-info → osism-0.20250514.0.dist-info}/entry_points.txt +2 -3
- osism-0.20250514.0.dist-info/licenses/AUTHORS +1 -0
- osism-0.20250514.0.dist-info/pbr.json +1 -0
- osism-0.20250505.0.dist-info/licenses/AUTHORS +0 -1
- osism-0.20250505.0.dist-info/pbr.json +0 -1
- {osism-0.20250505.0.dist-info → osism-0.20250514.0.dist-info}/licenses/LICENSE +0 -0
- {osism-0.20250505.0.dist-info → osism-0.20250514.0.dist-info}/top_level.txt +0 -0
osism/api.py
CHANGED
@@ -41,7 +41,7 @@ class WebhookNetboxData(BaseModel):
|
|
41
41
|
class LogConfig(BaseModel):
|
42
42
|
"""Logging configuration to be set for the server"""
|
43
43
|
|
44
|
-
LOGGER_NAME: str = "
|
44
|
+
LOGGER_NAME: str = "osism"
|
45
45
|
LOG_FORMAT: str = "%(levelprefix)s | %(asctime)s | %(message)s"
|
46
46
|
LOG_LEVEL: str = "DEBUG"
|
47
47
|
|
osism/commands/apply.py
CHANGED
@@ -116,7 +116,7 @@ class Run(Command):
|
|
116
116
|
if format == "log":
|
117
117
|
for c in t.children:
|
118
118
|
logger.info(
|
119
|
-
f"Task {c.task_id} is running in background. No more output. Check ARA for logs."
|
119
|
+
f"Task {c.task_id} (loadbalancer) is running in background. No more output. Check ARA for logs."
|
120
120
|
)
|
121
121
|
|
122
122
|
# As explained above, it is neceesary to wait for all tasks.
|
osism/commands/configuration.py
CHANGED
@@ -23,9 +23,11 @@ class Sync(Command):
|
|
23
23
|
"manager", "configuration", arguments, auto_release_time=60
|
24
24
|
)
|
25
25
|
|
26
|
-
logger.info(f"Task {t.task_id} was prepared for execution.")
|
27
26
|
logger.info(
|
28
|
-
f"
|
27
|
+
f"Task {t.task_id} (sync configuration) was prepared for execution."
|
28
|
+
)
|
29
|
+
logger.info(
|
30
|
+
f"It takes a moment until task {t.task_id} (sync configuration) has been started and output is visible here."
|
29
31
|
)
|
30
32
|
|
31
33
|
rc = handle_task(t, True, format, 60)
|
osism/commands/manage.py
CHANGED
@@ -12,7 +12,7 @@ import requests
|
|
12
12
|
from osism.data import TEMPLATE_IMAGE_CLUSTERAPI, TEMPLATE_IMAGE_OCTAVIA
|
13
13
|
from osism.tasks import openstack, handle_task
|
14
14
|
|
15
|
-
SUPPORTED_CLUSTERAPI_K8S_IMAGES = ["1.
|
15
|
+
SUPPORTED_CLUSTERAPI_K8S_IMAGES = ["1.31", "1.32", "1.33"]
|
16
16
|
|
17
17
|
|
18
18
|
class ImageClusterapi(Command):
|
osism/commands/netbox.py
CHANGED
@@ -1,9 +1,13 @@
|
|
1
1
|
# SPDX-License-Identifier: Apache-2.0
|
2
2
|
|
3
|
+
import os
|
4
|
+
import subprocess
|
5
|
+
|
3
6
|
from cliff.command import Command
|
4
7
|
from loguru import logger
|
8
|
+
import yaml
|
5
9
|
|
6
|
-
from osism.tasks import conductor, netbox,
|
10
|
+
from osism.tasks import conductor, netbox, handle_task
|
7
11
|
|
8
12
|
|
9
13
|
class Ironic(Command):
|
@@ -28,26 +32,9 @@ class Ironic(Command):
|
|
28
32
|
force_update=parsed_args.force_update
|
29
33
|
)
|
30
34
|
if wait:
|
31
|
-
logger.info(
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
class Sync(Command):
|
36
|
-
def get_parser(self, prog_name):
|
37
|
-
parser = super(Sync, self).get_parser(prog_name)
|
38
|
-
parser.add_argument(
|
39
|
-
"--no-wait",
|
40
|
-
help="Do not wait until the sync has been completed",
|
41
|
-
action="store_true",
|
42
|
-
)
|
43
|
-
return parser
|
44
|
-
|
45
|
-
def take_action(self, parsed_args):
|
46
|
-
wait = not parsed_args.no_wait
|
47
|
-
|
48
|
-
task = reconciler.sync_inventory_with_netbox.delay()
|
49
|
-
if wait:
|
50
|
-
logger.info(f"Task {task.task_id} is running. Wait. No more output.")
|
35
|
+
logger.info(
|
36
|
+
f"Task {task.task_id} (sync ironic) is running. Wait. No more output."
|
37
|
+
)
|
51
38
|
task.wait(timeout=None, interval=0.5)
|
52
39
|
|
53
40
|
|
@@ -140,9 +127,9 @@ class Manage(Command):
|
|
140
127
|
return handle_task(task, wait, format="script", timeout=3600)
|
141
128
|
|
142
129
|
|
143
|
-
class
|
130
|
+
class Versions(Command):
|
144
131
|
def get_parser(self, prog_name):
|
145
|
-
parser = super(
|
132
|
+
parser = super(Versions, self).get_parser(prog_name)
|
146
133
|
return parser
|
147
134
|
|
148
135
|
def take_action(self, parsed_args):
|
@@ -150,3 +137,65 @@ class Ping(Command):
|
|
150
137
|
task.wait(timeout=None, interval=0.5)
|
151
138
|
result = task.get()
|
152
139
|
print(result)
|
140
|
+
|
141
|
+
|
142
|
+
class Console(Command):
|
143
|
+
def get_parser(self, prog_name):
|
144
|
+
parser = super(Console, self).get_parser(prog_name)
|
145
|
+
parser.add_argument(
|
146
|
+
"type",
|
147
|
+
nargs=1,
|
148
|
+
choices=["info", "search", "filter", "shell"],
|
149
|
+
help="Type of the console (default: %(default)s)",
|
150
|
+
)
|
151
|
+
parser.add_argument(
|
152
|
+
"arguments", nargs="*", type=str, default="", help="Additional arguments"
|
153
|
+
)
|
154
|
+
|
155
|
+
return parser
|
156
|
+
|
157
|
+
def take_action(self, parsed_args):
|
158
|
+
type_console = parsed_args.type[0]
|
159
|
+
arguments = " ".join(
|
160
|
+
[f"'{item}'" if " " in item else item for item in parsed_args.arguments]
|
161
|
+
)
|
162
|
+
|
163
|
+
home_dir = os.path.expanduser("~")
|
164
|
+
nbcli_dir = os.path.join(home_dir, ".nbcli")
|
165
|
+
if not os.path.exists(nbcli_dir):
|
166
|
+
os.mkdir(nbcli_dir)
|
167
|
+
|
168
|
+
nbcli_file = os.path.join(nbcli_dir, "user_config.yml")
|
169
|
+
if not os.path.exists(nbcli_file):
|
170
|
+
try:
|
171
|
+
with open("/run/secrets/NETBOX_TOKEN", "r") as fp:
|
172
|
+
token = fp.read().strip()
|
173
|
+
except FileNotFoundError:
|
174
|
+
token = None
|
175
|
+
|
176
|
+
url = os.environ.get("NETBOX_API", None)
|
177
|
+
|
178
|
+
if not token or not url:
|
179
|
+
logger.error("Netbox integration not configured.")
|
180
|
+
return
|
181
|
+
|
182
|
+
subprocess.call(
|
183
|
+
["/usr/local/bin/nbcli", "init"],
|
184
|
+
stdout=subprocess.DEVNULL,
|
185
|
+
stderr=subprocess.DEVNULL,
|
186
|
+
)
|
187
|
+
os.remove(nbcli_file)
|
188
|
+
|
189
|
+
nbcli_config = {
|
190
|
+
"pynetbox": {
|
191
|
+
"url": url,
|
192
|
+
"token": token,
|
193
|
+
},
|
194
|
+
"requests": {"verify": False},
|
195
|
+
"nbcli": {"filter_limit": 50},
|
196
|
+
"user": {},
|
197
|
+
}
|
198
|
+
with open(nbcli_file, "w") as fp:
|
199
|
+
yaml.dump(nbcli_config, fp, default_flow_style=False)
|
200
|
+
|
201
|
+
subprocess.call(f"/usr/local/bin/nbcli {type_console} {arguments}", shell=True)
|
osism/commands/reconciler.py
CHANGED
@@ -47,7 +47,7 @@ class Sync(Command):
|
|
47
47
|
t = reconciler.run.delay(publish=wait)
|
48
48
|
if wait:
|
49
49
|
logger.info(
|
50
|
-
f"Task {t.task_id} is running in background. Output coming soon."
|
50
|
+
f"Task {t.task_id} (sync inventory) is running in background. Output coming soon."
|
51
51
|
)
|
52
52
|
rc = 0
|
53
53
|
stoptime = time.time() + task_timeout
|
@@ -77,4 +77,6 @@ class Sync(Command):
|
|
77
77
|
redis.close()
|
78
78
|
return rc
|
79
79
|
else:
|
80
|
-
logger.info(
|
80
|
+
logger.info(
|
81
|
+
f"Task {t.task_id} (sync inventory) is running in background. No more output."
|
82
|
+
)
|
osism/commands/validate.py
CHANGED
@@ -52,7 +52,7 @@ class Run(Command):
|
|
52
52
|
)
|
53
53
|
return parser
|
54
54
|
|
55
|
-
def _handle_task(self, t, wait, format, timeout):
|
55
|
+
def _handle_task(self, t, wait, format, timeout, playbook):
|
56
56
|
rc = 0
|
57
57
|
if wait:
|
58
58
|
stoptime = time.time() + timeout
|
@@ -85,7 +85,7 @@ class Run(Command):
|
|
85
85
|
else:
|
86
86
|
if format == "log":
|
87
87
|
logger.info(
|
88
|
-
f"Task {t.task_id} is running in background. No more output. Check ARA for logs."
|
88
|
+
f"Task {t.task_id} (validate {playbook}) is running in background. No more output. Check ARA for logs."
|
89
89
|
)
|
90
90
|
elif format == "script":
|
91
91
|
print(f"{t.task_id}")
|
@@ -120,6 +120,6 @@ class Run(Command):
|
|
120
120
|
environment = VALIDATE_PLAYBOOKS[validator]["environment"]
|
121
121
|
t = ansible.run.delay(environment, playbook, arguments)
|
122
122
|
|
123
|
-
rc = self._handle_task(t, wait, format, timeout)
|
123
|
+
rc = self._handle_task(t, wait, format, timeout, playbook)
|
124
124
|
|
125
125
|
return rc
|
osism/settings.py
CHANGED
@@ -23,7 +23,7 @@ REDIS_PORT: int = int(os.getenv("REDIS_PORT", "6379"))
|
|
23
23
|
REDIS_DB: int = int(os.getenv("REDIS_DB", "0"))
|
24
24
|
|
25
25
|
|
26
|
-
NETBOX_URL = os.getenv("NETBOX_API")
|
26
|
+
NETBOX_URL = os.getenv("NETBOX_API", os.getenv("NETBOX_URL"))
|
27
27
|
NETBOX_TOKEN = os.getenv("NETBOX_TOKEN", read_secret("NETBOX_TOKEN"))
|
28
28
|
IGNORE_SSL_ERRORS = os.getenv("IGNORE_SSL_ERRORS", "True") == "True"
|
29
29
|
|
@@ -35,8 +35,8 @@ INVENTORY_RECONCILER_SCHEDULE = float(
|
|
35
35
|
|
36
36
|
OSISM_API_URL = os.getenv("OSISM_API_URL", None)
|
37
37
|
|
38
|
-
|
39
|
-
"
|
38
|
+
NETBOX_FILTER_LIST = os.getenv(
|
39
|
+
"NETBOX_FILTER_LIST",
|
40
40
|
"[{'state': 'active', 'tag': ['managed-by-ironic']}]",
|
41
41
|
)
|
42
42
|
|
osism/tasks/conductor.py
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# SPDX-License-Identifier: Apache-2.0
|
2
2
|
|
3
|
+
from ansible import constants as ansible_constants
|
4
|
+
from ansible.parsing.vault import VaultLib, VaultSecret
|
3
5
|
from celery import Celery
|
4
6
|
from celery.signals import worker_process_init
|
5
7
|
import copy
|
@@ -18,83 +20,9 @@ app.config_from_object(Config)
|
|
18
20
|
|
19
21
|
|
20
22
|
configuration = {}
|
21
|
-
nb_device_query_list = None
|
22
23
|
|
23
24
|
|
24
|
-
|
25
|
-
def celery_init_worker(**kwargs):
|
26
|
-
global configuration
|
27
|
-
|
28
|
-
with open("/etc/conductor.yml") as fp:
|
29
|
-
configuration = yaml.load(fp, Loader=yaml.SafeLoader)
|
30
|
-
|
31
|
-
if not configuration:
|
32
|
-
logger.warning(
|
33
|
-
"The conductor configuration is empty. That's probably wrong"
|
34
|
-
)
|
35
|
-
configuration = {}
|
36
|
-
return
|
37
|
-
|
38
|
-
# Resolve all IDs in the conductor.yml
|
39
|
-
if Config.enable_ironic.lower() in ["true", "yes"]:
|
40
|
-
if "ironic_parameters" not in configuration:
|
41
|
-
logger.error(
|
42
|
-
"ironic_parameters not found in the conductor configuration"
|
43
|
-
)
|
44
|
-
return
|
45
|
-
|
46
|
-
if "driver_info" in configuration["ironic_parameters"]:
|
47
|
-
if "deploy_kernel" in configuration["ironic_parameters"]["driver_info"]:
|
48
|
-
result = openstack.image_get(
|
49
|
-
configuration["ironic_parameters"]["driver_info"][
|
50
|
-
"deploy_kernel"
|
51
|
-
]
|
52
|
-
)
|
53
|
-
configuration["ironic_parameters"]["driver_info"][
|
54
|
-
"deploy_kernel"
|
55
|
-
] = result.id
|
56
|
-
|
57
|
-
if (
|
58
|
-
"deploy_ramdisk"
|
59
|
-
in configuration["ironic_parameters"]["driver_info"]
|
60
|
-
):
|
61
|
-
result = openstack.image_get(
|
62
|
-
configuration["ironic_parameters"]["driver_info"][
|
63
|
-
"deploy_ramdisk"
|
64
|
-
]
|
65
|
-
)
|
66
|
-
configuration["ironic_parameters"]["driver_info"][
|
67
|
-
"deploy_ramdisk"
|
68
|
-
] = result.id
|
69
|
-
|
70
|
-
if (
|
71
|
-
"cleaning_network"
|
72
|
-
in configuration["ironic_parameters"]["driver_info"]
|
73
|
-
):
|
74
|
-
result = openstack.network_get(
|
75
|
-
configuration["ironic_parameters"]["driver_info"][
|
76
|
-
"cleaning_network"
|
77
|
-
]
|
78
|
-
)
|
79
|
-
configuration["ironic_parameters"]["driver_info"][
|
80
|
-
"cleaning_network"
|
81
|
-
] = result.id
|
82
|
-
|
83
|
-
if (
|
84
|
-
"provisioning_network"
|
85
|
-
in configuration["ironic_parameters"]["driver_info"]
|
86
|
-
):
|
87
|
-
result = openstack.network_get(
|
88
|
-
configuration["ironic_parameters"]["driver_info"][
|
89
|
-
"provisioning_network"
|
90
|
-
]
|
91
|
-
)
|
92
|
-
configuration["ironic_parameters"]["driver_info"][
|
93
|
-
"provisioning_network"
|
94
|
-
] = result.id
|
95
|
-
|
96
|
-
global nb_device_query_list
|
97
|
-
|
25
|
+
def get_nb_device_query_list():
|
98
26
|
try:
|
99
27
|
supported_nb_device_filters = [
|
100
28
|
"site",
|
@@ -105,9 +33,7 @@ def celery_init_worker(**kwargs):
|
|
105
33
|
"tag",
|
106
34
|
"state",
|
107
35
|
]
|
108
|
-
nb_device_query_list = yaml.safe_load(
|
109
|
-
settings.OSISM_CONDUCTOR_NETBOX_FILTER_LIST
|
110
|
-
)
|
36
|
+
nb_device_query_list = yaml.safe_load(settings.NETBOX_FILTER_LIST)
|
111
37
|
if type(nb_device_query_list) is not list:
|
112
38
|
raise TypeError
|
113
39
|
for nb_device_query in nb_device_query_list:
|
@@ -129,13 +55,81 @@ def celery_init_worker(**kwargs):
|
|
129
55
|
raise ValueError(f"Invalid name {value_name} for {key}")
|
130
56
|
except (yaml.YAMLError, TypeError):
|
131
57
|
logger.error(
|
132
|
-
f"Setting
|
58
|
+
f"Setting NETBOX_FILTER_LIST needs to be an array of mappings containing supported netbox device filters: {supported_nb_device_filters}"
|
133
59
|
)
|
134
60
|
nb_device_query_list = []
|
135
61
|
except ValueError as exc:
|
136
|
-
logger.error(f"Unknown value in
|
62
|
+
logger.error(f"Unknown value in NETBOX_FILTER_LIST: {exc}")
|
137
63
|
nb_device_query_list = []
|
138
64
|
|
65
|
+
return nb_device_query_list
|
66
|
+
|
67
|
+
|
68
|
+
def get_configuration():
|
69
|
+
with open("/etc/conductor.yml") as fp:
|
70
|
+
configuration = yaml.load(fp, Loader=yaml.SafeLoader)
|
71
|
+
|
72
|
+
if not configuration:
|
73
|
+
logger.warning(
|
74
|
+
"The conductor configuration is empty. That's probably wrong"
|
75
|
+
)
|
76
|
+
return {}
|
77
|
+
|
78
|
+
if Config.enable_ironic.lower() not in ["true", "yes"]:
|
79
|
+
return configuration
|
80
|
+
|
81
|
+
if "ironic_parameters" not in configuration:
|
82
|
+
logger.error("ironic_parameters not found in the conductor configuration")
|
83
|
+
return configuration
|
84
|
+
|
85
|
+
if "driver_info" in configuration["ironic_parameters"]:
|
86
|
+
if "deploy_kernel" in configuration["ironic_parameters"]["driver_info"]:
|
87
|
+
result = openstack.image_get(
|
88
|
+
configuration["ironic_parameters"]["driver_info"]["deploy_kernel"]
|
89
|
+
)
|
90
|
+
configuration["ironic_parameters"]["driver_info"][
|
91
|
+
"deploy_kernel"
|
92
|
+
] = result.id
|
93
|
+
|
94
|
+
if "deploy_ramdisk" in configuration["ironic_parameters"]["driver_info"]:
|
95
|
+
result = openstack.image_get(
|
96
|
+
configuration["ironic_parameters"]["driver_info"]["deploy_ramdisk"]
|
97
|
+
)
|
98
|
+
configuration["ironic_parameters"]["driver_info"][
|
99
|
+
"deploy_ramdisk"
|
100
|
+
] = result.id
|
101
|
+
|
102
|
+
if "cleaning_network" in configuration["ironic_parameters"]["driver_info"]:
|
103
|
+
result = openstack.network_get(
|
104
|
+
configuration["ironic_parameters"]["driver_info"][
|
105
|
+
"cleaning_network"
|
106
|
+
]
|
107
|
+
)
|
108
|
+
configuration["ironic_parameters"]["driver_info"][
|
109
|
+
"cleaning_network"
|
110
|
+
] = result.id
|
111
|
+
|
112
|
+
if (
|
113
|
+
"provisioning_network"
|
114
|
+
in configuration["ironic_parameters"]["driver_info"]
|
115
|
+
):
|
116
|
+
result = openstack.network_get(
|
117
|
+
configuration["ironic_parameters"]["driver_info"][
|
118
|
+
"provisioning_network"
|
119
|
+
]
|
120
|
+
)
|
121
|
+
configuration["ironic_parameters"]["driver_info"][
|
122
|
+
"provisioning_network"
|
123
|
+
] = result.id
|
124
|
+
|
125
|
+
return configuration
|
126
|
+
|
127
|
+
|
128
|
+
@worker_process_init.connect
|
129
|
+
def celery_init_worker(**kwargs):
|
130
|
+
global configuration
|
131
|
+
configuration = get_configuration()
|
132
|
+
|
139
133
|
|
140
134
|
@app.on_after_configure.connect
|
141
135
|
def setup_periodic_tasks(sender, **kwargs):
|
@@ -168,6 +162,31 @@ def sync_netbox_with_ironic(self, force_update=False):
|
|
168
162
|
if not updates[key]:
|
169
163
|
updates.pop(key)
|
170
164
|
|
165
|
+
def deep_merge(a, b):
|
166
|
+
for key, value in b.items():
|
167
|
+
if value == "DELETE":
|
168
|
+
# NOTE: Use special string to remove keys
|
169
|
+
a.pop(key, None)
|
170
|
+
elif (
|
171
|
+
key not in a.keys()
|
172
|
+
or not isinstance(a[key], dict)
|
173
|
+
or not isinstance(value, dict)
|
174
|
+
):
|
175
|
+
a[key] = value
|
176
|
+
else:
|
177
|
+
deep_merge(a[key], value)
|
178
|
+
|
179
|
+
def deep_decrypt(a, vault):
|
180
|
+
for key, value in list(a.items()):
|
181
|
+
if not isinstance(value, dict):
|
182
|
+
if vault.is_encrypted(value):
|
183
|
+
try:
|
184
|
+
a[key] = vault.decrypt(value).decode()
|
185
|
+
except Exception:
|
186
|
+
a.pop(key, None)
|
187
|
+
else:
|
188
|
+
deep_decrypt(a[key], vault)
|
189
|
+
|
171
190
|
driver_params = {
|
172
191
|
"ipmi": {
|
173
192
|
"address": "ipmi_address",
|
@@ -181,6 +200,7 @@ def sync_netbox_with_ironic(self, force_update=False):
|
|
181
200
|
}
|
182
201
|
|
183
202
|
devices = set()
|
203
|
+
nb_device_query_list = get_nb_device_query_list()
|
184
204
|
for nb_device_query in nb_device_query_list:
|
185
205
|
devices |= set(netbox.get_devices(**nb_device_query))
|
186
206
|
|
@@ -211,20 +231,51 @@ def sync_netbox_with_ironic(self, force_update=False):
|
|
211
231
|
# NOTE: Find nodes in netbox which are not present in Ironic and add them
|
212
232
|
for device in devices:
|
213
233
|
logger.info(f"Looking for {device.name} in ironic")
|
234
|
+
logger.info(device)
|
214
235
|
|
215
236
|
node_interfaces = list(netbox.get_interfaces_by_device(device.name))
|
216
237
|
|
217
238
|
node_attributes = get_ironic_parameters()
|
239
|
+
if (
|
240
|
+
"ironic_parameters" in device.custom_fields
|
241
|
+
and device.custom_fields["ironic_parameters"]
|
242
|
+
):
|
243
|
+
# NOTE: Update node attributes with overrides from netbox device
|
244
|
+
deep_merge(node_attributes, device.custom_fields["ironic_parameters"])
|
245
|
+
# NOTE: Decrypt ansible vaulted secrets
|
246
|
+
try:
|
247
|
+
vault_secret = utils.get_ansible_vault_password()
|
248
|
+
vault = VaultLib(
|
249
|
+
[
|
250
|
+
(
|
251
|
+
ansible_constants.DEFAULT_VAULT_ID_MATCH,
|
252
|
+
VaultSecret(vault_secret.encode()),
|
253
|
+
)
|
254
|
+
]
|
255
|
+
)
|
256
|
+
except Exception:
|
257
|
+
logger.error("Unable to get vault secret. Dropping encrypted entries")
|
258
|
+
vault = VaultLib()
|
259
|
+
deep_decrypt(node_attributes, vault)
|
218
260
|
if (
|
219
261
|
"driver" in node_attributes
|
220
262
|
and node_attributes["driver"] in driver_params.keys()
|
221
263
|
):
|
222
264
|
if "driver_info" in node_attributes:
|
265
|
+
# NOTE: Pop all fields belonging to a different driver
|
266
|
+
unused_drivers = [
|
267
|
+
driver
|
268
|
+
for driver in driver_params.keys()
|
269
|
+
if driver != node_attributes["driver"]
|
270
|
+
]
|
271
|
+
for key in list(node_attributes["driver_info"].keys()):
|
272
|
+
for driver in unused_drivers:
|
273
|
+
if key.startswith(driver + "_"):
|
274
|
+
node_attributes["driver_info"].pop(key, None)
|
275
|
+
# NOTE: Render driver address field
|
223
276
|
address_key = driver_params[node_attributes["driver"]]["address"]
|
224
277
|
if address_key in node_attributes["driver_info"]:
|
225
|
-
if "
|
226
|
-
node_mgmt_address = device.custom_fields["oob_address"]
|
227
|
-
elif "address" in device.oob_ip:
|
278
|
+
if device.oob_ip and "address" in device.oob_ip:
|
228
279
|
node_mgmt_address = device.oob_ip["address"]
|
229
280
|
else:
|
230
281
|
node_mgmt_addresses = [
|
@@ -248,18 +299,6 @@ def sync_netbox_with_ironic(self, force_update=False):
|
|
248
299
|
)
|
249
300
|
)
|
250
301
|
)
|
251
|
-
else:
|
252
|
-
logger.error(f"Could not find out-of-band address for {device}")
|
253
|
-
node_attributes["driver_info"].pop(address_key, None)
|
254
|
-
if (
|
255
|
-
"port" in driver_params[node_attributes["driver"]]
|
256
|
-
and "oob_port" in device.custom_fields
|
257
|
-
and device.custom_fields["oob_port"]
|
258
|
-
):
|
259
|
-
port_key = driver_params[node_attributes["driver"]]["port"]
|
260
|
-
node_attributes["driver_info"].update(
|
261
|
-
{port_key: device.custom_fields["oob_port"]}
|
262
|
-
)
|
263
302
|
node_attributes.update({"resource_class": device.name})
|
264
303
|
ports_attributes = [
|
265
304
|
dict(address=interface.mac_address)
|
@@ -270,9 +309,9 @@ def sync_netbox_with_ironic(self, force_update=False):
|
|
270
309
|
lock = Redlock(
|
271
310
|
key=f"lock_osism_tasks_conductor_sync_netbox_with_ironic-{device.name}",
|
272
311
|
masters={utils.redis},
|
273
|
-
auto_release_time=
|
312
|
+
auto_release_time=600,
|
274
313
|
)
|
275
|
-
if lock.acquire(timeout=
|
314
|
+
if lock.acquire(timeout=120):
|
276
315
|
try:
|
277
316
|
logger.info(f"Processing device {device.name}")
|
278
317
|
node = openstack.baremetal_node_show(device.name, ignore_missing=True)
|
osism/tasks/reconciler.py
CHANGED
@@ -63,33 +63,3 @@ def run_on_change(self):
|
|
63
63
|
p.wait()
|
64
64
|
|
65
65
|
lock.release()
|
66
|
-
|
67
|
-
|
68
|
-
@app.task(bind=True, name="osism.tasks.reconciler.sync_inventory_with_netbox")
|
69
|
-
def sync_inventory_with_netbox(self):
|
70
|
-
lock = Redlock(
|
71
|
-
key="lock_osism_tasks_reconciler_sync_inventory_with_netbox",
|
72
|
-
masters={utils.redis},
|
73
|
-
auto_release_time=60,
|
74
|
-
)
|
75
|
-
|
76
|
-
if lock.acquire(timeout=20):
|
77
|
-
p = subprocess.Popen(
|
78
|
-
"/sync-inventory-with-netbox.sh",
|
79
|
-
shell=True,
|
80
|
-
stdout=subprocess.PIPE,
|
81
|
-
stderr=subprocess.STDOUT,
|
82
|
-
)
|
83
|
-
|
84
|
-
for line in io.TextIOWrapper(p.stdout, encoding="utf-8"):
|
85
|
-
# NOTE: use task_id or request_id in future
|
86
|
-
utils.redis.publish(
|
87
|
-
"netbox-sync-inventory-with-netbox", {"type": "stdout", "content": line}
|
88
|
-
)
|
89
|
-
|
90
|
-
lock.release()
|
91
|
-
|
92
|
-
# NOTE: use task_id or request_id in future
|
93
|
-
utils.redis.publish(
|
94
|
-
"netbox-sync-inventory-with-netbox", {"type": "action", "content": "quit"}
|
95
|
-
)
|
osism/utils/__init__.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# SPDX-License-Identifier: Apache-2.0
|
2
2
|
|
3
|
+
from cryptography.fernet import Fernet
|
3
4
|
import keystoneauth1
|
4
5
|
from loguru import logger
|
5
6
|
import openstack
|
@@ -95,6 +96,22 @@ def get_openstack_connection():
|
|
95
96
|
return conn
|
96
97
|
|
97
98
|
|
99
|
+
def get_ansible_vault_password():
|
100
|
+
keyfile = "/share/ansible_vault_password.key"
|
101
|
+
|
102
|
+
try:
|
103
|
+
with open(keyfile, "r") as fp:
|
104
|
+
key = fp.read()
|
105
|
+
f = Fernet(key)
|
106
|
+
|
107
|
+
encrypted_ansible_vault_password = redis.get("ansible_vault_password")
|
108
|
+
ansible_vault_password = f.decrypt(encrypted_ansible_vault_password)
|
109
|
+
return ansible_vault_password.decode("utf-8")
|
110
|
+
except Exception as exc:
|
111
|
+
logger.error("Unable to get ansible vault password")
|
112
|
+
raise exc
|
113
|
+
|
114
|
+
|
98
115
|
# https://stackoverflow.com/questions/2361426/get-the-first-item-from-an-iterable-that-matches-a-condition
|
99
116
|
def first(iterable, condition=lambda x: True):
|
100
117
|
"""
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: osism
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.20250514.0
|
4
4
|
Summary: OSISM manager interface
|
5
5
|
Home-page: https://github.com/osism/python-osism
|
6
6
|
Author: OSISM GmbH
|
@@ -29,20 +29,19 @@ Requires-Dist: PyYAML==6.0.2
|
|
29
29
|
Requires-Dist: ara==1.7.2
|
30
30
|
Requires-Dist: celery[redis]==5.5.2
|
31
31
|
Requires-Dist: cliff==4.9.1
|
32
|
-
Requires-Dist: deepdiff==8.
|
32
|
+
Requires-Dist: deepdiff==8.5.0
|
33
33
|
Requires-Dist: docker==7.1.0
|
34
34
|
Requires-Dist: dtrack-auditor==1.5.0
|
35
35
|
Requires-Dist: fastapi==0.115.12
|
36
36
|
Requires-Dist: flower==2.0.1
|
37
|
-
Requires-Dist: hiredis==3.1.
|
38
|
-
Requires-Dist: jc==1.25.
|
37
|
+
Requires-Dist: hiredis==3.1.1
|
38
|
+
Requires-Dist: jc==1.25.5
|
39
39
|
Requires-Dist: keystoneauth1==5.10.0
|
40
40
|
Requires-Dist: kombu==5.5.3
|
41
41
|
Requires-Dist: kubernetes==32.0.1
|
42
42
|
Requires-Dist: loguru==0.7.3
|
43
|
+
Requires-Dist: nbcli==0.10.0.dev2
|
43
44
|
Requires-Dist: netmiko==4.5.0
|
44
|
-
Requires-Dist: nornir-ansible==2023.12.28
|
45
|
-
Requires-Dist: nornir==3.5.0
|
46
45
|
Requires-Dist: openstacksdk==4.5.0
|
47
46
|
Requires-Dist: pottery==3.0.1
|
48
47
|
Requires-Dist: prompt-toolkit==3.0.51
|
@@ -50,7 +49,7 @@ Requires-Dist: pydantic==1.10.22
|
|
50
49
|
Requires-Dist: pynetbox==7.4.1
|
51
50
|
Requires-Dist: pytest-testinfra==10.2.2
|
52
51
|
Requires-Dist: python-dateutil==2.9.0.post0
|
53
|
-
Requires-Dist: setuptools==80.
|
52
|
+
Requires-Dist: setuptools==80.4.0
|
54
53
|
Requires-Dist: sqlmodel==0.0.24
|
55
54
|
Requires-Dist: sushy==5.5.0
|
56
55
|
Requires-Dist: tabulate==0.9.0
|
@@ -61,7 +60,7 @@ Provides-Extra: ansible
|
|
61
60
|
Requires-Dist: ansible-runner==2.4.1; extra == "ansible"
|
62
61
|
Requires-Dist: ansible-core==2.18.5; extra == "ansible"
|
63
62
|
Provides-Extra: openstack-image-manager
|
64
|
-
Requires-Dist: openstack-image-manager==0.
|
63
|
+
Requires-Dist: openstack-image-manager==0.20250508.0; extra == "openstack-image-manager"
|
65
64
|
Dynamic: author
|
66
65
|
Dynamic: author-email
|
67
66
|
Dynamic: classifier
|
@@ -1,29 +1,29 @@
|
|
1
1
|
osism/__init__.py,sha256=1UiNTBus0V0f2AbZQzAtVtu6zkfCCrw0OTq--NwFAqY,341
|
2
2
|
osism/__main__.py,sha256=ILe4gu61xEISiBsxanqTQIdSkV-YhpZXTRlguCYyssk,141
|
3
|
-
osism/api.py,sha256=
|
3
|
+
osism/api.py,sha256=_d7KvjvTK3PA-ZcAJKD7kXiSZrEQ8YV8NF7MYYriW3U,4327
|
4
4
|
osism/main.py,sha256=Dt2-9sLXcS-Ny4DAz7hrha-KRc7zd7BFUTRdfs_X8z4,893
|
5
|
-
osism/settings.py,sha256=
|
5
|
+
osism/settings.py,sha256=09nVwFNWrx2LY-kw8kDG_gMEG72o49BvU8eqoqgQvuc,1299
|
6
6
|
osism/actions/__init__.py,sha256=bG7Ffen4LvQtgnYPFEpFccsWs81t4zqqeqn9ZeirH6E,38
|
7
7
|
osism/commands/__init__.py,sha256=Ag4wX_DCgXRdoLn6t069jqb3DdRylsX2nyYkiyCx4uk,456
|
8
|
-
osism/commands/apply.py,sha256=
|
8
|
+
osism/commands/apply.py,sha256=mH3-NctgevVzP_1IW92FQeiYMCPB49K5hXbxmTY2vnA,16795
|
9
9
|
osism/commands/compose.py,sha256=iqzG7mS9E1VWaLNN6yQowjOqiHn3BMdj-yfXb3Dc4Ok,1200
|
10
10
|
osism/commands/compute.py,sha256=XqOdnTqQ5Yqz__4rXbr64ctfe9Qq342IVFLnKmvjVpI,17226
|
11
|
-
osism/commands/configuration.py,sha256=
|
11
|
+
osism/commands/configuration.py,sha256=sPe8b0dVKFRbr30xoeVdAnHbGwCwgUh0xa_Vzv5pSQQ,954
|
12
12
|
osism/commands/console.py,sha256=8BPz1hio5Wi6kONVAWFuSqkDRrMcLEYeFIY8dbtN6e4,3218
|
13
13
|
osism/commands/container.py,sha256=Fku2GaCM3Idq_FxExUtNqjrEM0XYjpVvXmueSVO8S_c,1601
|
14
14
|
osism/commands/get.py,sha256=ryytjtXWmlMV0NucP5tGkMZu0nIlC4xVtjRk4iMZ06c,8967
|
15
15
|
osism/commands/log.py,sha256=2IpYuosC7FZwwLvM8HmKSU1NRNIelVVYzqjjVMCrOJk,4072
|
16
|
-
osism/commands/manage.py,sha256=
|
17
|
-
osism/commands/netbox.py,sha256=
|
16
|
+
osism/commands/manage.py,sha256=WxUZEhylZj2IhydAe3BAr3S5ED6opG243skfSq5q41s,11971
|
17
|
+
osism/commands/netbox.py,sha256=XDJVuoh2pk7XKGAElnxIkJ_v8bZ2glk0FYynVzNnSPE,6059
|
18
18
|
osism/commands/noset.py,sha256=7zDFuFMyNpo7DUOKcNiYV8nodtdMOYFp5LDPcuJhlZ8,1481
|
19
|
-
osism/commands/reconciler.py,sha256=
|
19
|
+
osism/commands/reconciler.py,sha256=xOyPzQj66xwjdQd2ysCTHX2yBvmMVMppUDZTas6voXc,2882
|
20
20
|
osism/commands/server.py,sha256=avmoOv5rjOi-fN2A-27cPwOtiy2Q2j6UFtCh3QrfWAI,7512
|
21
21
|
osism/commands/service.py,sha256=A1lgAlGeCJpbFFqF55DRWPcCirIgpU0dzjzVLZ0mz3k,2649
|
22
22
|
osism/commands/set.py,sha256=xLBi2DzbVQo2jb3-cOIE9In5UB3vFxquQJkDN-EsfhM,1425
|
23
23
|
osism/commands/status.py,sha256=X-Rcj-XuNPDBoxsGkf96NswwpmTognxz1V6E2NX2ZgY,1997
|
24
24
|
osism/commands/sync.py,sha256=Vf9k7uVQTIu-8kK1u7Gjs3et3RRBEkmnNikot_PFJIE,484
|
25
25
|
osism/commands/task.py,sha256=mwJJ7a71Lw3o_FX7j3rR0-NbPdPwMDOjbOAiiXE4uGc,543
|
26
|
-
osism/commands/validate.py,sha256=
|
26
|
+
osism/commands/validate.py,sha256=cA0CSvcbTr0K_6C5EofULrJSEp5xthpRC0TZgb_eazU,4233
|
27
27
|
osism/commands/vault.py,sha256=Ip0IMR7zaBkPbLJenXr4ZwxM6FnozZ9wn9rwHmFHo8s,1818
|
28
28
|
osism/commands/volume.py,sha256=l6oAk__dFM8KKdLTWOvuSiI7tLh9wAPZp8hwmYF-NX0,6595
|
29
29
|
osism/commands/wait.py,sha256=mKFDqEXcaLlKw1T3MuBEZpNh7CeL3lpUXgubD2_f8es,6580
|
@@ -38,18 +38,18 @@ osism/services/listener.py,sha256=eEamlQsJqCuU9K2QFmk3yM9LAJZEanVcTLtGMsNCKjs,97
|
|
38
38
|
osism/tasks/__init__.py,sha256=ZEu_KYsapTYp0etr-rLqie_NT_LndHDDpx53xITru5Y,8691
|
39
39
|
osism/tasks/ansible.py,sha256=RcLxLrjzL5_X6OjNHm3H0lZlmKKlYKIANB0M4_d4chE,1109
|
40
40
|
osism/tasks/ceph.py,sha256=eIQkah3Kj4INtOkF9kTjHbXJ3_J2lg48EWJKfHc-UYw,615
|
41
|
-
osism/tasks/conductor.py,sha256=
|
41
|
+
osism/tasks/conductor.py,sha256=Rxx8LrHVMksVpywpzi-av8Nj8qiBTmOFG9P_ksUe6SE,19477
|
42
42
|
osism/tasks/kolla.py,sha256=wJQpWn_01iWLkr7l7T7RNrQGfRgsgmYi4WQlTmNGvew,618
|
43
43
|
osism/tasks/kubernetes.py,sha256=VzXq_VrYU_CLm4cOruqnE3Kq2ydfO9glZ3p0bp3OYoc,625
|
44
44
|
osism/tasks/netbox.py,sha256=QVOLiTH2Su237YAS0QfXbQ86E-OA1JzrFDfyi9JBmvk,5658
|
45
45
|
osism/tasks/openstack.py,sha256=g15tCll5vP1pC6ysxRCTZxplsdGmXbxaCH3k1Qdv5Xg,6367
|
46
|
-
osism/tasks/reconciler.py,sha256=
|
47
|
-
osism/utils/__init__.py,sha256=
|
48
|
-
osism-0.
|
49
|
-
osism-0.
|
50
|
-
osism-0.
|
51
|
-
osism-0.
|
52
|
-
osism-0.
|
53
|
-
osism-0.
|
54
|
-
osism-0.
|
55
|
-
osism-0.
|
46
|
+
osism/tasks/reconciler.py,sha256=b6IRJBYvG_lRlQ6cQ46jSZJPNJeTI7igaCJ_7AzgIDQ,1767
|
47
|
+
osism/utils/__init__.py,sha256=_Y4qchR5yyI_JKhBWd_jcsvDLYZjxO0c3iMA_VRQl58,4304
|
48
|
+
osism-0.20250514.0.dist-info/licenses/AUTHORS,sha256=oWotd63qsnNR945QLJP9mEXaXNtCMaesfo8ZNuLjwpU,39
|
49
|
+
osism-0.20250514.0.dist-info/licenses/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
|
50
|
+
osism-0.20250514.0.dist-info/METADATA,sha256=xH8NKJUuInnfv4yHUIA00DMY_SGxYjaqXvDt4_OW-Jo,2935
|
51
|
+
osism-0.20250514.0.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
|
52
|
+
osism-0.20250514.0.dist-info/entry_points.txt,sha256=IEmaQFJyKGN4mtNTLf9ogw7RXjEma4mSOUy_CEC4wzA,2975
|
53
|
+
osism-0.20250514.0.dist-info/pbr.json,sha256=PlM5t0PTJqXXvUbF4gDXHHJ-HyaeT0oUE17z93Yqg7A,47
|
54
|
+
osism-0.20250514.0.dist-info/top_level.txt,sha256=8L8dsI9hcaGHsdnR4k_LN9EM78EhwrXRFHyAryPXZtY,6
|
55
|
+
osism-0.20250514.0.dist-info/RECORD,,
|
@@ -21,6 +21,7 @@ get states = osism.commands.get:States
|
|
21
21
|
get status = osism.commands.status:Run
|
22
22
|
get tasks = osism.commands.get:Tasks
|
23
23
|
get versions manager = osism.commands.get:VersionsManager
|
24
|
+
get versions netbox = osism.commands.netbox:Versions
|
24
25
|
log ansible = osism.commands.log:Ansible
|
25
26
|
log container = osism.commands.log:Container
|
26
27
|
log file = osism.commands.log:File
|
@@ -40,9 +41,7 @@ manage netbox = osism.commands.netbox:Manage
|
|
40
41
|
manage server list = osism.commands.server:ServerList
|
41
42
|
manage server migrate = osism.commands.server:ServerMigrate
|
42
43
|
manage volume list = osism.commands.volume:VolumeList
|
43
|
-
netbox
|
44
|
-
netbox sync = osism.commands.netbox:Sync
|
45
|
-
netbox sync ironic = osism.commands.netbox:Ironic
|
44
|
+
netbox = osism.commands.netbox:Console
|
46
45
|
noset bootstrap = osism.commands.noset:NoBootstrap
|
47
46
|
noset maintenance = osism.commands.noset:NoMaintenance
|
48
47
|
noset vault password = osism.commands.vault:UnsetPassword
|
@@ -0,0 +1 @@
|
|
1
|
+
Christian Berendt <berendt@osism.tech>
|
@@ -0,0 +1 @@
|
|
1
|
+
{"git_version": "6bd0c0e", "is_release": false}
|
@@ -1 +0,0 @@
|
|
1
|
-
janhorstmann <horstmann@osism.tech>
|
@@ -1 +0,0 @@
|
|
1
|
-
{"git_version": "6139ca4", "is_release": false}
|
File without changes
|
File without changes
|