osism 0.20250628.0__tar.gz → 0.20250701.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. osism-0.20250701.0/ChangeLog +7 -0
  2. {osism-0.20250628.0 → osism-0.20250701.0}/Containerfile +6 -1
  3. {osism-0.20250628.0 → osism-0.20250701.0}/Dockerfile +6 -1
  4. {osism-0.20250628.0/osism.egg-info → osism-0.20250701.0}/PKG-INFO +2 -1
  5. {osism-0.20250628.0 → osism-0.20250701.0}/Pipfile +1 -0
  6. {osism-0.20250628.0 → osism-0.20250701.0}/Pipfile.lock +48 -7
  7. osism-0.20250701.0/files/redfishMockupCreate.py +499 -0
  8. {osism-0.20250628.0 → osism-0.20250701.0}/osism/commands/baremetal.py +37 -6
  9. osism-0.20250701.0/osism/commands/redfish.py +219 -0
  10. {osism-0.20250628.0 → osism-0.20250701.0}/osism/settings.py +3 -0
  11. {osism-0.20250628.0 → osism-0.20250701.0}/osism/tasks/conductor/__init__.py +7 -0
  12. {osism-0.20250628.0 → osism-0.20250701.0}/osism/tasks/conductor/config.py +52 -35
  13. {osism-0.20250628.0 → osism-0.20250701.0}/osism/tasks/conductor/ironic.py +95 -99
  14. osism-0.20250701.0/osism/tasks/conductor/redfish.py +300 -0
  15. osism-0.20250701.0/osism/tasks/conductor/utils.py +227 -0
  16. {osism-0.20250628.0 → osism-0.20250701.0/osism.egg-info}/PKG-INFO +2 -1
  17. {osism-0.20250628.0 → osism-0.20250701.0}/osism.egg-info/SOURCES.txt +3 -0
  18. {osism-0.20250628.0 → osism-0.20250701.0}/osism.egg-info/entry_points.txt +1 -0
  19. osism-0.20250701.0/osism.egg-info/pbr.json +1 -0
  20. {osism-0.20250628.0 → osism-0.20250701.0}/osism.egg-info/requires.txt +1 -0
  21. {osism-0.20250628.0 → osism-0.20250701.0}/requirements.txt +1 -0
  22. {osism-0.20250628.0 → osism-0.20250701.0}/setup.cfg +1 -0
  23. osism-0.20250628.0/ChangeLog +0 -7
  24. osism-0.20250628.0/osism/tasks/conductor/utils.py +0 -79
  25. osism-0.20250628.0/osism.egg-info/pbr.json +0 -1
  26. {osism-0.20250628.0 → osism-0.20250701.0}/.flake8 +0 -0
  27. {osism-0.20250628.0 → osism-0.20250701.0}/.github/renovate.json +0 -0
  28. {osism-0.20250628.0 → osism-0.20250701.0}/.github/workflows/publish.yml +0 -0
  29. {osism-0.20250628.0 → osism-0.20250701.0}/.hadolint.yaml +0 -0
  30. {osism-0.20250628.0 → osism-0.20250701.0}/.zuul.yaml +0 -0
  31. {osism-0.20250628.0 → osism-0.20250701.0}/AUTHORS +0 -0
  32. {osism-0.20250628.0 → osism-0.20250701.0}/LICENSE +0 -0
  33. {osism-0.20250628.0 → osism-0.20250701.0}/README.md +0 -0
  34. {osism-0.20250628.0 → osism-0.20250701.0}/files/change.sh +0 -0
  35. {osism-0.20250628.0 → osism-0.20250701.0}/files/cleanup-ansible-collections.sh +0 -0
  36. {osism-0.20250628.0 → osism-0.20250701.0}/files/clustershell/clush.conf +0 -0
  37. {osism-0.20250628.0 → osism-0.20250701.0}/files/clustershell/groups.conf +0 -0
  38. {osism-0.20250628.0 → osism-0.20250701.0}/files/data/SCS-Spec.MandatoryFlavors.verbose.yaml +0 -0
  39. {osism-0.20250628.0 → osism-0.20250701.0}/files/netbox-manager/settings.toml +0 -0
  40. {osism-0.20250628.0 → osism-0.20250701.0}/files/run-ansible-console.sh +0 -0
  41. {osism-0.20250628.0 → osism-0.20250701.0}/files/sonic/config_db.json +0 -0
  42. {osism-0.20250628.0 → osism-0.20250701.0}/files/sonic/port_config/Accton-AS4625-54T.ini +0 -0
  43. {osism-0.20250628.0 → osism-0.20250701.0}/files/sonic/port_config/Accton-AS5835-54T.ini +0 -0
  44. {osism-0.20250628.0 → osism-0.20250701.0}/files/sonic/port_config/Accton-AS5835-54X.ini +0 -0
  45. {osism-0.20250628.0 → osism-0.20250701.0}/files/sonic/port_config/Accton-AS7326-56X.ini +0 -0
  46. {osism-0.20250628.0 → osism-0.20250701.0}/files/sonic/port_config/Accton-AS7726-32X.ini +0 -0
  47. {osism-0.20250628.0 → osism-0.20250701.0}/files/sonic/port_config/Accton-AS9716-32D.ini +0 -0
  48. {osism-0.20250628.0 → osism-0.20250701.0}/osism/__init__.py +0 -0
  49. {osism-0.20250628.0 → osism-0.20250701.0}/osism/__main__.py +0 -0
  50. {osism-0.20250628.0 → osism-0.20250701.0}/osism/api.py +0 -0
  51. {osism-0.20250628.0 → osism-0.20250701.0}/osism/commands/__init__.py +0 -0
  52. {osism-0.20250628.0 → osism-0.20250701.0}/osism/commands/apply.py +0 -0
  53. {osism-0.20250628.0 → osism-0.20250701.0}/osism/commands/compose.py +0 -0
  54. {osism-0.20250628.0 → osism-0.20250701.0}/osism/commands/compute.py +0 -0
  55. {osism-0.20250628.0 → osism-0.20250701.0}/osism/commands/configuration.py +0 -0
  56. {osism-0.20250628.0 → osism-0.20250701.0}/osism/commands/console.py +0 -0
  57. {osism-0.20250628.0 → osism-0.20250701.0}/osism/commands/container.py +0 -0
  58. {osism-0.20250628.0 → osism-0.20250701.0}/osism/commands/get.py +0 -0
  59. {osism-0.20250628.0 → osism-0.20250701.0}/osism/commands/log.py +0 -0
  60. {osism-0.20250628.0 → osism-0.20250701.0}/osism/commands/manage.py +0 -0
  61. {osism-0.20250628.0 → osism-0.20250701.0}/osism/commands/netbox.py +0 -0
  62. {osism-0.20250628.0 → osism-0.20250701.0}/osism/commands/noset.py +0 -0
  63. {osism-0.20250628.0 → osism-0.20250701.0}/osism/commands/reconciler.py +0 -0
  64. {osism-0.20250628.0 → osism-0.20250701.0}/osism/commands/server.py +0 -0
  65. {osism-0.20250628.0 → osism-0.20250701.0}/osism/commands/service.py +0 -0
  66. {osism-0.20250628.0 → osism-0.20250701.0}/osism/commands/set.py +0 -0
  67. {osism-0.20250628.0 → osism-0.20250701.0}/osism/commands/status.py +0 -0
  68. {osism-0.20250628.0 → osism-0.20250701.0}/osism/commands/sync.py +0 -0
  69. {osism-0.20250628.0 → osism-0.20250701.0}/osism/commands/task.py +0 -0
  70. {osism-0.20250628.0 → osism-0.20250701.0}/osism/commands/validate.py +0 -0
  71. {osism-0.20250628.0 → osism-0.20250701.0}/osism/commands/vault.py +0 -0
  72. {osism-0.20250628.0 → osism-0.20250701.0}/osism/commands/volume.py +0 -0
  73. {osism-0.20250628.0 → osism-0.20250701.0}/osism/commands/wait.py +0 -0
  74. {osism-0.20250628.0 → osism-0.20250701.0}/osism/commands/worker.py +0 -0
  75. {osism-0.20250628.0 → osism-0.20250701.0}/osism/data/__init__.py +0 -0
  76. {osism-0.20250628.0 → osism-0.20250701.0}/osism/data/enums.py +0 -0
  77. {osism-0.20250628.0 → osism-0.20250701.0}/osism/data/playbooks.py +0 -0
  78. {osism-0.20250628.0 → osism-0.20250701.0}/osism/main.py +0 -0
  79. {osism-0.20250628.0 → osism-0.20250701.0}/osism/services/__init__.py +0 -0
  80. {osism-0.20250628.0 → osism-0.20250701.0}/osism/services/listener.py +0 -0
  81. {osism-0.20250628.0 → osism-0.20250701.0}/osism/tasks/__init__.py +0 -0
  82. {osism-0.20250628.0 → osism-0.20250701.0}/osism/tasks/ansible.py +0 -0
  83. {osism-0.20250628.0 → osism-0.20250701.0}/osism/tasks/ceph.py +0 -0
  84. {osism-0.20250628.0 → osism-0.20250701.0}/osism/tasks/conductor/netbox.py +0 -0
  85. {osism-0.20250628.0 → osism-0.20250701.0}/osism/tasks/conductor/sonic/__init__.py +0 -0
  86. {osism-0.20250628.0 → osism-0.20250701.0}/osism/tasks/conductor/sonic/bgp.py +0 -0
  87. {osism-0.20250628.0 → osism-0.20250701.0}/osism/tasks/conductor/sonic/cache.py +0 -0
  88. {osism-0.20250628.0 → osism-0.20250701.0}/osism/tasks/conductor/sonic/config_generator.py +0 -0
  89. {osism-0.20250628.0 → osism-0.20250701.0}/osism/tasks/conductor/sonic/connections.py +0 -0
  90. {osism-0.20250628.0 → osism-0.20250701.0}/osism/tasks/conductor/sonic/constants.py +0 -0
  91. {osism-0.20250628.0 → osism-0.20250701.0}/osism/tasks/conductor/sonic/device.py +0 -0
  92. {osism-0.20250628.0 → osism-0.20250701.0}/osism/tasks/conductor/sonic/exporter.py +0 -0
  93. {osism-0.20250628.0 → osism-0.20250701.0}/osism/tasks/conductor/sonic/interface.py +0 -0
  94. {osism-0.20250628.0 → osism-0.20250701.0}/osism/tasks/conductor/sonic/sync.py +0 -0
  95. {osism-0.20250628.0 → osism-0.20250701.0}/osism/tasks/conductor.py +0 -0
  96. {osism-0.20250628.0 → osism-0.20250701.0}/osism/tasks/kolla.py +0 -0
  97. {osism-0.20250628.0 → osism-0.20250701.0}/osism/tasks/kubernetes.py +0 -0
  98. {osism-0.20250628.0 → osism-0.20250701.0}/osism/tasks/netbox.py +0 -0
  99. {osism-0.20250628.0 → osism-0.20250701.0}/osism/tasks/openstack.py +0 -0
  100. {osism-0.20250628.0 → osism-0.20250701.0}/osism/tasks/reconciler.py +0 -0
  101. {osism-0.20250628.0 → osism-0.20250701.0}/osism/utils/__init__.py +0 -0
  102. {osism-0.20250628.0 → osism-0.20250701.0}/osism.egg-info/dependency_links.txt +0 -0
  103. {osism-0.20250628.0 → osism-0.20250701.0}/osism.egg-info/not-zip-safe +0 -0
  104. {osism-0.20250628.0 → osism-0.20250701.0}/osism.egg-info/top_level.txt +0 -0
  105. {osism-0.20250628.0 → osism-0.20250701.0}/playbooks/build.yml +0 -0
  106. {osism-0.20250628.0 → osism-0.20250701.0}/playbooks/pre.yml +0 -0
  107. {osism-0.20250628.0 → osism-0.20250701.0}/playbooks/test-setup.yml +0 -0
  108. {osism-0.20250628.0 → osism-0.20250701.0}/requirements.ansible.txt +0 -0
  109. {osism-0.20250628.0 → osism-0.20250701.0}/requirements.netbox-manager.txt +0 -0
  110. {osism-0.20250628.0 → osism-0.20250701.0}/requirements.openstack-flavor-manager.txt +0 -0
  111. {osism-0.20250628.0 → osism-0.20250701.0}/requirements.openstack-image-manager.txt +0 -0
  112. {osism-0.20250628.0 → osism-0.20250701.0}/requirements.yml +0 -0
  113. {osism-0.20250628.0 → osism-0.20250701.0}/setup.py +0 -0
@@ -0,0 +1,7 @@
1
+ CHANGES
2
+ =======
3
+
4
+ v0.20250701.0
5
+ -------------
6
+
7
+ * Skip UUID resolution for image URLs in conductor config (#1542)
@@ -7,7 +7,7 @@ FROM ${IMAGE}:${PYTHON_VERSION}-alpine${ALPINE_VERSION}
7
7
  ENV PYTHONWARNINGS="ignore::UserWarning"
8
8
 
9
9
  COPY . /src
10
- COPY --from=ghcr.io/astral-sh/uv:0.7.16 /uv /usr/local/bin/uv
10
+ COPY --from=ghcr.io/astral-sh/uv:0.7.17 /uv /usr/local/bin/uv
11
11
 
12
12
  COPY files/data /data
13
13
  COPY files/change.sh /change.sh
@@ -23,6 +23,8 @@ COPY files/sonic/config_db.json /etc/sonic/config_db.json
23
23
 
24
24
  COPY files/netbox-manager/settings.toml /usr/local/config/settings.toml
25
25
 
26
+ COPY files/redfishMockupCreate.py /
27
+
26
28
  RUN apk add --no-cache bash
27
29
 
28
30
  SHELL ["/bin/bash", "-o", "pipefail", "-c"]
@@ -53,6 +55,9 @@ uv pip install --no-cache --system -r /src/requirements.openstack-image-manager.
53
55
  uv pip install --no-cache --system -r /src/requirements.openstack-flavor-manager.txt
54
56
  uv pip install --no-cache --system -r /src/requirements.netbox-manager.txt
55
57
 
58
+ # required by redfishMockupCreate.py
59
+ uv pip install --no-cache --system "redfish==3.3.1"
60
+
56
61
  # install python-osism
57
62
  uv pip install --no-cache --system /src
58
63
 
@@ -7,7 +7,7 @@ FROM ${IMAGE}:${PYTHON_VERSION}-alpine${ALPINE_VERSION}
7
7
  ENV PYTHONWARNINGS="ignore::UserWarning"
8
8
 
9
9
  COPY . /src
10
- COPY --from=ghcr.io/astral-sh/uv:0.7.16 /uv /usr/local/bin/uv
10
+ COPY --from=ghcr.io/astral-sh/uv:0.7.17 /uv /usr/local/bin/uv
11
11
 
12
12
  COPY files/data /data
13
13
  COPY files/change.sh /change.sh
@@ -23,6 +23,8 @@ COPY files/sonic/config_db.json /etc/sonic/config_db.json
23
23
 
24
24
  COPY files/netbox-manager/settings.toml /usr/local/config/settings.toml
25
25
 
26
+ COPY files/redfishMockupCreate.py /
27
+
26
28
  RUN apk add --no-cache bash
27
29
 
28
30
  SHELL ["/bin/bash", "-o", "pipefail", "-c"]
@@ -53,6 +55,9 @@ uv pip install --no-cache --system -r /src/requirements.openstack-image-manager.
53
55
  uv pip install --no-cache --system -r /src/requirements.openstack-flavor-manager.txt
54
56
  uv pip install --no-cache --system -r /src/requirements.netbox-manager.txt
55
57
 
58
+ # required by redfishMockupCreate.py
59
+ uv pip install --no-cache --system "redfish==3.3.1"
60
+
56
61
  # install python-osism
57
62
  uv pip install --no-cache --system /src
58
63
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: osism
3
- Version: 0.20250628.0
3
+ Version: 0.20250701.0
4
4
  Summary: OSISM manager interface
5
5
  Home-page: https://github.com/osism/python-osism
6
6
  Author: OSISM GmbH
@@ -53,6 +53,7 @@ Requires-Dist: sushy==5.6.0
53
53
  Requires-Dist: tabulate==0.9.0
54
54
  Requires-Dist: transitions==0.9.2
55
55
  Requires-Dist: uvicorn[standard]==0.35.0
56
+ Requires-Dist: validators==0.35.0
56
57
  Requires-Dist: watchdog==6.0.0
57
58
  Provides-Extra: ansible
58
59
  Requires-Dist: ansible-runner==2.4.1; extra == "ansible"
@@ -35,4 +35,5 @@ setuptools = "==80.9.0"
35
35
  sqlmodel = "==0.0.24"
36
36
  sushy = "==5.6.0"
37
37
  transitions = "==0.9.2"
38
+ validators = "==0.35.0"
38
39
  watchdog = "==6.0.0"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "_meta": {
3
3
  "hash": {
4
- "sha256": "e43533c2a1b4d5822ad125c9911000317264ecc9c79919413c6da4adcc3b4e48"
4
+ "sha256": "072d329d514cdaa8d46395aeee9fc0a440813e50314abfabce1c8c382f05ecf7"
5
5
  },
6
6
  "pipfile-spec": 6,
7
7
  "requires": {},
@@ -337,10 +337,10 @@
337
337
  },
338
338
  "click-plugins": {
339
339
  "hashes": [
340
- "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b",
341
- "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"
340
+ "sha256:008d65743833ffc1f5417bf0e78e8d2c23aab04d9745ba817bd3e71b0feb6aa6",
341
+ "sha256:d7af3984a99d243c131aa1a828331e7630f4a88a9741fd05c927b204bcf92261"
342
342
  ],
343
- "version": "==1.1.1"
343
+ "version": "==1.1.1.2"
344
344
  },
345
345
  "click-repl": {
346
346
  "hashes": [
@@ -368,11 +368,11 @@
368
368
  },
369
369
  "cmd2": {
370
370
  "hashes": [
371
- "sha256:650a5892bf29b233d3d6775b5e3cc813648cff0d79134f707981f66baaed9f42",
372
- "sha256:c4c8e3a515a7e69061b7ebbaadeb9d7fae283b7fee1a44ff169e33e2ccb2eec3"
371
+ "sha256:81d8135b46210e1d03a5a810baf859069a62214788ceeec3588f44eed86fbeeb",
372
+ "sha256:c85faf603e8cfeb4302206f49c0530a83d63386b0d90ff6a957f2c816eb767d7"
373
373
  ],
374
374
  "markers": "python_version >= '3.9'",
375
- "version": "==2.6.1"
375
+ "version": "==2.7.0"
376
376
  },
377
377
  "cryptography": {
378
378
  "hashes": [
@@ -780,6 +780,14 @@
780
780
  "markers": "python_version >= '3.5' and python_version < '4.0'",
781
781
  "version": "==0.7.3"
782
782
  },
783
+ "markdown-it-py": {
784
+ "hashes": [
785
+ "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1",
786
+ "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"
787
+ ],
788
+ "markers": "python_version >= '3.8'",
789
+ "version": "==3.0.0"
790
+ },
783
791
  "markupsafe": {
784
792
  "hashes": [
785
793
  "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4",
@@ -847,6 +855,14 @@
847
855
  "markers": "python_version >= '3.9'",
848
856
  "version": "==3.0.2"
849
857
  },
858
+ "mdurl": {
859
+ "hashes": [
860
+ "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8",
861
+ "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"
862
+ ],
863
+ "markers": "python_version >= '3.7'",
864
+ "version": "==0.1.2"
865
+ },
850
866
  "mmh3": {
851
867
  "hashes": [
852
868
  "sha256:00f810647c22c179b6821079f7aa306d51953ac893587ee09cf1afb35adf87cb",
@@ -1393,6 +1409,22 @@
1393
1409
  ],
1394
1410
  "version": "==1.4.0"
1395
1411
  },
1412
+ "rich": {
1413
+ "hashes": [
1414
+ "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0",
1415
+ "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725"
1416
+ ],
1417
+ "markers": "python_full_version >= '3.8.0'",
1418
+ "version": "==14.0.0"
1419
+ },
1420
+ "rich-argparse": {
1421
+ "hashes": [
1422
+ "sha256:a8650b42e4a4ff72127837632fba6b7da40784842f08d7395eb67a9cbd7b4bf9",
1423
+ "sha256:d7a493cde94043e41ea68fb43a74405fa178de981bf7b800f7a3bd02ac5c27be"
1424
+ ],
1425
+ "markers": "python_version >= '3.8'",
1426
+ "version": "==1.7.1"
1427
+ },
1396
1428
  "rsa": {
1397
1429
  "hashes": [
1398
1430
  "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762",
@@ -1633,6 +1665,15 @@
1633
1665
  "markers": "python_version >= '3.9'",
1634
1666
  "version": "==2.5.0"
1635
1667
  },
1668
+ "validators": {
1669
+ "hashes": [
1670
+ "sha256:992d6c48a4e77c81f1b4daba10d16c3a9bb0dbb79b3a19ea847ff0928e70497a",
1671
+ "sha256:e8c947097eae7892cb3d26868d637f79f47b4a0554bc6b80065dfe5aac3705dd"
1672
+ ],
1673
+ "index": "pypi",
1674
+ "markers": "python_version >= '3.9'",
1675
+ "version": "==0.35.0"
1676
+ },
1636
1677
  "vine": {
1637
1678
  "hashes": [
1638
1679
  "sha256:40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc",
@@ -0,0 +1,499 @@
1
+ # Copyright Notice:
2
+ # Copyright 2016-2020 DMTF. All rights reserved.
3
+ # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Mockup-Creator/blob/main/LICENSE.md
4
+ # source: https://github.com/DMTF/Redfish-Mockup-Creator
5
+
6
+ """
7
+ Redfish Mockup Creator
8
+
9
+ File : redfishMockupCreate.py
10
+
11
+ Brief : This tool walks a service and creates a mockup from all resources
12
+ """
13
+
14
+ import argparse
15
+ import datetime
16
+ import json
17
+ import os
18
+ import redfish
19
+ import sys
20
+ import time
21
+ import xml.etree.ElementTree as ET
22
+ import logging
23
+ import copy
24
+ import gc
25
+ from redfish import redfish_logger
26
+
27
+ # Version info
28
+ tool_version = "1.2.0"
29
+
30
+ # For Windows, there are restricted characters in folder names that could be used in URIs
31
+ disallowed_folder_characters_win = [":", "*", "?", '"', "<", ">", "|"]
32
+ folder_name_fix = False
33
+ if sys.platform == "win32" or sys.platform == "cygwin":
34
+ folder_name_fix = True
35
+
36
+
37
+ def main():
38
+ """
39
+ Main entry point for the script
40
+ """
41
+
42
+ # Get the input arguments
43
+ argget = argparse.ArgumentParser(
44
+ description="A tool to walk a Redfish service and create a mockup from all resources"
45
+ )
46
+ argget.add_argument(
47
+ "--user", "-u", type=str, required=True, help="The user name for authentication"
48
+ )
49
+ argget.add_argument(
50
+ "--password",
51
+ "-p",
52
+ type=str,
53
+ required=True,
54
+ help="The password for authentication",
55
+ )
56
+ argget.add_argument(
57
+ "--rhost",
58
+ "-r",
59
+ type=str,
60
+ required=True,
61
+ help="The IP address (and port) of the Redfish service",
62
+ )
63
+ argget.add_argument(
64
+ "--Dir",
65
+ "-D",
66
+ type=str,
67
+ help="Output directory for the mockup; defaults to 'rfMockUpDfltDir'",
68
+ default="rfMockUpDfltDir",
69
+ )
70
+ argget.add_argument(
71
+ "--Secure", "-S", action="store_true", help="Use HTTPS for all operations"
72
+ )
73
+ argget.add_argument(
74
+ "--Auth",
75
+ "-A",
76
+ type=str,
77
+ help="Authentication mode",
78
+ choices=["None", "Basic", "Session"],
79
+ default="Session",
80
+ )
81
+ argget.add_argument(
82
+ "--Headers",
83
+ "-H",
84
+ action="store_true",
85
+ help="Captures the response headers in the mockup",
86
+ )
87
+ argget.add_argument(
88
+ "--Time",
89
+ "-T",
90
+ action="store_true",
91
+ help="Capture the time of each GET in the mockup",
92
+ )
93
+ argget.add_argument(
94
+ "--Copyright",
95
+ "-C",
96
+ type=str,
97
+ help="Copyright string to add to each resource",
98
+ default=None,
99
+ )
100
+ argget.add_argument(
101
+ "--description",
102
+ "-d",
103
+ type=str,
104
+ help="Mockup description to add to the output readme file",
105
+ default="",
106
+ )
107
+ argget.add_argument(
108
+ "--quiet",
109
+ "-q",
110
+ action="store_true",
111
+ help="Quiet mode; progress messages suppressed",
112
+ )
113
+ argget.add_argument(
114
+ "--trace",
115
+ "-trace",
116
+ action="store_true",
117
+ help="Enable tracing; creates the file rf-mockup-create.log in the output directory to capture Redfish traces with the service",
118
+ )
119
+ argget.add_argument(
120
+ "--maxlogentries",
121
+ "-maxlogentries",
122
+ type=int,
123
+ help="The maximum number of log entries to collect in each log service",
124
+ )
125
+ argget.add_argument(
126
+ "--forcefolderrename",
127
+ "-forcefolderrename",
128
+ action="store_true",
129
+ help="Indicates if URIs containing characters that are disallowed in Windows folder names are renamed to replace the characters with underscores",
130
+ )
131
+ args, unknown = argget.parse_known_args()
132
+
133
+ # Convert the authentication method to something usable with the Redfish library
134
+ # This is needed for backwards compatibility with older versions of the tool
135
+ if args.Auth == "Session":
136
+ args.Auth = "session"
137
+ else:
138
+ args.Auth = "basic"
139
+
140
+ # Build the base URL for the service
141
+ # More backwards compatibility
142
+ if "://" not in args.rhost:
143
+ if args.Secure:
144
+ args.rhost = "https://{}".format(args.rhost)
145
+ else:
146
+ args.rhost = "http://{}".format(args.rhost)
147
+
148
+ # Set up the output
149
+ if not os.path.isdir(args.Dir):
150
+ # Does not exist; make the directory
151
+ try:
152
+ os.makedirs(args.Dir)
153
+ except Exception as err:
154
+ print(
155
+ "ERROR: Aborting; could not create output directory '{}': {}".format(
156
+ args.Dir, err
157
+ )
158
+ )
159
+ sys.exit(1)
160
+ else:
161
+ if len(os.listdir(args.Dir)) != 0:
162
+ print("ERROR: Aborting; output directory not empty...")
163
+ sys.exit(1)
164
+
165
+ print("Redfish Mockup Creator, Version {}".format(tool_version))
166
+ print("Address: {}".format(args.rhost))
167
+ print("Full Output Path: {}".format(os.path.abspath(args.Dir)))
168
+ print("Description: {}".format(args.description))
169
+ print("Starting mockup creation...")
170
+ if args.quiet:
171
+ print("Quiet mode enabled; please wait...")
172
+
173
+ # Create the readme file
174
+ try:
175
+ with open(os.path.join(args.Dir, "README"), "w") as readf:
176
+ readf.write(
177
+ "Redfish service captured by the Redfish Mockup Creator, Version {}\n".format(
178
+ tool_version
179
+ )
180
+ )
181
+ readf.write(
182
+ "Created: {}\n".format(
183
+ datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
184
+ )
185
+ )
186
+ readf.write("Service: {}\n".format(args.rhost))
187
+ readf.write("User: {}\n".format(args.user))
188
+ readf.write("Description: {}\n".format(args.description))
189
+ except Exception as err:
190
+ print(
191
+ "ERROR: Aborting; could not create README file in output directory: {}".format(
192
+ err
193
+ )
194
+ )
195
+ sys.exit(1)
196
+
197
+ # Set up the trace file if requested
198
+ if args.trace:
199
+ redfish_logger(
200
+ os.path.join(args.Dir, "rf-mockup-create.log"),
201
+ "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
202
+ logging.DEBUG,
203
+ )
204
+
205
+ # Set up the Redfish object
206
+ try:
207
+ redfish_obj = redfish.redfish_client(
208
+ base_url=args.rhost, username=args.user, password=args.password
209
+ )
210
+ redfish_obj.login(auth=args.Auth)
211
+ except Exception as err:
212
+ print(
213
+ "ERROR: Aborting; could not authenticate with the Redfish service: {}".format(
214
+ err
215
+ )
216
+ )
217
+ sys.exit(1)
218
+
219
+ # Scan the service
220
+ response_times = {}
221
+ scan_resource(redfish_obj, args, response_times, "/redfish")
222
+ scan_resource(redfish_obj, args, response_times, "/redfish/v1/odata")
223
+ scan_resource(
224
+ redfish_obj, args, response_times, "/redfish/v1/$metadata", is_csdl=True
225
+ )
226
+ scan_resource(redfish_obj, args, response_times, "/redfish/v1")
227
+ redfish_obj.logout()
228
+
229
+ # Add time statistics to the readme
230
+ total_response_time = sum(response_times.values())
231
+ average_response_time = total_response_time / len(response_times)
232
+ min_response_uri = min(response_times, key=response_times.get)
233
+ max_response_uri = max(response_times, key=response_times.get)
234
+ with open(os.path.join(args.Dir, "README"), "a") as readf:
235
+ readf.write("Total response time: {}\n".format(total_response_time))
236
+ readf.write("Average response time: {}\n".format(average_response_time))
237
+ readf.write(
238
+ "Minimum response time: {}, {}\n".format(
239
+ response_times[min_response_uri], min_response_uri
240
+ )
241
+ )
242
+ readf.write(
243
+ "Maximum response time: {}, {}\n".format(
244
+ response_times[max_response_uri], max_response_uri
245
+ )
246
+ )
247
+
248
+ print("Completed mockup creation!")
249
+
250
+
251
+ def scan_resource(redfish_obj, args, response_times, uri, is_csdl=False):
252
+ """
253
+ Scans a resource and saves its response
254
+
255
+ Args:
256
+ redfish_obj: The Redfish client object with an open session
257
+ args: The command line arguments
258
+ response_times: The response times database
259
+ uri: The URI to get
260
+ is_csdl: Indicates if the resource is a CSDL file
261
+ """
262
+
263
+ # Check if the URI is a relative URI
264
+ if not uri.startswith("/"):
265
+ return
266
+
267
+ # Set up the output folder
268
+ try:
269
+ path = uri[1:]
270
+ if folder_name_fix or args.forcefolderrename:
271
+ for character in disallowed_folder_characters_win:
272
+ path = path.replace(character, "_")
273
+ path = os.path.join(args.Dir, path)
274
+ if not os.path.isdir(path):
275
+ # Does not exist; make the directory
276
+ os.makedirs(path)
277
+ except Exception as err:
278
+ print("ERROR: Could not create directory for '{}': {}".format(uri, err))
279
+ return
280
+
281
+ # Check if the index file already exists
282
+ index_name = "index.json"
283
+ if is_csdl:
284
+ index_name = "index.xml"
285
+ index_path = os.path.join(path, index_name)
286
+ if os.path.isfile(index_path):
287
+ # File exists; already scanned this resource
288
+ return
289
+
290
+ # Get the resource
291
+ if not args.quiet:
292
+ print("Getting {}...".format(uri))
293
+ try:
294
+ start_time = time.time()
295
+ resource = redfish_obj.get(uri, headers={"Accept-Encoding": "*"})
296
+ end_time = time.time()
297
+ except Exception as err:
298
+ print("ERROR: Could not get '{}': {}".format(uri, err))
299
+ return
300
+
301
+ # Save the resource and other information
302
+ try:
303
+ # Save the resource itself
304
+ if is_csdl:
305
+ with open(index_path, "w", encoding="utf-8") as file:
306
+ file.write(resource.text)
307
+ else:
308
+ save_dict = resource.dict
309
+
310
+ # Prune the log entry collection if needed
311
+ if (
312
+ save_dict.get("@odata.type", None)
313
+ == "#LogEntryCollection.LogEntryCollection"
314
+ and args.maxlogentries is not None
315
+ ):
316
+ if args.maxlogentries < 0:
317
+ args.maxlogentries = 0
318
+ if "Members@odata.nextLink" in save_dict:
319
+ save_dict.pop("Members@odata.nextLink")
320
+ if "Members" in save_dict:
321
+ if isinstance(save_dict["Members"], list):
322
+ for i in range(
323
+ 0, len(save_dict["Members"]) - args.maxlogentries
324
+ ):
325
+ save_dict["Members"].pop()
326
+ save_dict["Members@odata.count"] = len(save_dict["Members"])
327
+
328
+ # The saved copy might contain URI fixes and other changes that aren't reflective of the service, but are
329
+ # needed to ensure compatibility with the system creating the mockup
330
+ scan_dict = copy.deepcopy(save_dict)
331
+
332
+ # Add the copyright statement if needed
333
+ if args.Copyright:
334
+ save_dict["@Redfish.Copyright"] = args.Copyright
335
+
336
+ # Update the payload's URIs if they need to be corrected based on allowable folder names for the system
337
+ if folder_name_fix or args.forcefolderrename:
338
+ fix_uris(save_dict)
339
+
340
+ with open(index_path, "w", encoding="utf-8") as file:
341
+ json.dump(save_dict, file, indent=4, separators=(",", ": "))
342
+
343
+ # Deep copies of all payloads gets expensive; force garbage collection to avoid stack overflows
344
+ del save_dict
345
+ gc.collect()
346
+ except Exception as err:
347
+ print("ERROR: Could not save '{}': {}".format(uri, err))
348
+ print("Attempting to save response data in error.txt...")
349
+ try:
350
+ with open(os.path.join(path, "error.txt"), "w", encoding="utf-8") as file:
351
+ file.write("HTTP {}\n".format(resource.status))
352
+ for header in resource.getheaders():
353
+ file.write("{}: {}\n".format(header[0], header[1]))
354
+ file.write("\n")
355
+ file.write(resource.text)
356
+ except:
357
+ print("Could not save response data; moving on... ")
358
+ return
359
+
360
+ # Save additional info
361
+ try:
362
+ # Save headers
363
+ if args.Headers:
364
+ with open(
365
+ os.path.join(path, "headers.json"), "w", encoding="utf-8"
366
+ ) as file:
367
+ headers_dict = {}
368
+ for header in resource.getheaders():
369
+ headers_dict[header[0]] = header[1]
370
+ json.dump({"GET": headers_dict}, file, indent=4, separators=(",", ": "))
371
+
372
+ # Save timing info
373
+ response_times[uri] = end_time - start_time
374
+ if args.Time:
375
+ with open(os.path.join(path, "time.json"), "w", encoding="utf-8") as file:
376
+ json.dump(
377
+ {"GET_Time": "{0:.2f}".format(response_times[uri])},
378
+ file,
379
+ indent=4,
380
+ separators=(",", ": "),
381
+ )
382
+ except Exception as err:
383
+ print(
384
+ "ERROR: Could not save header or timing data for '{}': {}".format(uri, err)
385
+ )
386
+ return
387
+
388
+ # Scan the response to see where to go next
389
+ try:
390
+ if is_csdl:
391
+ scan_csdl(redfish_obj, args, response_times, resource.text)
392
+ else:
393
+ scan_object(redfish_obj, args, response_times, scan_dict)
394
+ except Exception as err:
395
+ print("ERROR: Could not scan '{}': {}".format(uri, err))
396
+ return
397
+
398
+
399
+ def scan_object(redfish_obj, args, response_times, object):
400
+ """
401
+ Scans an object or array to find links to other resources
402
+
403
+ Args:
404
+ redfish_obj: The Redfish client object with an open session
405
+ args: The command line arguments
406
+ response_times: The response times database
407
+ object: The object to scan
408
+ """
409
+
410
+ for item in object:
411
+ # If the object is a dictionary, inspect the properties found
412
+ if isinstance(object, dict):
413
+ # If the item is a reference, go to the resource
414
+ if (
415
+ item == "@odata.id"
416
+ or item == "Uri"
417
+ or item == "Members@odata.nextLink"
418
+ or item == "@Redfish.ActionInfo"
419
+ ):
420
+ if isinstance(object[item], str):
421
+ if object[item].startswith("/") and "#" not in object[item]:
422
+ scan_resource(redfish_obj, args, response_times, object[item])
423
+
424
+ # If the item is an object or array, scan one level deeper
425
+ elif isinstance(object[item], dict) or isinstance(object[item], list):
426
+ scan_object(redfish_obj, args, response_times, object[item])
427
+
428
+ # If the object is a list, see if the member needs to be scanned
429
+ elif isinstance(object, list):
430
+ if isinstance(item, dict) or isinstance(item, list):
431
+ scan_object(redfish_obj, args, response_times, item)
432
+
433
+
434
+ def scan_csdl(redfish_obj, args, response_times, csdl):
435
+ """
436
+ Scans a CSDL string to find links to other CSDL files
437
+
438
+ Args:
439
+ redfish_obj: The Redfish client object with an open session
440
+ args: The command line arguments
441
+ response_times: The response times database
442
+ csdl: The CSDL string to scan
443
+ """
444
+
445
+ # Convert to an element tree object
446
+ tree = ET.ElementTree(ET.fromstring(csdl))
447
+ root = tree.getroot()
448
+
449
+ # Find references
450
+ for reference in root:
451
+ if "reference" in str(reference.tag).lower():
452
+ if "Reference" not in str(reference.tag):
453
+ print(
454
+ "Warning: Found invalid reference tag '{}'; tags are case sensitive!".format(
455
+ str(reference.tag)
456
+ )
457
+ )
458
+ for tag in ["Uri", "uri", "URI"]:
459
+ uri = reference.attrib.get(tag)
460
+ if uri is not None:
461
+ if tag != "Uri":
462
+ print(
463
+ "Warning: Found invalid Uri attribute '{}'; attributes are case sensitive!".format(
464
+ tag
465
+ )
466
+ )
467
+ # Scan the reference
468
+ scan_resource(redfish_obj, args, response_times, uri, is_csdl=True)
469
+
470
+
471
+ def fix_uris(payload):
472
+ """
473
+ Updates URIs in a payload to ensure they do not conflict with local system folder name rules
474
+
475
+ Args:
476
+ payload: The payload to update
477
+ """
478
+
479
+ for item in payload:
480
+ # If the payload is a dictionary, inspect the properties found
481
+ if isinstance(payload, dict):
482
+ # If the item is a reference, go to the resource
483
+ if item == "@odata.id" or item == "Uri" or item == "Members@odata.nextLink":
484
+ if isinstance(payload[item], str):
485
+ for character in disallowed_folder_characters_win:
486
+ payload[item] = payload[item].replace(character, "_")
487
+
488
+ # If the item is an object or array, scan one level deeper
489
+ elif isinstance(payload[item], dict) or isinstance(payload[item], list):
490
+ fix_uris(payload[item])
491
+
492
+ # If the object is a list, see if the member needs to be scanned
493
+ elif isinstance(payload, list):
494
+ if isinstance(item, dict) or isinstance(item, list):
495
+ fix_uris(item)
496
+
497
+
498
+ if __name__ == "__main__":
499
+ sys.exit(main())