Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-pyghmi for openSUSE:Factory 
checked in at 2025-12-29 15:16:35
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pyghmi (Old)
 and      /work/SRC/openSUSE:Factory/.python-pyghmi.new.1928 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-pyghmi"

Mon Dec 29 15:16:35 2025 rev:25 rq:1324589 version:1.6.11

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pyghmi/python-pyghmi.changes      
2025-11-10 19:18:32.731375936 +0100
+++ /work/SRC/openSUSE:Factory/.python-pyghmi.new.1928/python-pyghmi.changes    
2025-12-29 15:17:16.740181049 +0100
@@ -1,0 +2,17 @@
+Sun Dec 28 19:43:20 UTC 2025 - Dirk Müller <[email protected]>
+
+- update to 1.6.11:
+  * Remove six dependency
+  * Bail if number of max RDOC already met
+  * Change multipart boundary
+  * Avoid media upload to duplicate target
+  * Add AMI to oem modules
+  * Handle systems that model portions as distinct 'Systems'
+    members
+  * Add recognition of AMI convention of 'Self'
+  * Filter out UEFI boot certificates for BMC certificate
+    candidates
+  * Manage BMC certificates
+  * Handle missing dependencies in a redfish dependency map
+
+-------------------------------------------------------------------

Old:
----
  pyghmi-1.6.6.tar.gz

New:
----
  pyghmi-1.6.11.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-pyghmi.spec ++++++
--- /var/tmp/diff_new_pack.cZ43xu/_old  2025-12-29 15:17:17.768223275 +0100
+++ /var/tmp/diff_new_pack.cZ43xu/_new  2025-12-29 15:17:17.772223439 +0100
@@ -17,7 +17,7 @@
 
 
 Name:           python-pyghmi
-Version:        1.6.6
+Version:        1.6.11
 Release:        0
 Summary:        General Hardware Management Initiative (IPMI and others)
 License:        Apache-2.0
@@ -31,11 +31,9 @@
 BuildRequires:  %{python_module pip}
 BuildRequires:  %{python_module pytest}
 BuildRequires:  %{python_module python-dateutil >= 2.8.1}
-BuildRequires:  %{python_module six}
 BuildRequires:  %{python_module wheel}
 Requires:       python-cryptography >= 2.1
 Requires:       python-python-dateutil >= 2.8.1
-Requires:       python-six
 BuildArch:      noarch
 %if "python%{python_nodots_ver}" == "%{primary_python}"
 Obsoletes:      python3-pyghmi < %{version}

++++++ pyghmi-1.6.6.tar.gz -> pyghmi-1.6.11.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.6.6/ChangeLog new/pyghmi-1.6.11/ChangeLog
--- old/pyghmi-1.6.6/ChangeLog  2025-10-15 16:51:51.000000000 +0200
+++ new/pyghmi-1.6.11/ChangeLog 2025-12-10 20:10:59.000000000 +0100
@@ -1,6 +1,36 @@
 CHANGES
 =======
 
+1.6.11
+------
+
+* Remove six dependency
+
+1.6.10
+------
+
+* Bail if number of max RDOC already met
+* Change multipart boundary
+
+1.6.9
+-----
+
+* Avoid media upload to duplicate target
+
+1.6.8
+-----
+
+* Add AMI to oem modules
+* Handle systems that model portions as distinct 'Systems' members
+* Add recognition of AMI convention of 'Self'
+
+1.6.7
+-----
+
+* Filter out UEFI boot certificates for BMC certificate candidates
+* Manage BMC certificates
+* Handle missing dependencies in a redfish dependency map
+
 1.6.6
 -----
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.6.6/PKG-INFO new/pyghmi-1.6.11/PKG-INFO
--- old/pyghmi-1.6.6/PKG-INFO   2025-10-15 16:51:52.061963000 +0200
+++ new/pyghmi-1.6.11/PKG-INFO  2025-12-10 20:11:00.127628300 +0100
@@ -1,6 +1,6 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
 Name: pyghmi
-Version: 1.6.6
+Version: 1.6.11
 Summary: Python General Hardware Management Initiative (IPMI and others)
 Home-page: http://github.com/openstack/pyghmi/
 Author: Jarrod Johnson
@@ -20,7 +20,15 @@
 License-File: LICENSE
 Requires-Dist: cryptography>=2.1
 Requires-Dist: python-dateutil>=2.8.1
-Requires-Dist: six>=1.10.0
+Dynamic: author
+Dynamic: author-email
+Dynamic: classifier
+Dynamic: description
+Dynamic: home-page
+Dynamic: license
+Dynamic: license-file
+Dynamic: requires-dist
+Dynamic: summary
 
 This is a pure python implementation of IPMI protocol.
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.6.6/pyghmi/cmd/pyghmicons.py 
new/pyghmi-1.6.11/pyghmi/cmd/pyghmicons.py
--- old/pyghmi-1.6.6/pyghmi/cmd/pyghmicons.py   2025-10-15 16:51:03.000000000 
+0200
+++ new/pyghmi-1.6.11/pyghmi/cmd/pyghmicons.py  2025-12-10 20:10:03.000000000 
+0100
@@ -22,8 +22,6 @@
 import threading
 import tty
 
-import six
-
 from pyghmi.ipmi import console
 
 
@@ -42,7 +40,7 @@
 
 def _print(data):
     bailout = False
-    if not isinstance(data, six.string_types):
+    if not isinstance(data, str):
         bailout = True
         data = repr(data)
     sys.stdout.write(data)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.6.6/pyghmi/ipmi/oem/lenovo/config.py 
new/pyghmi-1.6.11/pyghmi/ipmi/oem/lenovo/config.py
--- old/pyghmi-1.6.6/pyghmi/ipmi/oem/lenovo/config.py   2025-10-15 
16:51:03.000000000 +0200
+++ new/pyghmi-1.6.11/pyghmi/ipmi/oem/lenovo/config.py  2025-12-10 
20:10:03.000000000 +0100
@@ -22,7 +22,6 @@
 import random
 import struct
 
-import six
 import time
 
 import pyghmi.exceptions as pygexc
@@ -564,7 +563,7 @@
                 continue
             if options[option]['pending'] == options[option]['new_value']:
                 continue
-            if isinstance(options[option]['new_value'], six.string_types):
+            if isinstance(options[option]['new_value'], str):
                 # Coerce a simple string parameter to the expected list format
                 options[option]['new_value'] = [options[option]['new_value']]
             options[option]['pending'] = options[option]['new_value']
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.6.6/pyghmi/ipmi/oem/lenovo/imm.py 
new/pyghmi-1.6.11/pyghmi/ipmi/oem/lenovo/imm.py
--- old/pyghmi-1.6.6/pyghmi/ipmi/oem/lenovo/imm.py      2025-10-15 
16:51:03.000000000 +0200
+++ new/pyghmi-1.6.11/pyghmi/ipmi/oem/lenovo/imm.py     2025-12-10 
20:10:03.000000000 +0100
@@ -27,7 +27,6 @@
 import struct
 import weakref
 
-import six
 import zipfile
 
 import pyghmi.constants as pygconst
@@ -281,7 +280,7 @@
 
     def merge_changeset(self, changeset):
         for key in changeset:
-            if isinstance(changeset[key], six.string_types):
+            if isinstance(changeset[key], str):
                 changeset[key] = {'value': changeset[key]}
             newvalue = changeset[key]['value']
             if self.fwo[key]['is_list'] and not isinstance(newvalue, list):
@@ -1124,7 +1123,7 @@
         ruleset = {}
         usbsettings = {}
         for key in changeset:
-            if isinstance(changeset[key], six.string_types):
+            if isinstance(changeset[key], str):
                 changeset[key] = {'value': changeset[key]}
             currval = changeset[key].get('value', None)
             if 'smm'.startswith(key.lower()):
@@ -1966,15 +1965,29 @@
                     url = url.replace(':', '')
                     url = 'nfs://' + url
                 yield media.Media(mt['filename'], url)
+        for rdoc in self._list_rdoc():
+            yield rdoc
+        self.weblogout()
+
+    
+    def _list_rdoc(self):
         rt = self.wc.grab_json_response('/api/providers/rp_rdoc_imagelist')
         if 'items' in rt:
             for mt in rt['items']:
                 yield media.Media(mt['filename'])
-        self.weblogout()
 
     def upload_media(self, filename, progress=None, data=None):
         wc = self.wc
         self._refresh_token()
+        numrdocs = 0
+        for rdoc in self._list_rdoc():
+            numrdocs += 1
+            if rdoc.name == os.path.basename(filename):
+                raise pygexc.InvalidParameterValue(
+                    'An image with that name already exists')
+        if numrdocs >= 2:
+            raise pygexc.InvalidParameterValue(
+                'Maximum number of uploaded media reached')
         rsp, statu = wc.grab_json_response_with_status('/rdocupload')
         newmode = False
         if statu == 404:
@@ -1999,10 +2012,16 @@
                     progress({'phase': 'upload',
                             'progress': 100.0 * rsp['received'] / rsp['size']})
             self._refresh_token()
-        rsp = json.loads(uploadthread.rsp)
+        if uploadthread.rsp:
+            rsp = json.loads(uploadthread.rsp)
+        else:
+            rsp = {}
         if progress:
             progress({'phase': 'upload',
                       'progress': 100.0})
+        if 'items' not in rsp or len(rsp['items']) == 0:
+            errmsg = repr(rsp) if rsp else self.wc.lastjsonerror if 
self.wc.lastjsonerror else repr(uploadthread.rspstatus)
+            raise pygexc.PyghmiException('Failed to upload image: ' + errmsg)
         thepath = rsp['items'][0]['path']
         thename = rsp['items'][0]['name']
         writeable = 1 if filename.lower().endswith('.img') else 0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.6.6/pyghmi/ipmi/oem/lenovo/nextscale.py 
new/pyghmi-1.6.11/pyghmi/ipmi/oem/lenovo/nextscale.py
--- old/pyghmi-1.6.6/pyghmi/ipmi/oem/lenovo/nextscale.py        2025-10-15 
16:51:03.000000000 +0200
+++ new/pyghmi-1.6.11/pyghmi/ipmi/oem/lenovo/nextscale.py       2025-12-10 
20:10:03.000000000 +0100
@@ -18,8 +18,6 @@
 from xml.etree.ElementTree import fromstring as rfromstring
 import zipfile
 
-import six
-
 import pyghmi.constants as pygconst
 import pyghmi.exceptions as pygexc
 import pyghmi.ipmi.private.session as ipmisession
@@ -641,7 +639,7 @@
         for key in changeset:
             if not key:
                 raise pygexc.InvalidParameterValue('Empty key is invalid')
-            if isinstance(changeset[key], six.string_types):
+            if isinstance(changeset[key], str):
                 changeset[key] = {'value': changeset[key]}
             for rule in self.rulemap:
                 if fnmatch.fnmatch(rule, key.lower()):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.6.6/pyghmi/ipmi/sdr.py 
new/pyghmi-1.6.11/pyghmi/ipmi/sdr.py
--- old/pyghmi-1.6.6/pyghmi/ipmi/sdr.py 2025-10-15 16:51:03.000000000 +0200
+++ new/pyghmi-1.6.11/pyghmi/ipmi/sdr.py        2025-12-10 20:10:03.000000000 
+0100
@@ -36,8 +36,6 @@
 import struct
 import weakref
 
-import six
-
 import pyghmi.constants as const
 import pyghmi.exceptions as exc
 
@@ -621,8 +619,7 @@
             return ""
         if ipmitype == 0:  # Unicode per 43.15 in ipmi 2.0 spec
             # the spec is not specific about encoding, assuming utf8
-            return six.text_type(struct.pack("%dB" % len(data), *data),
-                                 "utf_8")
+            return struct.pack("%dB" % len(data), *data).decode("utf-8")
         elif ipmitype == 1:  # BCD '+'
             tmpl = "%02X" * len(data)
             tstr = tmpl % tuple(data)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.6.6/pyghmi/redfish/command.py 
new/pyghmi-1.6.11/pyghmi/redfish/command.py
--- old/pyghmi-1.6.6/pyghmi/redfish/command.py  2025-10-15 16:51:03.000000000 
+0200
+++ new/pyghmi-1.6.11/pyghmi/redfish/command.py 2025-12-10 20:10:03.000000000 
+0100
@@ -188,6 +188,7 @@
         self._varsensormap = {}
         self.powerurl = None
         self.sysurl = None
+        self._initsysurl = sysurl
         tmpoem = oem.get_oem_handler({}, sysurl, self.wc, self._urlcache, self,
                                     rootinfo=overview)
         self._varbmcurl = tmpoem.get_default_mgrurl()
@@ -243,6 +244,14 @@
         for ca in self.oem.get_trusted_cas():
             yield ca
     
+    def get_bmc_csr(self, keytype=None, keylength=None, cn=None, city=None,
+                    state=None, country=None, org=None, orgunit=None):
+        return self.oem.get_bmc_csr(
+            keytype=keytype, keylength=keylength, cn=cn)
+
+    def install_bmc_certificate(self, certdata):
+        return self.oem.install_bmc_certificate(certdata)
+
     def add_trusted_ca(self, pemdata):
         return self.oem.add_trusted_ca(pemdata)
     
@@ -1220,7 +1229,7 @@
             elif self._varbmcurl:
                 self._do_web_request(self._varbmcurl, cache=False)  # This is 
to trigger token validation and renewel
             self._oem = oem.get_oem_handler(
-                self.sysinfo, self.sysurl, self.wc, self._urlcache, self)
+                self.sysinfo, self._initsysurl, self.wc, self._urlcache, self)
             self._oem.set_credentials(self.username, self.password)
         return self._oem
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.6.6/pyghmi/redfish/oem/ami/main.py 
new/pyghmi-1.6.11/pyghmi/redfish/oem/ami/main.py
--- old/pyghmi-1.6.6/pyghmi/redfish/oem/ami/main.py     1970-01-01 
01:00:00.000000000 +0100
+++ new/pyghmi-1.6.11/pyghmi/redfish/oem/ami/main.py    2025-12-10 
20:10:03.000000000 +0100
@@ -0,0 +1,20 @@
+# Copyright 2025 Lenovo Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import pyghmi.redfish.oem.ami.megarac as megarac
+
+
+def get_handler(sysinfo, sysurl, webclient, cache, cmd, rootinfo={}):
+    return megarac.OEMHandler(sysinfo, sysurl, webclient, cache,
+                            gpool=cmd._gpool)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.6.6/pyghmi/redfish/oem/ami/megarac.py 
new/pyghmi-1.6.11/pyghmi/redfish/oem/ami/megarac.py
--- old/pyghmi-1.6.6/pyghmi/redfish/oem/ami/megarac.py  1970-01-01 
01:00:00.000000000 +0100
+++ new/pyghmi-1.6.11/pyghmi/redfish/oem/ami/megarac.py 2025-12-10 
20:10:03.000000000 +0100
@@ -0,0 +1,30 @@
+# Copyright 2025 Lenovo Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import pyghmi.redfish.oem.generic as generic
+
+
+class OEMHandler(generic.OEMHandler):
+
+    def __init__(self, sysinfo, sysurl, webclient, cache, gpool=None):
+        super(OEMHandler, self).__init__(sysinfo, sysurl, webclient, cache,
+                                         gpool)
+        if sysurl is None:
+            systems, status = 
webclient.grab_json_response_with_status('/redfish/v1/Systems')
+            if status == 200:
+                for system in systems.get('Members', []):
+                    if system.get('@odata.id', '').endswith('/Self'):
+                        sysurl = system['@odata.id']
+                        break
+            self._varsysurl = sysurl
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.6.6/pyghmi/redfish/oem/generic.py 
new/pyghmi-1.6.11/pyghmi/redfish/oem/generic.py
--- old/pyghmi-1.6.6/pyghmi/redfish/oem/generic.py      2025-10-15 
16:51:03.000000000 +0200
+++ new/pyghmi-1.6.11/pyghmi/redfish/oem/generic.py     2025-12-10 
20:10:03.000000000 +0100
@@ -167,8 +167,11 @@
                 if currprop == 'CurrentValue':
                     if currattr in self.pend:
                         currval = self.pend[currattr]
-                    else:
+                    elif currattr in self.curr:
                         currval = self.curr[currattr]
+                    else:
+                        break  # The cited dependency attribute is missing, 
can't enforce
+                               # requested override
                 else:
                     currval = self.reg[currattr][currprop]
                 lastcond = self.process(currval, mapfrom, lastcond, lastoper)
@@ -232,6 +235,18 @@
                 '/redfish/v1/')
         self._varbmcurl = None
         self._varsysurl = sysurl
+        self._allsysurls = []
+        if sysurl is None:  # generic means we need to gather all systems
+            if 'Systems' in self._rootinfo:
+                systems = self._rootinfo['Systems']['@odata.id']
+                res = self.webclient.grab_json_response_with_status(systems)
+                if res[1] == 200:
+                    members = res[0]['Members']
+                    for system in members:
+                        if system['@odata.id'] != sysurl:
+                            self._allsysurls.append(system['@odata.id'])
+        else:
+            self._allsysurls = [sysurl]
 
     def get_screenshot(self, outfile):
         raise exc.UnsupportedFunctionality(
@@ -327,6 +342,152 @@
     def sysinfo(self):
         return self._do_web_request(self._varsysurl)
 
+    def get_bmc_csr(self, keytype=None, keylength=None, cn=None, city=None,
+                    state=None, country=None, org=None, orgunit=None):
+        # A fun time here, the redfish specification is weird about this.
+        # We have a certificateservice, sounds good, and an action to generate 
a CSR,
+        # straightforward enough, but you have to indicate a certificate 
collection...
+        # We get a list of locations, so we have to infer the collection, which
+        # is perhaps odd, but a relatively safe bet.
+        # However, the purpose of the certificates is opaque, so we can only 
guess
+        # based on strings in the url if there is ambiguity.
+        rootinfo = self._do_web_request('/redfish/v1/')
+        certserviceurl = rootinfo.get('CertificateService', 
{}).get('@odata.id', None)
+        if not certserviceurl:
+            raise exc.PyghmiException('No CertificateService found on 
platform')
+        certservice = self._do_web_request(certserviceurl)
+        gencsractinfo = certservice.get('Actions', 
{}).get("#CertificateService.GenerateCSR", {})
+        curveids = gencsractinfo.get('[email protected]', [])
+        keylens = gencsractinfo.get('[email protected]', [])
+        keypairalgorithms = 
gencsractinfo.get('[email protected]', [])
+        selectedcurve = None
+        selectedkeylen = None
+        selectedkpa = None
+        if not keytype:
+            for kpa in keypairalgorithms:
+                if 'ECDH' in kpa:
+                    keytype = 'ECC'
+                    selectedkpa = kpa
+                    break
+                if 'RSA' in kpa:
+                    selectedkpa = kpa
+                    keytype = 'RSA'
+        if not keytype:
+            raise exc.PyghmiException('No valid key type found for CSR 
generation')
+        if keytype.upper() in ('ECC', 'ECDSA'):
+            if not curveids:
+                raise exc.PyghmiException('No valid curves found for ECC/ECDSA 
key type')
+            if keylength:
+                for curve in curveids:
+                    if fnmatch(curve, '*{0}'.format(keylength)):
+                        selectedcurve = curve
+                        break
+            else:
+                selectedcurve = curveids[-1]
+        elif keytype.upper() == 'RSA':
+            if not keylens:
+                raise exc.PyghmiException('No valid key lengths found for RSA 
key type')
+            if keylength:
+                allkeylens = []
+                for klp in keylens:
+                    if isinstance(klp, int):
+                        allkeylens.append(klp)
+                        continue
+                    for kl in klp.split(':'):
+                        allkeylens.append(int(kl))
+                if keylength not in allkeylens:
+                    raise exc.PyghmiException('Requested key length {0} not 
supported'.format(keylength))
+                selectedkeylen = keylength
+        gencsrtarg = gencsractinfo.get('target', None)
+        certcoll = self.get_certificate_collection(certservice)
+        payload = {
+            'CertificateCollection': {"@odata.id": certcoll},
+            'City': city or 'Unspecified',
+            'CommonName': cn or self.webclient.thehost,
+            'Country': country or 'AQ',  # Need *a* valid two letter country 
code, Antarctica is more equally likely to be wrong than most.
+            'Organization': org or 'Unspecified',
+            'State': state or 'Unspecified',
+        }
+        if orgunit:
+            payload['OrganizationalUnit'] = orgunit
+        if selectedcurve:
+            payload['KeyCurveId'] = selectedcurve
+        elif selectedkeylen:
+            payload['KeyLength'] = selectedkeylen
+        if selectedkpa:
+            payload['KeyPairAlgorithm'] = selectedkpa
+        rsp = self._do_web_request(gencsrtarg, payload)
+        csr = rsp.get('CSRString', None)
+        return csr
+
+    def get_certificate_collection(self, certservice):
+        certcollections = set([])
+        certlocs = certservice.get('CertificateLocations', 
{}).get('@odata.id', None)
+        if certlocs:
+            certlocdata = self._do_web_request(certlocs)
+            for cert in certlocdata.get('Links', {}).get('Certificates', []):
+                certurl = cert.get('@odata.id', None)
+                if not certurl:
+                    continue
+                # we need to remove the last part of url to get collection
+                collurl = '/'.join(certurl.split('/')[:-1])
+                certcollections.add(collurl)
+        if len(certcollections) == 0:
+            raise exc.PyghmiException('No certificate collections found for 
certificate operation')
+        if len(certcollections) > 1:
+            for candcoll in list(certcollections):
+                if 'TrustedCertificates' in candcoll:  # likely a CA store
+                    certcollections.discard(candcoll)
+                elif 'LDAP' in candcoll:  # certificate for LDAP server
+                    certcollections.discard(candcoll)
+                elif 'KMIP' in candcoll:  # not for TLS
+                    certcollections.discard(candcoll)
+                elif 'Boot/Certificates' in candcoll:
+                    certcollections.discard(candcoll)
+        if len(certcollections) > 1:
+            raise exc.PyghmiException('Multiple certificate collections found, 
unable to infer intended target for certificate operation')
+        certcoll = list(certcollections)[0]
+        return certcoll
+
+    def install_bmc_certificate(self, certdata):
+        rootinfo = self._do_web_request('/redfish/v1/')
+        certserviceurl = rootinfo.get('CertificateService', 
{}).get('@odata.id', None)
+        if not certserviceurl:
+            raise exc.PyghmiException('No CertificateService found on 
platform')
+        certservice = self._do_web_request(certserviceurl)
+        certlocs = certservice.get('CertificateLocations', 
{}).get('@odata.id', None)
+        if not certlocs:
+            raise exc.PyghmiException('No CertificateLocations found on 
platform')
+        certlocdata = self._do_web_request(certlocs)
+        allcerts = set([])
+        for certloc in certlocdata.get('Links', {}).get('Certificates', []):
+            certurl = certloc.get('@odata.id', None)
+            if not certurl:
+                continue
+            allcerts.add(certurl)
+        if len(allcerts) == 0:
+            raise exc.PyghmiException('No Certificates found on platform')
+        elif len(allcerts) > 1:
+            # try to narrow down to server cert
+            for certurl in list(allcerts):
+                if 'TrustedCertificates' in certurl:
+                    allcerts.discard(certurl)
+                elif 'LDAP' in certurl:
+                    allcerts.discard(certurl)
+                elif 'KMIP' in certurl:
+                    allcerts.discard(certurl)
+                elif 'Boot/Certificates' in certurl:
+                    allcerts.discard(certurl)
+        if len(allcerts) > 1:
+            raise exc.PyghmiException('Multiple Certificates found, unable to 
infer intended target for certificate installation')
+        targcerturl = list(allcerts)[0]
+        replacecerturl = certservice.get('Actions', {}).get(
+            '#CertificateService.ReplaceCertificate', {}).get('target', None)
+        certpayload = _pem_to_dict(certdata)
+        certpayload['CertificateUri'] = {'@odata.id': targcerturl}
+        
#/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate
+        self._do_web_request(replacecerturl, certpayload)
+
     def add_trusted_ca(self, pemdata):
         mgrinfo = self._do_web_request(self._bmcurl)
         secpolicy = mgrinfo.get('SecurityPolicy', {}).get('@odata.id', None)
@@ -507,7 +668,7 @@
             if memsumstatus != 'OK':
                 dimmfound = False
                 dimmdata = self._get_mem_data()
-                for dimminfo in dimmdata['Members']:
+                for dimminfo in dimmdata:
                     if dimminfo.get('Status', {}).get(
                             'State', None) == 'Absent':
                         continue
@@ -1091,8 +1252,7 @@
         return urls
 
     def _get_cpu_inventory(self, onlynames=False, withids=False, urls=None):
-        for currcpuinfo in self._get_cpu_data().get(
-                'Members', []):
+        for currcpuinfo in self._get_cpu_data():
             url = currcpuinfo['@odata.id']
             name = currcpuinfo.get('Name', 'CPU')
             if name in self._hwnamemap:
@@ -1119,18 +1279,21 @@
 
     def _get_cpu_urls(self):
         md = self._get_cpu_data(False)
-        return [x['@odata.id'] for x in md.get('Members', [])]
+        return [x['@odata.id'] for x in md]
 
     def _get_cpu_data(self, expand='.'):
-        cpurl = self._varsysinfo.get('Processors', {}).get('@odata.id', None)
-        if not cpurl:
-            return {}
-        return self._get_expanded_data(cpurl, expand)
-
+        cpumembers = []
+        for sysurl in self._allsysurls:
+            currsysdata = self._do_web_request(sysurl)
+            currcpuurl = currsysdata.get('Processors', {}).get('@odata.id', 
None)
+            if currcpuurl:
+                currcpudata = self._get_expanded_data(currcpuurl, expand)
+                cpumembers.extend(currcpudata.get('Members', []))
+        return cpumembers
 
     def _get_mem_inventory(self, onlyname=False, withids=False, urls=None):
         memdata = self._get_mem_data()
-        for currmeminfo in memdata.get('Members', []): # 
self._do_bulk_requests(urls):
+        for currmeminfo in memdata:
             url = currmeminfo['@odata.id']
             name = currmeminfo.get('Name', 'Memory')
             if name in self._hwnamemap:
@@ -1161,13 +1324,17 @@
 
     def _get_mem_urls(self):
         md = self._get_mem_data(False)
-        return [x['@odata.id'] for x in md.get('Members', [])]
+        return [x['@odata.id'] for x in md]
 
     def _get_mem_data(self, expand='.'):
-        memurl = self._varsysinfo.get('Memory', {}).get('@odata.id', None)
-        if not memurl:
-            return {}
-        return self._get_expanded_data(memurl, expand)
+        memmembers = []
+        for sysurl in self._allsysurls:
+            currsysdata = self._do_web_request(sysurl)
+            currmemurl = currsysdata.get('Memory', {}).get('@odata.id', None)
+            if currmemurl:
+                currmemdata = self._get_expanded_data(currmemurl, expand)
+                memmembers.extend(currmemdata.get('Members', []))
+        return memmembers
 
     def _get_expanded_data(self, url, expand='.'):
         topdata = []
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.6.6/pyghmi/redfish/oem/lenovo/tsma.py 
new/pyghmi-1.6.11/pyghmi/redfish/oem/lenovo/tsma.py
--- old/pyghmi-1.6.6/pyghmi/redfish/oem/lenovo/tsma.py  2025-10-15 
16:51:03.000000000 +0200
+++ new/pyghmi-1.6.11/pyghmi/redfish/oem/lenovo/tsma.py 2025-12-10 
20:10:03.000000000 +0100
@@ -20,8 +20,6 @@
 except ImportError:
     from urllib.parse import urlencode
 
-import six
-
 import pyghmi.exceptions as exc
 import pyghmi.media as media
 import pyghmi.redfish.oem.generic as generic
@@ -171,7 +169,7 @@
         dnschgs = {}
         wc = self.wc
         for key in changeset:
-            if isinstance(changeset[key], six.string_types):
+            if isinstance(changeset[key], str):
                 changeset[key] = {'value': changeset[key]}
             currval = changeset[key].get('value', None)
             if 'dns_servers'.startswith(key.lower()):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.6.6/pyghmi/redfish/oem/lenovo/xcc.py 
new/pyghmi-1.6.11/pyghmi/redfish/oem/lenovo/xcc.py
--- old/pyghmi-1.6.6/pyghmi/redfish/oem/lenovo/xcc.py   2025-10-15 
16:51:03.000000000 +0200
+++ new/pyghmi-1.6.11/pyghmi/redfish/oem/lenovo/xcc.py  2025-12-10 
20:10:03.000000000 +0100
@@ -23,7 +23,6 @@
 import socket
 import time
 
-import six
 import zipfile
 
 import pyghmi.constants as pygconst
@@ -261,7 +260,7 @@
 
     def merge_changeset(self, changeset):
         for key in changeset:
-            if isinstance(changeset[key], six.string_types):
+            if isinstance(changeset[key], str):
                 changeset[key] = {'value': changeset[key]}
             newvalue = changeset[key]['value']
             if self.fwo[key]['is_list'] and not isinstance(newvalue, list):
@@ -416,7 +415,7 @@
         usbsettings = {}
         secparms = {}
         for key in changeset:
-            if isinstance(changeset[key], six.string_types):
+            if isinstance(changeset[key], str):
                 changeset[key] = {'value': changeset[key]}
             currval = changeset[key].get('value', None)
             if key.lower() in self.rulemap:
@@ -1187,6 +1186,10 @@
                     url = url.replace(':', '')
                     url = 'nfs://' + url
                 yield media.Media(mt['filename'], url)
+        for rdoc in self._list_rdoc():
+            yield rdoc
+    
+    def _list_rdoc(self):
         rt = self.wc.grab_json_response('/api/providers/rp_rdoc_imagelist')
         if 'items' in rt:
             for mt in rt['items']:
@@ -1210,6 +1213,15 @@
     def upload_media(self, filename, progress=None, data=None):
         wc = self.wc
         self._refresh_token()
+        numrdocs = 0
+        for rdoc in self._list_rdoc():
+            numrdocs += 1
+            if rdoc.name == os.path.basename(filename):
+                raise pygexc.InvalidParameterValue(
+                    'An image with that name already exists')
+        if numrdocs >= 2:
+            raise pygexc.InvalidParameterValue(
+                'Maximum number of uploaded media reached')
         rsp, statu = wc.grab_json_response_with_status('/rdocupload')
         newmode = False
         if statu == 404:
@@ -1234,10 +1246,16 @@
                     progress({'phase': 'upload',
                             'progress': 100.0 * rsp['received'] / rsp['size']})
             self._refresh_token()
-        rsp = json.loads(uploadthread.rsp)
+        if uploadthread.rsp:
+            rsp = json.loads(uploadthread.rsp)
+        else:
+            rsp = {}
         if progress:
             progress({'phase': 'upload',
                       'progress': 100.0})
+        if 'items' not in rsp or len(rsp['items']) == 0:
+            errmsg = repr(rsp) if rsp else self.wc.lastjsonerror if 
self.wc.lastjsonerror else repr(uploadthread.rspstatus)
+            raise pygexc.PyghmiException('Failed to upload image: ' + errmsg)
         thepath = rsp['items'][0]['path']
         thename = rsp['items'][0]['name']
         writeable = 1 if filename.lower().endswith('.img') else 0
@@ -1249,7 +1267,7 @@
         self._refresh_token()
         if rsp.get('return', -1) != 0:
             errmsg = repr(rsp) if rsp else self.wc.lastjsonerror
-            raise Exception('Unrecognized return: ' + errmsg)
+            raise pygexc.PyghmiException('Failed to upload image: ' + errmsg)
         ready = False
         while not ready:
             time.sleep(3)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.6.6/pyghmi/redfish/oem/lookup.py 
new/pyghmi-1.6.11/pyghmi/redfish/oem/lookup.py
--- old/pyghmi-1.6.6/pyghmi/redfish/oem/lookup.py       2025-10-15 
16:51:03.000000000 +0200
+++ new/pyghmi-1.6.11/pyghmi/redfish/oem/lookup.py      2025-12-10 
20:10:03.000000000 +0100
@@ -15,10 +15,13 @@
 import pyghmi.redfish.oem.dell.main as dell
 import pyghmi.redfish.oem.generic as generic
 import pyghmi.redfish.oem.lenovo.main as lenovo
+import pyghmi.redfish.oem.ami.main as ami
 
 OEMMAP = {
     'Lenovo': lenovo,
     'Dell': dell,
+    'AMI': ami,
+    'Ami': ami,
 }
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.6.6/pyghmi/util/webclient.py 
new/pyghmi-1.6.11/pyghmi/util/webclient.py
--- old/pyghmi-1.6.6/pyghmi/util/webclient.py   2025-10-15 16:51:03.000000000 
+0200
+++ new/pyghmi-1.6.11/pyghmi/util/webclient.py  2025-12-10 20:10:03.000000000 
+0100
@@ -26,8 +26,6 @@
 import threading
 import os
 
-import six
-
 import pyghmi.exceptions as pygexc
 
 try:
@@ -99,7 +97,7 @@
 
 def get_upload_form(filename, data, formname, otherfields, boundary=BND):
     if not boundary:
-        boundary = base64.b64encode(os.urandom(54))[:70]
+        boundary = base64.urlsafe_b64encode(os.urandom(54))[:66]
     ffilename = filename.split('/')[-1]
     if not formname:
         formname = ffilename
@@ -205,7 +203,10 @@
             pass
         plainsock.connect(addrinfo[4])
         if self._certverify:
-            self.sock = ssl.wrap_socket(plainsock, cert_reqs=self.cert_reqs)
+            ctx = ssl.create_default_context()
+            ctx.check_hostname = False
+            ctx.verify_mode = ssl.CERT_NONE
+            self.sock = ctx.wrap_socket(plainsock)
             bincert = self.sock.getpeercert(binary_form=True)
             if not self._certverify(bincert):
                 raise pygexc.UnrecognizedCertificate('Unknown certificate',
@@ -303,7 +304,7 @@
         """Download a file to filename or file object
 
         """
-        if isinstance(file, six.string_types):
+        if isinstance(file, str):
             file = open(file, 'wb')
         webclient = self.dupe()
         dlheaders = self.stdheaders.copy()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.6.6/pyghmi.egg-info/PKG-INFO 
new/pyghmi-1.6.11/pyghmi.egg-info/PKG-INFO
--- old/pyghmi-1.6.6/pyghmi.egg-info/PKG-INFO   2025-10-15 16:51:51.000000000 
+0200
+++ new/pyghmi-1.6.11/pyghmi.egg-info/PKG-INFO  2025-12-10 20:11:00.000000000 
+0100
@@ -1,6 +1,6 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
 Name: pyghmi
-Version: 1.6.6
+Version: 1.6.11
 Summary: Python General Hardware Management Initiative (IPMI and others)
 Home-page: http://github.com/openstack/pyghmi/
 Author: Jarrod Johnson
@@ -20,7 +20,15 @@
 License-File: LICENSE
 Requires-Dist: cryptography>=2.1
 Requires-Dist: python-dateutil>=2.8.1
-Requires-Dist: six>=1.10.0
+Dynamic: author
+Dynamic: author-email
+Dynamic: classifier
+Dynamic: description
+Dynamic: home-page
+Dynamic: license
+Dynamic: license-file
+Dynamic: requires-dist
+Dynamic: summary
 
 This is a pure python implementation of IPMI protocol.
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.6.6/pyghmi.egg-info/SOURCES.txt 
new/pyghmi-1.6.11/pyghmi.egg-info/SOURCES.txt
--- old/pyghmi-1.6.6/pyghmi.egg-info/SOURCES.txt        2025-10-15 
16:51:52.000000000 +0200
+++ new/pyghmi-1.6.11/pyghmi.egg-info/SOURCES.txt       2025-12-10 
20:11:00.000000000 +0100
@@ -85,6 +85,8 @@
 pyghmi/redfish/oem/__init__.py
 pyghmi/redfish/oem/generic.py
 pyghmi/redfish/oem/lookup.py
+pyghmi/redfish/oem/ami/main.py
+pyghmi/redfish/oem/ami/megarac.py
 pyghmi/redfish/oem/dell/__init__.py
 pyghmi/redfish/oem/dell/idrac.py
 pyghmi/redfish/oem/dell/main.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.6.6/pyghmi.egg-info/pbr.json 
new/pyghmi-1.6.11/pyghmi.egg-info/pbr.json
--- old/pyghmi-1.6.6/pyghmi.egg-info/pbr.json   2025-10-15 16:51:51.000000000 
+0200
+++ new/pyghmi-1.6.11/pyghmi.egg-info/pbr.json  2025-12-10 20:11:00.000000000 
+0100
@@ -1 +1 @@
-{"git_version": "bbebfd9", "is_release": true}
\ No newline at end of file
+{"git_version": "8ccf30f", "is_release": true}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.6.6/pyghmi.egg-info/requires.txt 
new/pyghmi-1.6.11/pyghmi.egg-info/requires.txt
--- old/pyghmi-1.6.6/pyghmi.egg-info/requires.txt       2025-10-15 
16:51:51.000000000 +0200
+++ new/pyghmi-1.6.11/pyghmi.egg-info/requires.txt      2025-12-10 
20:11:00.000000000 +0100
@@ -1,3 +1,2 @@
 cryptography>=2.1
 python-dateutil>=2.8.1
-six>=1.10.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.6.6/requirements.txt 
new/pyghmi-1.6.11/requirements.txt
--- old/pyghmi-1.6.6/requirements.txt   2025-10-15 16:51:03.000000000 +0200
+++ new/pyghmi-1.6.11/requirements.txt  2025-12-10 20:10:03.000000000 +0100
@@ -1,5 +1,4 @@
 
 cryptography>=2.1  # BSD/Apache-2.0
 python-dateutil>=2.8.1  # BSD
-six>=1.10.0 # MIT
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyghmi-1.6.6/setup.py.tmpl 
new/pyghmi-1.6.11/setup.py.tmpl
--- old/pyghmi-1.6.6/setup.py.tmpl      2025-10-15 16:51:03.000000000 +0200
+++ new/pyghmi-1.6.11/setup.py.tmpl     2025-12-10 20:10:03.000000000 +0100
@@ -27,6 +27,6 @@
     packages=['pyghmi', 'pyghmi.util', 'pyghmi.ipmi', 'pyghmi.cmd',
               'pyghmi.redfish', 'pyghmi.ipmi.private', 'pyghmi.ipmi.oem',
               'pyghmi.ipmi.oem.lenovo', 'pyghmi.redfish.oem',
-              'pyghmi.redfish.oem.dell', 'pyghmi.redfish.oem.lenovo'],
+              'pyghmi.redfish.oem.dell', 'pyghmi.redfish.oem.lenovo', 
'pyghmi.redfish.oem.ami'],
     license='Apache License, Version 2.0')
 

Reply via email to