| [1027] | 1 | #!/usr/bin/env python
 | 
|---|
 | 2 | 
 | 
|---|
| [1061] | 3 | # -*- coding: utf-8 -*-
 | 
|---|
 | 4 | 
 | 
|---|
| [1055] | 5 | """ospi-client: performs operation for opsi clients on opsi server via JSON-RPC."""
 | 
|---|
 | 6 | 
 | 
|---|
| [1234] | 7 | from __future__ import print_function
 | 
|---|
 | 8 | 
 | 
|---|
| [1061] | 9 | __author__ = "Joerg Steffens"
 | 
|---|
| [1254] | 10 | __copyright__ = "Copyright 2012-2021, dass IT GmbH"
 | 
|---|
| [1055] | 11 | __license__ = "GPL"
 | 
|---|
| [1254] | 12 | __version__ = "1.3"
 | 
|---|
| [1055] | 13 | __email__ = "joerg.steffens@dass-it.de"
 | 
|---|
 | 14 | 
 | 
|---|
| [1051] | 15 | #self.command("opsi-admin -d method host_createOpsiClient "+ \
 | 
|---|
 | 16 |         #computername + " null " + "\\'"+description+"\\'" + \
 | 
|---|
 | 17 |         #" \\'created by dassadmin\\' " + mac_address + " " + \
 | 
|---|
 | 18 |         #ip_address)
 | 
|---|
 | 19 | #self.command("opsi-admin -d method configState_create clientconfig.depot.id " + \
 | 
|---|
| [1174] | 20 |         #computername + " " + depotName)
 | 
|---|
 | 21 | 
 | 
|---|
| [1047] | 22 | import argparse
 | 
|---|
| [1239] | 23 | from   datetime import datetime, timedelta
 | 
|---|
 | 24 | from   dateutil import parser as dateparser
 | 
|---|
| [1110] | 25 | import logging
 | 
|---|
 | 26 | import os
 | 
|---|
| [1239] | 27 | from   pprint import pprint, pformat
 | 
|---|
| [1234] | 28 | import sys
 | 
|---|
| [1239] | 29 | import ssl
 | 
|---|
| [1110] | 30 | import time
 | 
|---|
| [1027] | 31 | 
 | 
|---|
| [1254] | 32 | from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
 | 
|---|
 | 33 | from tinyrpc.transports.http import HttpPostClientTransport
 | 
|---|
 | 34 | from tinyrpc import RPCClient
 | 
|---|
| [1239] | 35 | 
 | 
|---|
| [1063] | 36 | UrlJsonRpc="https://<username>:<password>@opsi:4447/rpc"
 | 
|---|
| [1027] | 37 | 
 | 
|---|
| [1255] | 38 | HelpEpilog="WARNING: json-rpc is known to have problems with HTTP proxies. In case of problems, make sure, the environment variables http_proxy and/or https_proxy are *not* set. It might also be necessary to set variable PYTHONHTTPSVERIFY=0."
 | 
|---|
| [1063] | 39 | 
 | 
|---|
| [1234] | 40 | class Nrpe:
 | 
|---|
 | 41 |     OK = 0
 | 
|---|
 | 42 |     WARNING = 1
 | 
|---|
 | 43 |     CRITICAL = 2
 | 
|---|
 | 44 |     UNKNOWN = 3
 | 
|---|
 | 45 |     
 | 
|---|
 | 46 |     def toString(self, status):
 | 
|---|
 | 47 |         if status == self.OK:
 | 
|---|
 | 48 |             return 'OK'
 | 
|---|
 | 49 |         elif status == self.WARNING:
 | 
|---|
 | 50 |             return 'WARNING'
 | 
|---|
 | 51 |         elif status == self.CRITICAL:
 | 
|---|
 | 52 |             return 'CRITICAL'
 | 
|---|
 | 53 |         else:
 | 
|---|
 | 54 |             return 'UNKNOWN'
 | 
|---|
 | 55 | 
 | 
|---|
 | 56 | class Output(Nrpe):
 | 
|---|
 | 57 |     def __init__(self, nagios=False):
 | 
|---|
 | 58 |         self.nagios = nagios
 | 
|---|
 | 59 |         self.result = Nrpe.UNKNOWN
 | 
|---|
 | 60 |         self.message = ''
 | 
|---|
 | 61 | 
 | 
|---|
 | 62 |     def setStatus(self, status, message = None):
 | 
|---|
 | 63 |         self.result = status
 | 
|---|
 | 64 |         if message is not None:
 | 
|---|
 | 65 |             self.setMessage(message)
 | 
|---|
 | 66 | 
 | 
|---|
 | 67 |     def setMessage(self, string):
 | 
|---|
 | 68 |         self.message = string
 | 
|---|
 | 69 | 
 | 
|---|
 | 70 |     def finalize(self):
 | 
|---|
 | 71 |         if self.nagios:
 | 
|---|
 | 72 |             print('{0} - {1}'.format(self.toString(self.result), self.message))
 | 
|---|
 | 73 |             sys.exit(self.result)
 | 
|---|
 | 74 |         else:
 | 
|---|
 | 75 |             print('{0} - {1}'.format(self.toString(self.result), self.message))
 | 
|---|
 | 76 |             return (self.result == Nrpe.OK)
 | 
|---|
 | 77 | 
 | 
|---|
| [1087] | 78 | class OpsiRpc:
 | 
|---|
 | 79 | 
 | 
|---|
 | 80 |     UrlJsonRpcDefault="https://opsi:4447/rpc"
 | 
|---|
| [1174] | 81 | 
 | 
|---|
| [1087] | 82 |     ProductAttributesCopy = ['actionRequest','actionResult','installationStatus','packageVersion','productVersion']
 | 
|---|
| [1174] | 83 | 
 | 
|---|
| [1234] | 84 |     def __init__(self, urlJsonRpc = UrlJsonRpcDefault, debug=False, nagios=False):
 | 
|---|
| [1174] | 85 |         self.logger=logging.getLogger(__name__)
 | 
|---|
| [1087] | 86 |         self.debug=debug
 | 
|---|
| [1234] | 87 |         self.nagios=nagios
 | 
|---|
| [1087] | 88 |         self.urlJsonRpc=urlJsonRpc
 | 
|---|
| [1254] | 89 |         self.rpc = RPCClient(JSONRPCProtocol(), HttpPostClientTransport(self.urlJsonRpc, verify=False)).get_proxy()
 | 
|---|
 | 90 | 
 | 
|---|
| [1110] | 91 |         self.logger.debug( "initialized: " + self.urlJsonRpc )
 | 
|---|
| [1239] | 92 |         self.logger.debug(dir(self.rpc))
 | 
|---|
| [1110] | 93 | 
 | 
|---|
| [1174] | 94 | 
 | 
|---|
| [1110] | 95 |     def list(self):
 | 
|---|
| [1239] | 96 |         exceptions = []
 | 
|---|
 | 97 |         if 'jsonrpc' in sys.modules:
 | 
|---|
 | 98 |             exceptions = [ jsonrpc.json.JSONDecodeException ]
 | 
|---|
| [1234] | 99 |         try:
 | 
|---|
 | 100 |             print( "\n".join( self.rpc.getClientIds_list() ) )
 | 
|---|
| [1239] | 101 |         except exceptions as e:
 | 
|---|
| [1234] | 102 |             self.logger.debug( pformat(self.rpc.getClientIds_list()) )
 | 
|---|
 | 103 |             self.logger.exception( "failed" )
 | 
|---|
 | 104 |             return True
 | 
|---|
| [1087] | 105 | 
 | 
|---|
| [1174] | 106 | 
 | 
|---|
| [1110] | 107 |     def getClientsWithProduct( self, product ):
 | 
|---|
 | 108 |         return self.rpc.productOnClient_getObjects( [], { "productId": product, "installationStatus": "installed" } )
 | 
|---|
 | 109 | 
 | 
|---|
| [1234] | 110 |     def getHardwareSerialNumber(self, src):
 | 
|---|
 | 111 |         serialNumbers  = self.rpc.auditHardwareOnHost_getHashes( [], {"hostId":src, "hardwareClass":"BIOS", "serialNumber":"*"} )
 | 
|---|
 | 112 |         serialNumbers += self.rpc.auditHardwareOnHost_getHashes( [], {"hostId":src, "hardwareClass":"CHASSIS", "serialNumber":"*"} )
 | 
|---|
 | 113 |         result = set()
 | 
|---|
 | 114 |         for i in serialNumbers:
 | 
|---|
 | 115 |             if i['serialNumber']:
 | 
|---|
 | 116 |                 result.add(i['serialNumber'])
 | 
|---|
 | 117 |         if len(result) == 1:
 | 
|---|
 | 118 |             return result.pop()
 | 
|---|
 | 119 |         elif len(result) > 1:
 | 
|---|
 | 120 |             self.logger.warning("found more then one serial number")
 | 
|---|
 | 121 |             return list(result)
 | 
|---|
| [1110] | 122 | 
 | 
|---|
 | 123 |     def listClients( self, product ):
 | 
|---|
 | 124 |         if product:
 | 
|---|
 | 125 |             for client in self.getClientsWithProduct( product ):
 | 
|---|
| [1234] | 126 |                 print(client['clientId'])
 | 
|---|
| [1110] | 127 |         else:
 | 
|---|
 | 128 |             return self.list()
 | 
|---|
 | 129 |         return True
 | 
|---|
| [1174] | 130 | 
 | 
|---|
| [1087] | 131 |     def exists(self, src):
 | 
|---|
 | 132 |         return len( self.rpc.host_getObjects( [], {"id":src} ) ) == 1
 | 
|---|
| [1174] | 133 | 
 | 
|---|
| [1087] | 134 |     def info(self, src):
 | 
|---|
 | 135 |         if not self.exists( src ):
 | 
|---|
| [1234] | 136 |             print("failed: opsi client", src, "does not exist")
 | 
|---|
| [1087] | 137 |             return False
 | 
|---|
| [1234] | 138 |         print(src + ":")
 | 
|---|
| [1087] | 139 |         host = self.rpc.host_getHashes( [], {"id":src} )[0]
 | 
|---|
| [1234] | 140 |         print("  IP:", host["ipAddress"])
 | 
|---|
 | 141 |         print("  MAC:", host["hardwareAddress"])
 | 
|---|
 | 142 |         print("  inventory:", host["inventoryNumber"])
 | 
|---|
 | 143 |         print("  serial number:", self.getHardwareSerialNumber(src))
 | 
|---|
 | 144 |         print("  last seen:", host["lastSeen"])
 | 
|---|
 | 145 |         print("  notes:", host["notes"])
 | 
|---|
 | 146 |         print("  depot:", self.clientGetDepot(src))
 | 
|---|
| [1174] | 147 | 
 | 
|---|
| [1234] | 148 |         print("  products:")
 | 
|---|
| [1087] | 149 |         products = self.getProductOnClient( src, [] )
 | 
|---|
 | 150 |         for i in products:
 | 
|---|
| [1234] | 151 |             print("    " + i['productId'] + ":")
 | 
|---|
 | 152 |             print("      " + i['installationStatus'], "(", end='')
 | 
|---|
| [1087] | 153 |             if i['actionRequest']:
 | 
|---|
| [1234] | 154 |                 print(i['actionRequest'], end='')
 | 
|---|
| [1087] | 155 |                 if i['actionProgress']:
 | 
|---|
| [1234] | 156 |                     print(i['actionProgress'], end='')
 | 
|---|
 | 157 |             print(")")
 | 
|---|
 | 158 |             print("      ", end='')
 | 
|---|
| [1087] | 159 |             pprint( i, indent=8 )
 | 
|---|
 | 160 |         return True
 | 
|---|
 | 161 | 
 | 
|---|
 | 162 |     def clean(self, src):
 | 
|---|
 | 163 |         if not self.exists( src ):
 | 
|---|
 | 164 |             return False
 | 
|---|
 | 165 |         products = self.rpc.productOnClient_getObjects( [], { 'clientId': src } )
 | 
|---|
 | 166 |         self.rpc.productOnClient_deleteObjects( products )
 | 
|---|
| [1088] | 167 | 
 | 
|---|
| [1109] | 168 |         products = self.rpc.productPropertyState_getObjects( [], { 'objectId': src } )
 | 
|---|
| [1088] | 169 |         self.rpc.productPropertyState_deleteObjects( products )
 | 
|---|
| [1174] | 170 | 
 | 
|---|
| [1087] | 171 |         if self.debug:
 | 
|---|
 | 172 |             pprint( self.getProductOnClient( src ) )
 | 
|---|
 | 173 |         return True
 | 
|---|
| [1174] | 174 | 
 | 
|---|
 | 175 |     def getOpsiConfigserverId(self):
 | 
|---|
 | 176 |         # there should always be only one OpsiConfigserver
 | 
|---|
 | 177 |         opsiConfigservers=self.rpc.host_getHashes( [], { "type": "OpsiConfigserver" } )
 | 
|---|
 | 178 |         try:
 | 
|---|
 | 179 |             return opsiConfigservers[0]['id']
 | 
|---|
 | 180 |         except (KeyError,IndexError) as e:
 | 
|---|
 | 181 |             self.logger.error( "failed to retreive OpsiConfigserver" )
 | 
|---|
 | 182 | 
 | 
|---|
| [1087] | 183 |     def createClient(self, name, opsiHostKey, description, notes, hardwareAddress, ipAddress):
 | 
|---|
| [1117] | 184 |         #    self.rpc.host_createOpsiClient( name, opsiHostKey, description, notes, hardwareAddress, ipAddress )
 | 
|---|
| [1177] | 185 |         self.updateClient( name, opsiHostKey, description, notes, None, hardwareAddress, ipAddress )
 | 
|---|
| [1087] | 186 | 
 | 
|---|
| [1175] | 187 |     def deleteClient(self, name):
 | 
|---|
 | 188 |         self.rpc.host_delete( name )
 | 
|---|
 | 189 | 
 | 
|---|
| [1177] | 190 |     def updateClient(self, src, opsiHostKey = None, description = None, notes = None, inventoryNumber = None, hardwareAddress = None, ipAddress = None, depot = None ):
 | 
|---|
| [1117] | 191 |         obj = {
 | 
|---|
 | 192 |           "id" : src,
 | 
|---|
 | 193 |           "type" : "OpsiClient",
 | 
|---|
 | 194 |         }
 | 
|---|
 | 195 |         if opsiHostKey:
 | 
|---|
 | 196 |             obj['opsiHostKey'] = opsiHostKey
 | 
|---|
 | 197 |         if description:
 | 
|---|
 | 198 |             obj['description'] = description
 | 
|---|
 | 199 |         if notes:
 | 
|---|
| [1174] | 200 |             obj['notes'] = notes
 | 
|---|
| [1177] | 201 |         if inventoryNumber:
 | 
|---|
 | 202 |             obj['inventoryNumber'] = inventoryNumber
 | 
|---|
| [1117] | 203 |         if hardwareAddress:
 | 
|---|
 | 204 |             obj['hardwareAddress'] = hardwareAddress
 | 
|---|
 | 205 |         if ipAddress:
 | 
|---|
 | 206 |             obj['ipAddress'] = ipAddress
 | 
|---|
 | 207 | 
 | 
|---|
 | 208 |         if self.exists( src ):
 | 
|---|
 | 209 |             self.rpc.host_updateObject(obj)
 | 
|---|
 | 210 |         else:
 | 
|---|
 | 211 |             self.rpc.host_insertObject(obj)
 | 
|---|
| [1174] | 212 | 
 | 
|---|
| [1117] | 213 |         if depot:
 | 
|---|
 | 214 |             self.clientSetDepot(src,depot)
 | 
|---|
 | 215 |         return True
 | 
|---|
 | 216 | 
 | 
|---|
| [1176] | 217 |     def clientGetDepot(self, name):
 | 
|---|
 | 218 |         depot = self.rpc.configState_getHashes( [], {
 | 
|---|
 | 219 |             "configId": "clientconfig.depot.id",
 | 
|---|
 | 220 |             "objectId": name } )
 | 
|---|
 | 221 |         try:
 | 
|---|
 | 222 |             return depot[0]["values"][0]
 | 
|---|
 | 223 |         except (IndexError,KeyError):
 | 
|---|
 | 224 |             return self.getOpsiConfigserverId()
 | 
|---|
| [1117] | 225 | 
 | 
|---|
| [1087] | 226 |     def clientSetDepot(self, name, depot):
 | 
|---|
| [1174] | 227 |         self.rpc.configState_create( "clientconfig.depot.id", name, depot )
 | 
|---|
| [1087] | 228 | 
 | 
|---|
| [1088] | 229 |     def copyClient( self, src, dst, ipAddress = None, hardwareAddress = None, depot = None, description = "", copyProperties = True ):
 | 
|---|
| [1174] | 230 | 
 | 
|---|
| [1234] | 231 |         print("create/update", dst, "from template", src + ":", end='')
 | 
|---|
| [1087] | 232 |         obj = {
 | 
|---|
 | 233 |           "id" : dst,
 | 
|---|
 | 234 |           "type" : "OpsiClient",
 | 
|---|
 | 235 |           "notes" : "copy of " + src,
 | 
|---|
 | 236 |           "description" : description,
 | 
|---|
 | 237 |           #"inventoryNumber" : "",
 | 
|---|
 | 238 |         }
 | 
|---|
 | 239 |         if hardwareAddress:
 | 
|---|
 | 240 |             obj['hardwareAddress'] = hardwareAddress
 | 
|---|
 | 241 |         if ipAddress:
 | 
|---|
 | 242 |             obj['ipAddress'] = ipAddress
 | 
|---|
 | 243 | 
 | 
|---|
 | 244 |         if self.exists( dst ):
 | 
|---|
 | 245 |             self.rpc.host_updateObject(obj)
 | 
|---|
 | 246 |         else:
 | 
|---|
 | 247 |             self.rpc.host_insertObject(obj)
 | 
|---|
| [1174] | 248 | 
 | 
|---|
| [1087] | 249 |         if depot:
 | 
|---|
 | 250 |             self.clientSetDepot(dst,depot)
 | 
|---|
| [1174] | 251 | 
 | 
|---|
| [1087] | 252 |         if self.debug:
 | 
|---|
 | 253 |             pprint( self.getProductOnClient( src ) )
 | 
|---|
 | 254 |         self.copyProductOnClient( src, dst )
 | 
|---|
| [1088] | 255 |         if copyProperties:
 | 
|---|
 | 256 |             if self.debug:
 | 
|---|
| [1234] | 257 |                 print("copy product properties")
 | 
|---|
| [1174] | 258 |             if not depot:
 | 
|---|
 | 259 |                 # get default Properties from Master Depot Server (OpsiConfigserver)
 | 
|---|
 | 260 |                 depot = self.getOpsiConfigserverId()
 | 
|---|
 | 261 |             self.copyProductPropertyState( src, dst, depot )
 | 
|---|
| [1234] | 262 |         print("done")
 | 
|---|
| [1087] | 263 |         return True
 | 
|---|
 | 264 | 
 | 
|---|
 | 265 |     def getProductOnClient( self, client, attributes = ProductAttributesCopy ):
 | 
|---|
 | 266 |         return self.rpc.productOnClient_getHashes( [], { 'clientId': client } )
 | 
|---|
| [1110] | 267 | 
 | 
|---|
| [1087] | 268 |     def copyProductOnClient( self, src, dst, attributes = ProductAttributesCopy ):
 | 
|---|
| [1088] | 269 |         products_src = self.rpc.productOnClient_getHashes( attributes, { 'clientId': src } )
 | 
|---|
 | 270 |         products_dst = []
 | 
|---|
 | 271 |         for i in products_src:
 | 
|---|
| [1087] | 272 |             if self.debug: 
 | 
|---|
| [1234] | 273 |                 print(i['productId'])
 | 
|---|
| [1087] | 274 |                 pprint( i )
 | 
|---|
 | 275 |             i['clientId'] = dst
 | 
|---|
| [1088] | 276 |             products_dst.append(i)
 | 
|---|
 | 277 |         self.rpc.productOnClient_createObjects( products_dst )
 | 
|---|
| [1087] | 278 |         if self.debug:
 | 
|---|
 | 279 |             pprint( self.getProductOnClient( dst ) )
 | 
|---|
| [1088] | 280 | 
 | 
|---|
| [1174] | 281 | 
 | 
|---|
| [1088] | 282 |     def getProductPropertyState( self, client, attributes = [] ):
 | 
|---|
 | 283 |         return self.rpc.productPropertyState_getHashes( [], { 'objectId': client } )
 | 
|---|
| [1174] | 284 | 
 | 
|---|
 | 285 | 
 | 
|---|
 | 286 |     def copyProductPropertyState( self, src, dst, default = None, attributes = [] ):
 | 
|---|
 | 287 |         if default:
 | 
|---|
 | 288 |             productProperties_default = self.getProductPropertyState( default, attributes )
 | 
|---|
 | 289 |         else:
 | 
|---|
 | 290 |             productProperties_default = []
 | 
|---|
| [1088] | 291 |         productProperties_src = self.getProductPropertyState( src, attributes )
 | 
|---|
 | 292 |         productProperties_dst = []
 | 
|---|
 | 293 |         for i in productProperties_src:
 | 
|---|
| [1174] | 294 |             use_default=False
 | 
|---|
| [1176] | 295 |             default_value=None
 | 
|---|
| [1174] | 296 |             for j in productProperties_default:
 | 
|---|
 | 297 |                 if i['productId'] == j['productId'] and i["propertyId"] == j["propertyId"]:
 | 
|---|
| [1176] | 298 |                     default_value = j['values']
 | 
|---|
| [1174] | 299 |                     if i['values'] == j['values']:
 | 
|---|
 | 300 |                         use_default=True
 | 
|---|
 | 301 |             if self.debug:
 | 
|---|
| [1234] | 302 |                 print(i['productId'], "-", i["propertyId"] + ": ", pformat(i["values"]), end='')
 | 
|---|
| [1174] | 303 |                 if use_default:
 | 
|---|
| [1234] | 304 |                     print("(use default)")
 | 
|---|
| [1174] | 305 |                 else:
 | 
|---|
| [1234] | 306 |                     print("(set, default:", default_value, ")")
 | 
|---|
| [1174] | 307 |             if not use_default:
 | 
|---|
 | 308 |                 i['objectId'] = dst
 | 
|---|
 | 309 |                 productProperties_dst.append(i)
 | 
|---|
| [1088] | 310 |         self.rpc.productPropertyState_createObjects( productProperties_dst )
 | 
|---|
 | 311 |         if self.debug:
 | 
|---|
 | 312 |             pprint( self.getProductPropertyState( dst ) )
 | 
|---|
| [1110] | 313 | 
 | 
|---|
 | 314 | 
 | 
|---|
 | 315 |     def getClientProductProperty( self, client, product ):
 | 
|---|
 | 316 |         return self.rpc.getProductProperties_hash( product, [ client ] )
 | 
|---|
 | 317 | 
 | 
|---|
| [1118] | 318 |     def setProductPropertiesOnClient( self, dst, product, properties ):
 | 
|---|
 | 319 |         self.rpc.setProductProperties(product,properties,dst)
 | 
|---|
 | 320 | 
 | 
|---|
 | 321 |     def setProductPropertyOnClient( self, dst, product, prop, value):
 | 
|---|
 | 322 |         self.rpc.setProductProperty(product,prop,value,dst)
 | 
|---|
 | 323 | 
 | 
|---|
 | 324 | 
 | 
|---|
| [1239] | 325 | 
 | 
|---|
 | 326 |     def write_value_conf(self, fd, key, properties, default=None):
 | 
|---|
 | 327 |         value = None
 | 
|---|
 | 328 |         comment = ''
 | 
|---|
 | 329 |         try:
 | 
|---|
 | 330 |             value = properties[key.lower()]
 | 
|---|
 | 331 |         except KeyError:
 | 
|---|
 | 332 |             pass
 | 
|---|
 | 333 |         if not value and default:
 | 
|---|
 | 334 |             value = default
 | 
|---|
 | 335 |         if not value:
 | 
|---|
 | 336 |             # prevent a None to be written
 | 
|---|
 | 337 |             value = ''
 | 
|---|
 | 338 |             comment = '# '
 | 
|---|
 | 339 |         fd.write('  {}{} = "{}"\n'.format(comment, key, value))
 | 
|---|
 | 340 | 
 | 
|---|
 | 341 | 
 | 
|---|
 | 342 | 
 | 
|---|
| [1110] | 343 |     def write_client_conf( self, fd, client, properties ):
 | 
|---|
 | 344 |         #Client {
 | 
|---|
 | 345 |         #Name = ting-fd
 | 
|---|
 | 346 |         #Address = ting.dass-it
 | 
|---|
 | 347 |         #FDPort = 9102
 | 
|---|
| [1239] | 348 |         #Password = "D5w2V5w6B8a9H5Z"
 | 
|---|
| [1110] | 349 |         #Catalog = MyCatalog
 | 
|---|
 | 350 |         #File Retention = 6 months
 | 
|---|
 | 351 |         #Job Retention = 6 months
 | 
|---|
 | 352 |         #AutoPrune = yes
 | 
|---|
 | 353 |         #}
 | 
|---|
| [1239] | 354 |         params = [ 'catalog', "FDPort", "FileRetention", "JobRetention", "AutoPrune" ]
 | 
|---|
| [1110] | 355 |         fd.write( "Client {\n" )
 | 
|---|
 | 356 |         fd.write( '  Name     = "' + properties['filedaemon_full_name'] + '"' + "\n" )
 | 
|---|
| [1239] | 357 |         fd.write( '  Address  = "' + properties['filedaemon_client_address'] + '"' + "\n" )
 | 
|---|
| [1110] | 358 |         # ipAddress: method host_getObjects [] '{"id":client['clientId']}'
 | 
|---|
| [1234] | 359 |         #print("  # Address =", ipAddress)
 | 
|---|
| [1110] | 360 |         fd.write( '  Password = "' + properties['filedaemon_full_password'] + '"' + "\n" )
 | 
|---|
 | 361 |         for i in params:
 | 
|---|
| [1239] | 362 |             self.write_value_conf(fd, i, properties)
 | 
|---|
| [1110] | 363 |         fd.write( "}\n")
 | 
|---|
 | 364 |         fd.write( "\n" )
 | 
|---|
| [1087] | 365 | 
 | 
|---|
 | 366 | 
 | 
|---|
| [1239] | 367 |     def write_job_conf(self, fd, client, properties, defaultjobdefs, defaultfileset):
 | 
|---|
| [1110] | 368 |         #Job {
 | 
|---|
 | 369 |         #FileSet = "tingfileset"
 | 
|---|
 | 370 |         #Name = "ting"
 | 
|---|
 | 371 |         #Client = ting-fd
 | 
|---|
 | 372 |         #JobDefs = "LaptopJob"
 | 
|---|
 | 373 |         ## Write Bootstrap = "/var/lib/bacula/ting.bsr"
 | 
|---|
 | 374 |         #}
 | 
|---|
| [1239] | 375 |         params = [ "JobDefs", "FileSet" ]
 | 
|---|
| [1110] | 376 |         fd.write( "Job {" + "\n" )
 | 
|---|
 | 377 |         fd.write( '  Name    = "' + client['clientId'] + '-job"' + "\n" )
 | 
|---|
 | 378 |         fd.write( '  Client  = "' + properties['filedaemon_full_name'] + '"' + "\n" )
 | 
|---|
| [1239] | 379 |         self.write_value_conf(fd, 'JobDefs', properties, defaultjobdefs)
 | 
|---|
 | 380 |         self.write_value_conf(fd, 'FileSet', properties, defaultfileset)
 | 
|---|
| [1110] | 381 |         fd.write( "}" + "\n" )
 | 
|---|
 | 382 |         fd.write( "\n" )
 | 
|---|
 | 383 | 
 | 
|---|
 | 384 | 
 | 
|---|
 | 385 |     def write_config_file_header( self, fd ):
 | 
|---|
| [1174] | 386 |         try:
 | 
|---|
| [1110] | 387 |             fd.write( "#\n" )
 | 
|---|
| [1114] | 388 |             fd.write( "# automatically generated at {0}\n".format( time.asctime() ) )
 | 
|---|
| [1110] | 389 |             fd.write( "#\n\n" )
 | 
|---|
| [1177] | 390 |         except BaseException as e:
 | 
|---|
| [1110] | 391 |             self.logger.exception( "failed to create files" )
 | 
|---|
 | 392 |             return False
 | 
|---|
 | 393 |         return True
 | 
|---|
 | 394 | 
 | 
|---|
| [1174] | 395 | 
 | 
|---|
| [1239] | 396 | 
 | 
|---|
 | 397 |     def createBareosConfigFiles(self, defaultjobdefs, defaultfileset):
 | 
|---|
 | 398 |         bareosDirConfigPath = '/etc/bareos/bareos-dir.d/'
 | 
|---|
 | 399 |         clientsWithBacula=self.getClientsWithProduct('winbareos')
 | 
|---|
| [1110] | 400 |         if clientsWithBacula:
 | 
|---|
 | 401 |             try:
 | 
|---|
| [1239] | 402 |                 configfile = bareosDirConfigPath + 'client/opsi-clients-generated.conf'
 | 
|---|
 | 403 |                 file_opsi_clients = open(configfile, 'w')
 | 
|---|
| [1110] | 404 |                 self.write_config_file_header( file_opsi_clients )
 | 
|---|
| [1239] | 405 |             except (BaseException, IOError) as e:
 | 
|---|
 | 406 |                 self.logger.exception( "failed to create configuration file {}".format(configfile) )
 | 
|---|
 | 407 |                 return False
 | 
|---|
| [1174] | 408 | 
 | 
|---|
| [1239] | 409 |             try:
 | 
|---|
 | 410 |                 configfile = bareosDirConfigPath + 'job/opsi-jobs-generated.conf'
 | 
|---|
 | 411 |                 file_opsi_jobs = open(configfile, 'w')
 | 
|---|
| [1110] | 412 |                 self.write_config_file_header( file_opsi_jobs )
 | 
|---|
| [1239] | 413 |             except (BaseException, IOError) as e:
 | 
|---|
 | 414 |                 self.logger.exception( "failed to create configuration file {}".format(configfile) )
 | 
|---|
| [1110] | 415 |                 return False
 | 
|---|
| [1239] | 416 | 
 | 
|---|
| [1110] | 417 |             for client in clientsWithBacula:
 | 
|---|
| [1174] | 418 |                 clientId = client['clientId']
 | 
|---|
| [1110] | 419 |                 try:
 | 
|---|
| [1239] | 420 |                     clientBaculaProperties=self.getClientProductProperty( clientId, 'winbareos' )
 | 
|---|
| [1110] | 421 |                 except ValueError as e:
 | 
|---|
 | 422 |                     self.logger.warn( "%s: no valid information found: %s" %(clientId, e) )
 | 
|---|
 | 423 |                 else:
 | 
|---|
 | 424 |                     if clientBaculaProperties:
 | 
|---|
 | 425 |                         #pprint( clientBaculaProperties )
 | 
|---|
| [1239] | 426 |                         self.write_client_conf(file_opsi_clients, client, clientBaculaProperties)
 | 
|---|
 | 427 |                         self.write_job_conf(file_opsi_jobs, client, clientBaculaProperties, defaultjobdefs, defaultfileset)
 | 
|---|
| [1110] | 428 |                         self.logger.info( "%s: OK" % clientId )
 | 
|---|
 | 429 |                     else:
 | 
|---|
 | 430 |                         self.logger.warn( "%s: failed: no product properties defined" %(clientId) )
 | 
|---|
 | 431 |             return True
 | 
|---|
 | 432 | 
 | 
|---|
| [1234] | 433 |     def __getVersionString(self, product):
 | 
|---|
 | 434 |         return '{productVersion}-{packageVersion}'.format(**product)
 | 
|---|
| [1110] | 435 | 
 | 
|---|
| [1234] | 436 |     def getProductCurrentVersion(self, productId):
 | 
|---|
 | 437 |         products = self.rpc.product_getHashes( [], { 'id': productId } )
 | 
|---|
 | 438 |         if products:
 | 
|---|
 | 439 |             return self.__getVersionString(products[0])
 | 
|---|
 | 440 |         else:
 | 
|---|
 | 441 |             return None
 | 
|---|
| [1110] | 442 | 
 | 
|---|
| [1234] | 443 |     def __getClientId(self, d):
 | 
|---|
 | 444 |         return d.get("clientId")
 | 
|---|
 | 445 | 
 | 
|---|
 | 446 |     def listInstalled(self, productId):
 | 
|---|
 | 447 |         productVersion = self.getProductCurrentVersion(productId)
 | 
|---|
 | 448 |         self.logger.debug('version: {0}'.format(productVersion))
 | 
|---|
 | 449 |         products = self.rpc.productOnClient_getHashes( [], { 'productId': productId } )
 | 
|---|
 | 450 |         for i in sorted(products, key=self.__getClientId):
 | 
|---|
 | 451 |             i['version'] = self.__getVersionString(i)
 | 
|---|
 | 452 |             if i.get('installationStatus') == 'installed':
 | 
|---|
 | 453 |                 if productVersion != i['version']:
 | 
|---|
 | 454 |                     i['proposedAction'] = 'update'
 | 
|---|
 | 455 |                 else:
 | 
|---|
 | 456 |                     i['proposedAction'] = 'None'
 | 
|---|
| [1255] | 457 |                 print('{clientId}: {version} (proposed action={proposedAction}) request={actionRequest}, result={actionResult}'.format(**i))
 | 
|---|
| [1234] | 458 |             else:
 | 
|---|
 | 459 |                 i['proposedAction'] = 'install'
 | 
|---|
 | 460 |                 print('{clientId}: (proposed action={proposedAction})'.format(**i))
 | 
|---|
 | 461 |                 self.logger.debug('{clientId}: {actionRequest} {installationStatus} {version}'.format(**i))
 | 
|---|
 | 462 |             #pprint( i, indent=8 )
 | 
|---|
 | 463 |         return True
 | 
|---|
 | 464 | 
 | 
|---|
 | 465 |     def isInstalled(self, clientId, productId):
 | 
|---|
 | 466 |         """
 | 
|---|
 | 467 |         CRITICAL: not installed
 | 
|---|
 | 468 |         WARNING: installed, but not current version
 | 
|---|
 | 469 |         OK: current version is installed
 | 
|---|
 | 470 |         UNKNOWN: otherwise
 | 
|---|
 | 471 |         """
 | 
|---|
 | 472 |         output = Output(self.nagios)
 | 
|---|
 | 473 |         if not self.exists(clientId):
 | 
|---|
 | 474 |             output.setMessage("failed: opsi client {0} does not exist".format(clientId))
 | 
|---|
 | 475 |             return output.finalize()
 | 
|---|
 | 476 |         productVersion = self.getProductCurrentVersion(productId)
 | 
|---|
 | 477 |         if not productVersion:
 | 
|---|
 | 478 |             output.setMessage("failed: product {0} does not exist".format(productId))
 | 
|---|
 | 479 |             return output.finalize()
 | 
|---|
 | 480 |         self.logger.debug('version: {0}'.format(productVersion))
 | 
|---|
 | 481 |         products = self.rpc.productOnClient_getHashes( [], { "clientId": clientId,'productId': productId } )
 | 
|---|
 | 482 |         if len(products) != 1:
 | 
|---|
 | 483 |             print("failed: opsi client ({0}) product ({1}) combination does not exist".format(clientId, productId))
 | 
|---|
 | 484 |             return False            
 | 
|---|
 | 485 |         for i in sorted(products, key=self.__getClientId):
 | 
|---|
 | 486 |             i['version'] = self.__getVersionString(i)
 | 
|---|
 | 487 |             if i.get('installationStatus') == 'installed':
 | 
|---|
 | 488 |                 if productVersion != i['version']:
 | 
|---|
 | 489 |                     i['proposedAction'] = 'update'
 | 
|---|
 | 490 |                     output.setStatus(Nrpe.WARNING)
 | 
|---|
 | 491 |                 else:
 | 
|---|
 | 492 |                     i['proposedAction'] = 'None'
 | 
|---|
 | 493 |                     output.setStatus(Nrpe.OK)
 | 
|---|
 | 494 |                 output.setMessage('{version} (proposed action={proposedAction})'.format(**i))
 | 
|---|
 | 495 |             else:
 | 
|---|
 | 496 |                 i['proposedAction'] = 'install'
 | 
|---|
 | 497 |                 output.setStatus(Nrpe.CRITICAL, 'not installed (proposed action={proposedAction})'.format(**i))
 | 
|---|
 | 498 |                 self.logger.debug('{clientId}: {actionRequest} {installationStatus} {version}'.format(**i))
 | 
|---|
 | 499 |         return output.finalize()
 | 
|---|
 | 500 | 
 | 
|---|
 | 501 | 
 | 
|---|
 | 502 |     def clientLastSeen(self, clientId):
 | 
|---|
 | 503 |         """
 | 
|---|
 | 504 |         < 1 day: OK
 | 
|---|
 | 505 |         < 2 days: WARNING
 | 
|---|
 | 506 |         otherwise: CRITICAL
 | 
|---|
 | 507 |         """
 | 
|---|
 | 508 |         output = Output(self.nagios)
 | 
|---|
 | 509 |         if not self.exists(clientId):
 | 
|---|
 | 510 |             output.setMessage("failed: opsi client {0} does not exist".format(clientId))
 | 
|---|
 | 511 |             return output.finalize()
 | 
|---|
 | 512 |         host = self.rpc.host_getHashes( [], {"id":clientId} )[0]
 | 
|---|
 | 513 |         lastSeen = dateparser.parse(host["lastSeen"])
 | 
|---|
 | 514 |         output.setMessage(str(lastSeen))
 | 
|---|
 | 515 |         diff = datetime.now() - lastSeen
 | 
|---|
 | 516 |         output.setMessage('{0} ({1} ago)'.format(str(lastSeen), diff))
 | 
|---|
 | 517 |         if diff < timedelta(1):
 | 
|---|
 | 518 |             output.setStatus(Nrpe.OK)
 | 
|---|
 | 519 |         elif diff < timedelta(2):
 | 
|---|
 | 520 |             output.setStatus(Nrpe.WARNING)
 | 
|---|
 | 521 |         else:
 | 
|---|
 | 522 |             output.setStatus(Nrpe.CRITICAL)
 | 
|---|
 | 523 |         return output.finalize()
 | 
|---|
 | 524 | 
 | 
|---|
 | 525 | 
 | 
|---|
 | 526 | 
 | 
|---|
| [1047] | 527 | if __name__ == '__main__':
 | 
|---|
| [1110] | 528 |     logging.basicConfig(format='%(message)s')
 | 
|---|
 | 529 |     logger = logging.getLogger(__name__)
 | 
|---|
 | 530 |     logger.setLevel(logging.INFO)
 | 
|---|
 | 531 |     
 | 
|---|
| [1239] | 532 |     parser = argparse.ArgumentParser(
 | 
|---|
 | 533 |         description='Command line tool for OPSI configuration.',
 | 
|---|
 | 534 |         epilog=HelpEpilog
 | 
|---|
 | 535 |     )
 | 
|---|
| [1063] | 536 | 
 | 
|---|
| [1255] | 537 |     parser.add_argument('--debug', action='store_true', help="enable debugging output")
 | 
|---|
| [1234] | 538 |     parser.add_argument('--nagios', action='store_true', help='output in Nagios NRPE format')
 | 
|---|
| [1028] | 539 | 
 | 
|---|
| [1110] | 540 |     parser_url = parser.add_mutually_exclusive_group(required=True)
 | 
|---|
 | 541 |     parser_url.add_argument( '--url', help="OPSI Server JSON-RPC url, in following format: " + UrlJsonRpc )
 | 
|---|
 | 542 |     
 | 
|---|
 | 543 |     parser_url.add_argument( '--server', help="OPSI Server (instead of URL)" )
 | 
|---|
 | 544 |     username_default=os.getlogin()
 | 
|---|
 | 545 |     
 | 
|---|
 | 546 |     parser.add_argument( '--username', help="username (instead of URL), default: " + username_default, default=username_default )
 | 
|---|
 | 547 |     parser.add_argument( '--password', help="password (instead of URL)" )
 | 
|---|
 | 548 |     
 | 
|---|
| [1050] | 549 |     subparsers = parser.add_subparsers(title='subcommands',
 | 
|---|
 | 550 |         description='valid subcommands',
 | 
|---|
 | 551 |         help='additional help',
 | 
|---|
 | 552 |         dest='subcommand' )
 | 
|---|
| [1047] | 553 | 
 | 
|---|
| [1050] | 554 |     parser_clean = subparsers.add_parser('clean', help='remove all product states from a opsi client' )
 | 
|---|
 | 555 |     parser_clean.add_argument( 'src', help="source opsi client to clean" )
 | 
|---|
| [1174] | 556 | 
 | 
|---|
 | 557 |     parser_copy = subparsers.add_parser('copy', help='copy/create a opsi client from a template opsi client')
 | 
|---|
| [1050] | 558 |     parser_copy.add_argument( 'src', help="source/template opsi client" )
 | 
|---|
 | 559 |     parser_copy.add_argument( 'dst', help="opsi client to be created" )
 | 
|---|
 | 560 |     parser_copy.add_argument( '--ip', help="IP address of the new opsi client" )
 | 
|---|
 | 561 |     parser_copy.add_argument( '--mac', help="MAC address of the new opsi client" )
 | 
|---|
 | 562 |     parser_copy.add_argument( '--depot', help="depot server the new opsi client should be located" )
 | 
|---|
| [1088] | 563 |     #parser_copy.add_argument( '--no-properties', action='store_false', help="don't copy product properties" )
 | 
|---|
| [1174] | 564 | 
 | 
|---|
| [1239] | 565 |     parser_createBareosConfigFiles = subparsers.add_parser(
 | 
|---|
 | 566 |         'createBareosConfigFiles',
 | 
|---|
 | 567 |         help='create Bareos config files for all clients that have winbareos installed',
 | 
|---|
 | 568 |         formatter_class=argparse.ArgumentDefaultsHelpFormatter
 | 
|---|
 | 569 |     )
 | 
|---|
 | 570 |     parser_createBareosConfigFiles.add_argument(
 | 
|---|
 | 571 |         '--defaultjobdefs',
 | 
|---|
 | 572 |         metavar='JobDefs',
 | 
|---|
 | 573 |         default='DefaultJob',
 | 
|---|
 | 574 |         help="use this JobDefs if no other is defined for a client"
 | 
|---|
 | 575 |     )
 | 
|---|
 | 576 |     parser_createBareosConfigFiles.add_argument(
 | 
|---|
 | 577 |         '--defaultfileset',
 | 
|---|
 | 578 |         metavar='FileSet',
 | 
|---|
 | 579 |         help="use this FileSet if no other is defined for a client"
 | 
|---|
 | 580 |     )
 | 
|---|
| [1110] | 581 | 
 | 
|---|
 | 582 |     parser_exists = subparsers.add_parser('exists', help='check, if a opsi clients exists' )
 | 
|---|
 | 583 |     parser_exists.add_argument( 'src', help="source opsi client" )
 | 
|---|
 | 584 |     #parser_list = subparsers.add_parser('list', help='list all opsi clients' )
 | 
|---|
| [1174] | 585 | 
 | 
|---|
| [1110] | 586 |     parser_listClients = subparsers.add_parser('listClients', help='list opsi clients')
 | 
|---|
 | 587 |     parser_listClients.add_argument( '--product', help="only list clients, that have product installed" )
 | 
|---|
| [1174] | 588 | 
 | 
|---|
| [1110] | 589 |     parser_info = subparsers.add_parser('info', help='print information about a opsi client' )
 | 
|---|
 | 590 |     parser_info.add_argument( 'src', help="opsi client" )
 | 
|---|
| [1234] | 591 |     
 | 
|---|
 | 592 |     parser_clientLastSeen = subparsers.add_parser('clientLastSeen', help='print information about a opsi client' )
 | 
|---|
 | 593 |     parser_clientLastSeen.add_argument( 'client', help="opsi client" )
 | 
|---|
| [1174] | 594 | 
 | 
|---|
| [1234] | 595 |     parser_listInstalled = subparsers.add_parser('listInstalled', help='check if product is installed on client')
 | 
|---|
 | 596 |     parser_listInstalled.add_argument('product', help='opsi product')
 | 
|---|
 | 597 | 
 | 
|---|
 | 598 |     parser_isInstalled = subparsers.add_parser('isInstalled', help='check if product is installed on client')
 | 
|---|
 | 599 |     parser_isInstalled.add_argument('client', help='opsi client')
 | 
|---|
 | 600 |     parser_isInstalled.add_argument('product', help='opsi product')
 | 
|---|
 | 601 | 
 | 
|---|
| [1117] | 602 |     parser_update = subparsers.add_parser('update', help='update/create a opsi client')
 | 
|---|
| [1177] | 603 |     parser_update.add_argument( 'src', help="opsi client to be created/updated" )
 | 
|---|
 | 604 |     parser_update.add_argument( '--ip', help="IP address of the opsi client" )
 | 
|---|
 | 605 |     parser_update.add_argument( '--mac', help="MAC address of the opsi client" )
 | 
|---|
 | 606 |     parser_update.add_argument( '--description', help="a description of the client" )
 | 
|---|
 | 607 |     parser_update.add_argument( '--notes', help="notes about the client" )
 | 
|---|
 | 608 |     parser_update.add_argument( '--inventory', help="inventory number" )
 | 
|---|
 | 609 |     parser_update.add_argument( '--depot', help="depot server the opsi client should be located" )
 | 
|---|
 | 610 | 
 | 
|---|
| [1047] | 611 |     args = parser.parse_args()
 | 
|---|
| [1177] | 612 | 
 | 
|---|
| [1110] | 613 |     if args.debug:
 | 
|---|
 | 614 |         logger.setLevel(logging.DEBUG)
 | 
|---|
 | 615 | 
 | 
|---|
 | 616 |     url=args.url
 | 
|---|
 | 617 |     if (not url):
 | 
|---|
 | 618 |         if args.server:
 | 
|---|
 | 619 |             account=""
 | 
|---|
 | 620 |             if args.username and args.password:
 | 
|---|
 | 621 |                 account=args.username + ":" + args.password + "@"
 | 
|---|
 | 622 |             elif args.username:
 | 
|---|
 | 623 |                 account=args.username + "@"
 | 
|---|
 | 624 |             url="https://" + account + args.server + ":4447/rpc"
 | 
|---|
 | 625 |         else:
 | 
|---|
 | 626 |             parser.error( "argument --url is required" )
 | 
|---|
| [1174] | 627 | 
 | 
|---|
| [1234] | 628 |     opsi=OpsiRpc(url, args.debug, args.nagios)
 | 
|---|
| [1174] | 629 | 
 | 
|---|
| [1051] | 630 |     result = True
 | 
|---|
 | 631 | 
 | 
|---|
| [1174] | 632 |     try:
 | 
|---|
 | 633 |         if args.subcommand == "clean":
 | 
|---|
 | 634 |             result = opsi.clean( args.src )
 | 
|---|
 | 635 |         elif args.subcommand == "copy": 
 | 
|---|
 | 636 |             result = opsi.copyClient( args.src, args.dst, args.ip, args.mac, args.depot )
 | 
|---|
| [1239] | 637 |         elif args.subcommand == "createBareosConfigFiles": 
 | 
|---|
 | 638 |             result = opsi.createBareosConfigFiles(args.defaultjobdefs, args.defaultfileset)
 | 
|---|
| [1174] | 639 |         elif args.subcommand == "exists":
 | 
|---|
 | 640 |             result = opsi.exists( args.src )
 | 
|---|
 | 641 |         elif args.subcommand == "list":
 | 
|---|
 | 642 |             result = opsi.list()
 | 
|---|
 | 643 |         elif args.subcommand == "listClients":
 | 
|---|
 | 644 |             result = opsi.listClients( args.product )
 | 
|---|
 | 645 |         elif args.subcommand == "info":
 | 
|---|
 | 646 |             result = opsi.info( args.src )
 | 
|---|
 | 647 |         elif args.subcommand == "update": 
 | 
|---|
| [1177] | 648 |             result = opsi.updateClient( args.src, None, args.description, args.notes, args.inventory, args.mac, args.ip, args.depot )
 | 
|---|
| [1234] | 649 |         elif args.subcommand == 'listInstalled': 
 | 
|---|
 | 650 |             result = opsi.listInstalled(args.product)
 | 
|---|
 | 651 |         elif args.subcommand == 'isInstalled': 
 | 
|---|
 | 652 |             result = opsi.isInstalled(args.client, args.product)
 | 
|---|
 | 653 |         elif args.subcommand == 'clientLastSeen': 
 | 
|---|
 | 654 |             result = opsi.clientLastSeen(args.client)
 | 
|---|
| [1174] | 655 |         else:
 | 
|---|
| [1234] | 656 |             print("not yet implemented")
 | 
|---|
| [1174] | 657 |     except IOError as e:
 | 
|---|
 | 658 |         result = False
 | 
|---|
 | 659 |         # connection refused
 | 
|---|
| [1234] | 660 |         print("failed:", e)
 | 
|---|
| [1050] | 661 | 
 | 
|---|
| [1234] | 662 |     if args.debug: print(result)
 | 
|---|
| [1174] | 663 | 
 | 
|---|
| [1051] | 664 |     if result:
 | 
|---|
 | 665 |         exit(0)
 | 
|---|
 | 666 |     else:
 | 
|---|
 | 667 |         exit(1)
 | 
|---|