osism 0.20250425.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 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 = "mycoolapp"
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.
@@ -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"It takes a moment until task {t.task_id} has been started and output is visible here."
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.30", "1.31", "1.32"]
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, reconciler, handle_task
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(f"Task {task.task_id} is running. Wait. No more output.")
32
- task.wait(timeout=None, interval=0.5)
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 Ping(Command):
130
+ class Versions(Command):
144
131
  def get_parser(self, prog_name):
145
- parser = super(Ping, self).get_parser(prog_name)
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)
@@ -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(f"Task {t.task_id} is running in background. No more output.")
80
+ logger.info(
81
+ f"Task {t.task_id} (sync inventory) is running in background. No more output."
82
+ )
@@ -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
- OSISM_CONDUCTOR_NETBOX_FILTER_LIST = os.getenv(
39
- "OSISM_CONDUCTOR_NETBOX_FILTER_LIST",
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
- @worker_process_init.connect
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 OSISM_CONDUCTOR_NETBOX_FILTER_LIST needs to be an array of mappings containing supported netbox device filters: {supported_nb_device_filters}"
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 OSISM_CONDUCTOR_NETBOX_FILTER_LIST: {exc}")
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
 
@@ -198,11 +218,6 @@ def sync_netbox_with_ironic(self, force_update=False):
198
218
  logger.info(
199
219
  f"Cleaning up baremetal node not found in netbox: {node['Name']}"
200
220
  )
201
- flavor_name = "osism-" + node["Name"]
202
- flavor = openstack.compute_flavor_get(flavor_name)
203
- if flavor:
204
- logger.info(f"Deleting flavor {flavor_name}")
205
- openstack.compute_flavor_delete(flavor)
206
221
  for port in openstack.baremetal_port_list(
207
222
  details=False, attributes=dict(node_uuid=node["UUID"])
208
223
  ):
@@ -216,20 +231,51 @@ def sync_netbox_with_ironic(self, force_update=False):
216
231
  # NOTE: Find nodes in netbox which are not present in Ironic and add them
217
232
  for device in devices:
218
233
  logger.info(f"Looking for {device.name} in ironic")
234
+ logger.info(device)
219
235
 
220
236
  node_interfaces = list(netbox.get_interfaces_by_device(device.name))
221
237
 
222
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)
223
260
  if (
224
261
  "driver" in node_attributes
225
262
  and node_attributes["driver"] in driver_params.keys()
226
263
  ):
227
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
228
276
  address_key = driver_params[node_attributes["driver"]]["address"]
229
277
  if address_key in node_attributes["driver_info"]:
230
- if "oob_address" in device.custom_fields:
231
- node_mgmt_address = device.custom_fields["oob_address"]
232
- elif "address" in device.oob_ip:
278
+ if device.oob_ip and "address" in device.oob_ip:
233
279
  node_mgmt_address = device.oob_ip["address"]
234
280
  else:
235
281
  node_mgmt_addresses = [
@@ -253,44 +299,19 @@ def sync_netbox_with_ironic(self, force_update=False):
253
299
  )
254
300
  )
255
301
  )
256
- else:
257
- logger.error(f"Could not find out-of-band address for {device}")
258
- node_attributes["driver_info"].pop(address_key, None)
259
- if (
260
- "port" in driver_params[node_attributes["driver"]]
261
- and "oob_port" in device.custom_fields
262
- and device.custom_fields["oob_port"]
263
- ):
264
- port_key = driver_params[node_attributes["driver"]]["port"]
265
- node_attributes["driver_info"].update(
266
- {port_key: device.custom_fields["oob_port"]}
267
- )
268
302
  node_attributes.update({"resource_class": device.name})
269
303
  ports_attributes = [
270
304
  dict(address=interface.mac_address)
271
305
  for interface in node_interfaces
272
306
  if interface.enabled and not interface.mgmt_only and interface.mac_address
273
307
  ]
274
- flavor_attributes = {
275
- "ram": 1,
276
- "disk": 0,
277
- "vcpus": 1,
278
- "is_public": False,
279
- "extra_specs": {
280
- "resources:CUSTOM_"
281
- + device.name.upper().replace("-", "_").replace(".", "_"): "1",
282
- "resources:VCPU": "0",
283
- "resources:MEMORY_MB": "0",
284
- "resources:DISK_GB": "0",
285
- },
286
- }
287
308
 
288
309
  lock = Redlock(
289
310
  key=f"lock_osism_tasks_conductor_sync_netbox_with_ironic-{device.name}",
290
311
  masters={utils.redis},
291
- auto_release_time=60,
312
+ auto_release_time=600,
292
313
  )
293
- if lock.acquire(timeout=20):
314
+ if lock.acquire(timeout=120):
294
315
  try:
295
316
  logger.info(f"Processing device {device.name}")
296
317
  node = openstack.baremetal_node_show(device.name, ignore_missing=True)
@@ -413,48 +434,6 @@ def sync_netbox_with_ironic(self, force_update=False):
413
434
  logger.info(
414
435
  f"Validation of management interface failed for baremetal node for {device.name}\nReason: {node_validation['management'].reason}"
415
436
  )
416
-
417
- flavor_name = "osism-" + device.name
418
- flavor = openstack.compute_flavor_get(flavor_name)
419
- if not flavor:
420
- logger.info(f"Creating flavor for {flavor_name}")
421
- flavor = openstack.compute_flavor_create(
422
- flavor_name, flavor_attributes
423
- )
424
- else:
425
- flavor_updates = {}
426
- deep_compare(flavor_attributes, flavor, flavor_updates)
427
- flavor_updates_extra_specs = flavor_updates.pop("extra_specs", None)
428
- if flavor_updates:
429
- logger.info(
430
- f"Updating flavor for {device.name} with {flavor_updates}"
431
- )
432
- openstack.compute_flavor_delete(flavor)
433
- flavor = openstack.compute_flavor_create(
434
- flavor_name, flavor_attributes
435
- )
436
- elif flavor_updates_extra_specs:
437
- logger.info(
438
- f"Updating flavor extra_specs for {device.name} with {flavor_updates_extra_specs}"
439
- )
440
- openstack.compute_flavor_update_extra_specs(
441
- flavor, flavor_updates_extra_specs
442
- )
443
- flavor = openstack.compute_flavor_get(flavor_name)
444
- for extra_specs_key in flavor["extra_specs"].keys():
445
- if (
446
- extra_specs_key
447
- not in flavor_attributes["extra_specs"].keys()
448
- ):
449
- logger.info(
450
- f"Deleting flavor extra_specs property {extra_specs_key} for {device.name}"
451
- )
452
- flavor = (
453
- openstack.compute_flavor_delete_extra_specs_property(
454
- flavor, extra_specs_key
455
- )
456
- )
457
-
458
437
  except Exception as exc:
459
438
  logger.info(
460
439
  f"Could not fully synchronize device {device.name} with ironic: {exc}"
osism/tasks/openstack.py CHANGED
@@ -136,47 +136,6 @@ def baremetal_port_delete(self, port_or_id):
136
136
  return result
137
137
 
138
138
 
139
- @app.task(bind=True, name="osism.tasks.openstack.compute_flavor_get")
140
- def compute_flavor_get(self, name_or_id):
141
- conn = utils.get_openstack_connection()
142
- result = conn.compute.find_flavor(
143
- name_or_id, ignore_missing=True, get_extra_specs=True
144
- )
145
- return result
146
-
147
-
148
- @app.task(bind=True, name="osism.tasks.openstack.compute_flavor_create")
149
- def compute_flavor_create(self, name, attributes=None):
150
- if attributes is None:
151
- attributes = {}
152
- attributes.update({"name": name})
153
- extra_specs = attributes.pop("extra_specs", None)
154
- conn = utils.get_openstack_connection()
155
- flavor = conn.compute.create_flavor(**attributes)
156
- if extra_specs:
157
- flavor = conn.compute.create_flavor_extra_specs(flavor, extra_specs)
158
- return flavor
159
-
160
-
161
- @app.task(bind=True, name="osism.tasks.openstack.compute_flavor_delete")
162
- def compute_flavor_delete(self, flavor):
163
- conn = utils.get_openstack_connection()
164
- conn.compute.delete_flavor(flavor, ignore_missing=True)
165
-
166
-
167
- @app.task(bind=True, name="osism.tasks.openstack.compute_flavor_update_extra_specs")
168
- def compute_flavor_update_extra_specs(self, flavor, extra_specs={}):
169
- conn = utils.get_openstack_connection()
170
- for key, value in extra_specs.items():
171
- conn.compute.update_flavor_extra_specs_property(flavor, key, value)
172
-
173
-
174
- @app.task(bind=True, name="osism.tasks.openstack.compute_flavor_delete_extra_specs")
175
- def compute_flavor_delete_extra_specs_property(self, flavor, prop):
176
- conn = utils.get_openstack_connection()
177
- conn.compute.delete_flavor_extra_specs_property(flavor, prop)
178
-
179
-
180
139
  @app.task(bind=True, name="osism.tasks.openstack.image_manager")
181
140
  def image_manager(
182
141
  self,
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.20250425.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
@@ -27,22 +27,21 @@ Requires-Dist: GitPython==3.1.44
27
27
  Requires-Dist: Jinja2==3.1.6
28
28
  Requires-Dist: PyYAML==6.0.2
29
29
  Requires-Dist: ara==1.7.2
30
- Requires-Dist: celery[redis]==5.5.1
30
+ Requires-Dist: celery[redis]==5.5.2
31
31
  Requires-Dist: cliff==4.9.1
32
- Requires-Dist: deepdiff==8.4.2
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.0
38
- Requires-Dist: jc==1.25.4
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==79.0.1
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.20250423.0; extra == "openstack-image-manager"
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=xJC6RyC1TU54PU2C06rlialh2SmTCgM1L0MSgyUlubU,4331
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=xzFRbf5mdl_MUAUFqPavE14vaVuTr5z6b969vJQvF2E,1306
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=n3lLb1cS3GahQqRT0723di98hg47MjVzDzkAoeZX7qU,16780
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=bvwBuc27glcZ5xDXQkKrgMDoXEdYgJnyjKd8fRj6hHk,890
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=5ypZzA91QGgWCt35rVNJSXVC9l189IntS4UnsD8LRZY,11971
17
- osism/commands/netbox.py,sha256=_2-j6XM9JvH0DXnbct6rG9T6hT8KEpm3vazQC28Rt7I,4529
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=Ja_b86gX6-_Pr3DmrUUvskmEnnJpHQ-XJNQLycMJeyc,2818
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=hIQB0zk4xIBZJORtBp_tWrXTRKKhB2qi6j-mznDxKR4,4191
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=JlfSMgwwiXVjVk6HGwAtoJCyZO_qbY_O4tLtz7ibHKg,21039
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
- osism/tasks/openstack.py,sha256=J0dvJYUWT1182LArm8UXplVpK5bNWSAdiHYR7urh2GM,7941
46
- osism/tasks/reconciler.py,sha256=RGUcax2gDuyVLw1nGRQn5izXclnPBo9MRl0ndLDiiYQ,2707
47
- osism/utils/__init__.py,sha256=_uhe9ghqAJ2me0p187X-vzJ2Nh_Hpmfw3D6HU4kKa10,3759
48
- osism-0.20250425.0.dist-info/licenses/AUTHORS,sha256=DJIRsjyrFxKjFvmpUNDRDBS04nRiJ5B6FpKcDcfnoGM,36
49
- osism-0.20250425.0.dist-info/licenses/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
50
- osism-0.20250425.0.dist-info/METADATA,sha256=rofd2A8V3RZhLVaC0Y7qmKQuWCmjNSjickRtZIg5U-g,2972
51
- osism-0.20250425.0.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
52
- osism-0.20250425.0.dist-info/entry_points.txt,sha256=DlfrvU14rI55WuTrwNRoce9FY3ric4HeZKZx_Z3NzCw,3015
53
- osism-0.20250425.0.dist-info/pbr.json,sha256=qcrfoQrh4nhmive5GikkS5SflAbt999idvJgP56J1JY,47
54
- osism-0.20250425.0.dist-info/top_level.txt,sha256=8L8dsI9hcaGHsdnR4k_LN9EM78EhwrXRFHyAryPXZtY,6
55
- osism-0.20250425.0.dist-info/RECORD,,
45
+ osism/tasks/openstack.py,sha256=g15tCll5vP1pC6ysxRCTZxplsdGmXbxaCH3k1Qdv5Xg,6367
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (79.0.1)
2
+ Generator: setuptools (80.4.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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 ping = osism.commands.netbox:Ping
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": "121615f", "is_release": false}