From a9cefb939940f2300eb50a9d2069b8d8ad983f2e Mon Sep 17 00:00:00 2001 From: John Bampton Date: Wed, 12 Aug 2026 16:20:18 +1000 Subject: [PATCH] security: run pre-commit autoupdate --freeze; Pin to sha all hooks Format Python code to fix the new Flake8 linters errors --- .pre-commit-config.yaml | 22 +++---- .../vm/hypervisor/ovm/OvmOCFS2Module.py | 4 +- .../xenserver/cloudstack_pluginlib.py | 2 +- scripts/vm/hypervisor/xenserver/vmops | 12 ++-- scripts/vm/hypervisor/xenserver/vmopsSnapshot | 61 +++++++++--------- systemvm/agent/images/left.png | Bin 321 -> 315 bytes systemvm/agent/images/right.png | Bin 413 -> 411 bytes systemvm/agent/images/right2.png | Bin 434 -> 424 bytes systemvm/agent/images/winlog.png | Bin 2314 -> 2268 bytes 9 files changed, 50 insertions(+), 51 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a7b564b0f373..ec6b37506862 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,26 +26,26 @@ repos: - id: identity - id: check-hooks-apply - repo: https://github.com/thlorenz/doctoc.git - rev: v2.3.0 + rev: facaad890e72966c88a6a5592f5b6a01c422deb2 # frozen: v2.5.0 hooks: - id: doctoc name: Add TOC for Markdown files files: ^CONTRIBUTING\.md$|^INSTALL\.md$|^README\.md$ - repo: https://github.com/oxipng/oxipng - rev: v10.1.0 + rev: 340cd9878d8d8289f09fa101b48a8f5f0b7783f4 # frozen: v10.2.0 hooks: - id: oxipng name: run oxipng description: optimize PNG images with lossless compression args: ['-o', '4', '--strip', 'safe', '--alpha'] - repo: https://github.com/gitleaks/gitleaks - rev: v8.30.0 + rev: 2ca41cc1372d1e939a6a879f18cdc19fc1cac1ce # frozen: v8.30.0 hooks: - id: gitleaks name: run gitleaks description: detect hardcoded secrets - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.5.6 + rev: ad1b27d73581aa16cca06fc4a0761fc563ffe8e8 # frozen: v1.5.6 hooks: - id: chmod name: set file permissions @@ -122,7 +122,7 @@ repos: - --fuzzy-match-generates-todo exclude: ^\.github/workflows/.*\.lock\.yml$ - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v6.0.0 + rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.0.0 hooks: #- id: check-added-large-files - id: check-case-conflict @@ -166,18 +166,18 @@ repos: args: [--markdown-linebreak-ext=md] exclude: ^services/console-proxy/rdpconsole/src/test/doc/freerdp-debug-log\.txt$ - repo: https://github.com/codespell-project/codespell - rev: v2.4.2 + rev: 57b21406f092110c18776e39b0bda50d37c945c8 # frozen: v2.4.3 hooks: - id: codespell name: run codespell description: Check spelling with codespell - repo: https://github.com/pycqa/flake8 - rev: 7.3.0 + rev: d93590f5be797aabb60e3b09f2f52dddb02f349f # frozen: 7.3.0 hooks: - - id: flake8 - args: [--config, .github/linters/.flake8] + - id: flake8 + args: [--config, .github/linters/.flake8] - repo: https://github.com/igorshubovych/markdownlint-cli - rev: v0.48.0 + rev: 5b5dddc4fb0f83c3ea1fc5616fa63e115dce83e0 # frozen: v0.49.1 hooks: - id: markdownlint name: run markdownlint @@ -186,7 +186,7 @@ repos: types: [markdown] files: \.md$ - repo: https://github.com/adrienverge/yamllint - rev: v1.38.0 + rev: cba56bcde1fdd01c1deb3f945e69764c291a6530 # frozen: v1.38.0 hooks: - id: yamllint name: run yamllint diff --git a/plugins/hypervisors/ovm/src/main/scripts/vm/hypervisor/ovm/OvmOCFS2Module.py b/plugins/hypervisors/ovm/src/main/scripts/vm/hypervisor/ovm/OvmOCFS2Module.py index 06e1e28f28e8..7ff5435a3c29 100755 --- a/plugins/hypervisors/ovm/src/main/scripts/vm/hypervisor/ovm/OvmOCFS2Module.py +++ b/plugins/hypervisors/ovm/src/main/scripts/vm/hypervisor/ovm/OvmOCFS2Module.py @@ -54,8 +54,8 @@ def _isClusterOnline(self, cluster): cmds = ['service o2cb status', cluster] res = doCmd(cmds) for line in res.split('\n'): - if not 'Checking O2CB cluster' in line: continue - return not 'Offline' in line + if 'Checking O2CB cluster' not in line: continue + return 'Offline' not in line def _load(self): cmd = ['service o2cb load'] diff --git a/scripts/vm/hypervisor/xenserver/cloudstack_pluginlib.py b/scripts/vm/hypervisor/xenserver/cloudstack_pluginlib.py index 84f90d454e3c..3a1b202d4405 100644 --- a/scripts/vm/hypervisor/xenserver/cloudstack_pluginlib.py +++ b/scripts/vm/hypervisor/xenserver/cloudstack_pluginlib.py @@ -409,7 +409,7 @@ def create_tunnel(bridge, remote_ip, gre_key, src_host, dst_host, network_uuid): key_validation = do_cmd(verify_interface_key) ip_validation = do_cmd(verify_interface_ip) - if not gre_key in key_validation or not remote_ip in ip_validation: + if gre_key not in key_validation or remote_ip not in ip_validation: logging.debug("WARNING: Unexpected output while verifying " + "interface %s on bridge %s" % (name, bridge)) return "FAILURE:VERIFY_INTERFACE_FAILED" diff --git a/scripts/vm/hypervisor/xenserver/vmops b/scripts/vm/hypervisor/xenserver/vmops index 4f78a3c90f8b..5a1fa8ce368b 100755 --- a/scripts/vm/hypervisor/xenserver/vmops +++ b/scripts/vm/hypervisor/xenserver/vmops @@ -1180,7 +1180,7 @@ def cleanup_rules(session, args): chainscmd = "iptables-save | grep '^:' | awk '{print $1}' | cut -d':' -f2 | sed 's/-def//'| sed 's/-eg//' | sort|uniq" chains = util.pread2(['/bin/bash', '-c', chainscmd]).split('\n') - vmchains = [ch for ch in chains if 1 in [ ch.startswith(c) for c in ['r-', 'i-', 's-', 'v-', 'l-']]] + vmchains = [ch for ch in chains if 1 in [ ch.startswith(c) for c in ['r-', 'i-', 's-', 'v-', 'l-']]] util.SMlog('cleanup_rules: vmchains= %s' %vmchains) util.SMlog('cleanup_rules: found %s iptables chains for vms on this host %s' % (len(vmchains), hostname[0])) cleaned = 0 @@ -1192,14 +1192,14 @@ def cleanup_rules(session, args): #after trimming the vm names which more than 29 chars, resident vm name and iptables chain name is substring of #of resident vm. for rvm in resident_vms: - if vmname in rvm: + if vmname in rvm: vmpresent = True break if vmpresent is False: vmname = chain + "-untagged" for rvm in resident_vms: - if vmname in rvm: + if vmname in rvm: vmpresent = True break #vm chain is present but vm is not running on the host. So remove the rules @@ -1491,11 +1491,11 @@ def network_rules(session, args): egressrules = 0 for line in lines: logging.debug("Processing rule [%s]." % line) - + #Example of rule: [I:tcp;12;34;1.2.3.4/24,NEXT] -> tokens: ['I:tcp', '12', '34', '1.2.3.4/24,NEXT']. tokens = line.split(';') logging.debug("Tokens %s." % tokens) - + tokens_size = len(tokens) expected_tokens_size = 4 @@ -1530,7 +1530,7 @@ def network_rules(session, args): i = cidrs.index('0.0.0.0/0') del cidrs[i] allow_any = True - + port_range = start + ":" + end logging.debug("port range [%s]" % port_range) diff --git a/scripts/vm/hypervisor/xenserver/vmopsSnapshot b/scripts/vm/hypervisor/xenserver/vmopsSnapshot index 0d5fcc184c54..8dcf2b888328 100755 --- a/scripts/vm/hypervisor/xenserver/vmopsSnapshot +++ b/scripts/vm/hypervisor/xenserver/vmopsSnapshot @@ -6,9 +6,9 @@ # to you 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 @@ -18,7 +18,7 @@ # Version @VERSION@ # -# A plugin for executing script needed by vmops cloud +# A plugin for executing script needed by vmops cloud import os, sys, time import XenAPIPlugin @@ -91,7 +91,7 @@ def create_secondary_storage_folder(session, args): umount(local_mount_path) # Remove the local folder os.system("rmdir " + local_mount_path) - + return "1" @echo @@ -127,15 +127,15 @@ def delete_secondary_storage_folder(session, args): umount(local_mount_path) # Remove the local folder os.system("rmdir " + local_mount_path) - + return "1" - + @echo def post_create_private_template(session, args): local_mount_path = None try: try: - # get local template folder + # get local template folder templatePath = args["templatePath"] local_mount_path = os.path.join(CLOUD_DIR, util.gen_uuid()) nfsVersion = args["nfsVersion"] @@ -148,7 +148,7 @@ def post_create_private_template(session, args): file_size = args["size"] virtual_size = args["virtualSize"] template_id = args["templateId"] - + # Create the template.properties file template_properties_install_path = local_mount_path + "/template.properties" f = open(template_properties_install_path, "w") @@ -167,7 +167,7 @@ def post_create_private_template(session, args): f.write("size=" + str(file_size) + "\n") f.close() logging.debug("Created template.properties file") - + # Set permissions permissions = stat.S_IREAD | stat.S_IWRITE | stat.S_IRGRP | stat.S_IWGRP | stat.S_IROTH | stat.S_IWOTH os.chmod(template_properties_install_path, permissions) @@ -184,8 +184,8 @@ def post_create_private_template(session, args): umount(local_mount_path) # Remove the local folder os.system("rmdir " + local_mount_path) - return "1" - + return "1" + def isfile(path, isISCSI): errMsg = '' exists = True @@ -193,7 +193,7 @@ def isfile(path, isISCSI): exists = checkVolumeAvailability(path) else: exists = os.path.isfile(path) - + if not exists: errMsg = "File " + path + " does not exist." logging.debug(errMsg) @@ -241,7 +241,7 @@ def scanParent(path): try: lvName = os.path.basename(path) dirname = os.path.dirname(path) - vgName = os.path.basename(dirname) + vgName = os.path.basename(dirname) vhdInfo = vhdutil.getVHDInfoLVM(lvName, lvhdutil.extractUuid, vgName) parentUUID = vhdInfo.parentUuid except: @@ -273,7 +273,7 @@ def getParentOfSnapshot(snapshotUuid, primarySRPath, isISCSI): baseCopyUuid = scanParent(snapshotPath) else: baseCopyUuid = getParent(snapshotPath, isISCSI) - + logging.debug("Base copy of snapshotUuid: " + snapshotUuid + " is " + baseCopyUuid) return baseCopyUuid @@ -282,7 +282,7 @@ def setParent(parent, child): cmd = [VHDUTIL, "modify", "-p", parent, "-n", child] txt = util.pread2(cmd) except: - errMsg = "Unexpected error while trying to set parent of " + child + " to " + parent + errMsg = "Unexpected error while trying to set parent of " + child + " to " + parent logging.debug(errMsg) raise xs_errors.XenError(errMsg) logging.debug("Successfully set parent of " + child + " to " + parent) @@ -315,12 +315,12 @@ def mount(remoteDir, localDir, nfsVersion=None): options = "soft,tcp,timeo=133,retrans=1" if nfsVersion: options += ",vers=" + nfsVersion - try: + try: cmd = ['mount', '-o', options, remoteDir, localDir] txt = util.pread2(cmd) except: txt = '' - errMsg = "Unexpected error while trying to mount " + remoteDir + " to " + localDir + errMsg = "Unexpected error while trying to mount " + remoteDir + " to " + localDir logging.debug(errMsg) raise xs_errors.XenError(errMsg) logging.debug("Successfully mounted " + remoteDir + " to " + localDir) @@ -328,11 +328,11 @@ def mount(remoteDir, localDir, nfsVersion=None): return def umount(localDir): - try: + try: cmd = ['umount', localDir] util.pread2(cmd) except CommandException: - errMsg = "CommandException raised while trying to umount " + localDir + errMsg = "CommandException raised while trying to umount " + localDir logging.debug(errMsg) raise xs_errors.XenError(errMsg) @@ -340,7 +340,7 @@ def umount(localDir): return def mountSnapshotsDir(secondaryStorageMountPath, localMountPointPath, path): - # The aim is to mount secondaryStorageMountPath on + # The aim is to mount secondaryStorageMountPath on # And create / dir on it, if it doesn't exist already. # Assuming that secondaryStorageMountPath exists remotely @@ -410,7 +410,7 @@ def getIsTrueString(stringValue): booleanValue = False if (stringValue and stringValue == 'true'): booleanValue = True - return booleanValue + return booleanValue def makeUnavailable(uuid, primarySRPath, isISCSI): if not isISCSI: @@ -433,7 +433,7 @@ def manageAvailability(path, value): logging.debug(errMsg) if value == "-ay": # Raise an error only if we are trying to make it available. - # Just warn if we are trying to make it unavailable after the + # Just warn if we are trying to make it unavailable after the # snapshot operation is done. raise xs_errors.XenError(errMsg) return @@ -450,7 +450,7 @@ def checkVolumeAvailability(path): errMsg = "Could not determine status of ISCSI path: " + path logging.debug(errMsg) raise xs_errors.XenError(errMsg) - + success = False i = 0 while i < 6: @@ -482,20 +482,20 @@ def isVolumeAvailable(path): logging.debug(errMsg) raise xs_errors.XenError(errMsg) - return (status == "1") + return (status == "1") def getVhdParent(session, args): logging.debug("getParent with " + str(args)) primaryStorageSRUuid = args['primaryStorageSRUuid'] snapshotUuid = args['snapshotUuid'] - isISCSI = getIsTrueString(args['isISCSI']) + isISCSI = getIsTrueString(args['isISCSI']) primarySRPath = getPrimarySRPath(primaryStorageSRUuid, isISCSI) logging.debug("primarySRPath: " + primarySRPath) baseCopyUuid = getParentOfSnapshot(snapshotUuid, primarySRPath, isISCSI) - return baseCopyUuid + return baseCopyUuid def getSnapshotSize(session, args): primaryStorageSRUuid = args['primaryStorageSRUuid'] @@ -545,7 +545,7 @@ def backupSnapshot(session, args): isfile(prevBackupFile, False) # copy baseCopyPath to backupsDir with new uuid - backupVHD = getBackupVHD(backupUuid) + backupVHD = getBackupVHD(backupUuid) backupFile = os.path.join(backupsDir, backupVHD) logging.debug("Back up " + baseCopyUuid + " to Secondary Storage as " + backupUuid) copyfile(baseCopyPath, backupFile, isISCSI) @@ -553,7 +553,7 @@ def backupSnapshot(session, args): # Because the primary storage is always scanned, the parent of this base copy is always the first base copy. # We don't want that, we want a chain of VHDs each of which is a delta from the previous. - # So set the parent of the current baseCopyVHD to prevBackupVHD + # So set the parent of the current baseCopyVHD to prevBackupVHD if prevBackupUuid: # If there was a previous snapshot setParent(prevBackupFile, backupFile) @@ -581,7 +581,7 @@ def deleteSnapshotBackup(session, args): logging.debug("backupVHD " + backupVHD + "does not exist. Not trying to delete it") return "1" logging.debug("backupVHD " + backupVHD + " exists.") - + # Just delete the backupVHD try: os.remove(backupVHD) @@ -591,7 +591,7 @@ def deleteSnapshotBackup(session, args): raise xs_errors.XenError(errMsg) return "1" - + @echo def revert_memory_snapshot(session, args): logging.debug("Calling revert_memory_snapshot with " + str(args)) @@ -620,4 +620,3 @@ def revert_memory_snapshot(session, args): if __name__ == "__main__": XenAPIPlugin.dispatch({"getVhdParent":getVhdParent, "create_secondary_storage_folder":create_secondary_storage_folder, "delete_secondary_storage_folder":delete_secondary_storage_folder, "post_create_private_template":post_create_private_template, "backupSnapshot": backupSnapshot, "deleteSnapshotBackup": deleteSnapshotBackup, "unmountSnapshotsDir": unmountSnapshotsDir, "revert_memory_snapshot":revert_memory_snapshot, "getSnapshotSize":getSnapshotSize}) - diff --git a/systemvm/agent/images/left.png b/systemvm/agent/images/left.png index 01b05a3ddbfcf9bd65ef3d464773ae4ebf9bce22..170ac6b15b8b3949f78c5078206d627cc7b79df9 100644 GIT binary patch delta 253 zcmVZ*c*{Q6=4yx2Y*KyPr> zKf8Q-{lLwGlm6~jmSyB?zr3{g_Vq*2EQnT7M#FP}79d!pVdz?Ua>>SS`bHdGa zfn6e{4p`aPJ1%aIiaS`{$=7@N>CHW^J{1Mt|l>L_t(|+GD6(x;wLfQB+koLu&W@qTX4@uHIn`DrqfB2tBj)~S_kD|RqgR8BR>Y|xC%1pwUNS{Tv-Poe+-002ov JPDHLkV1oK9g1`U( diff --git a/systemvm/agent/images/right.png b/systemvm/agent/images/right.png index d1514e2dc18b948f711a4d3adb45dac5a359a05d..8375d2a10f5145cae83a9c084d741d5255f2fe63 100644 GIT binary patch delta 363 zcmV-x0hIoo1DgYo83+Ra000I~$-0ptB!2+sP)t-sr^C>hxyXyJzka5;aGkMbm8)Tr zsa=q!T8*U~Salp(bsk!GAYOSQV0ep7aaW{seGr@L#BqH>O!#@5}>-{!^D+|l9a%YWVE z#n|D#)7`eu+OW&lsm9Zz!qA?+&3Kuq2s=`Go2q-9tA3xZfT6I1q_c*nwu-B|jjp|c zqqBXawTG&?JZE__YJfU?l__C*9Zh2xMq3m@R}er|4Lwu~JyQxiQVBa!TYQUFb%WXF z?6S+&%@GX{0000@Nklsr~=}002ov JPDHLkV1lRjsEz;t delta 365 zcmV-z0h0ck1Dykq83+OZ006%7uHTU%B!2-yNklBF078bIhJk7>K!$c;HZVY203yW!{Ebf9Ld77|5GLae!4;u+7jGDzPOo)wz82@{l z`+pHjMU|Gw$UGg$ks6H|jMV=1Q%%npO50(i{{G$Lpmc0TDlctmJJ225WTaM7HThX>I_BI-@0o(A2I`Fj``2)Tj%=D5(%M-WxrYhI*0CNU0D}=ZF(RxEHE} zaYko)<7jsMu(YKgtmFlwGrx0EHyA4KorJOuKciFO9jO|c@r11cSfAVj7_4_(00000 LNkvXXu0mjfVN9=# diff --git a/systemvm/agent/images/right2.png b/systemvm/agent/images/right2.png index 4a3f4752ed1d4eb6aa340a12cbb532642d1d4255..28769c5bed7785ed47c971deb94459bc246d53f7 100644 GIT binary patch delta 363 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 Y1jx-9Kp^m3z5oCK07*qoM6N<$g1_T*EdT%j