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 +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 -157
- osism/tasks/openstack.py +0 -41
- osism/tasks/reconciler.py +0 -30
- osism/utils/__init__.py +17 -0
- {osism-0.20250425.0.dist-info → osism-0.20250514.0.dist-info}/METADATA +8 -9
- {osism-0.20250425.0.dist-info → osism-0.20250514.0.dist-info}/RECORD +20 -20
- {osism-0.20250425.0.dist-info → osism-0.20250514.0.dist-info}/WHEEL +1 -1
- {osism-0.20250425.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.20250425.0.dist-info/licenses/AUTHORS +0 -1
- osism-0.20250425.0.dist-info/pbr.json +0 -1
- {osism-0.20250425.0.dist-info → osism-0.20250514.0.dist-info}/licenses/LICENSE +0 -0
- {osism-0.20250425.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
|
|
@@ -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 "
|
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=
|
312
|
+
auto_release_time=600,
|
292
313
|
)
|
293
|
-
if lock.acquire(timeout=
|
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.
|
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.
|
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==
|
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
|
-
osism/tasks/openstack.py,sha256=
|
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.
|
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,,
|
@@ -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": "121615f", "is_release": false}
|
File without changes
|
File without changes
|