| 1 | #!/usr/bin/env python
 | 
|---|
| 2 | 
 | 
|---|
| 3 | # -*- coding: utf-8 -*-
 | 
|---|
| 4 | 
 | 
|---|
| 5 | """ospi-client: performs operation for opsi clients on opsi server via JSON-RPC."""
 | 
|---|
| 6 | 
 | 
|---|
| 7 | from __future__ import print_function
 | 
|---|
| 8 | 
 | 
|---|
| 9 | __author__ = "Joerg Steffens"
 | 
|---|
| 10 | __copyright__ = "Copyright 2012-2021, dass IT GmbH"
 | 
|---|
| 11 | __license__ = "GPL"
 | 
|---|
| 12 | __version__ = "1.3"
 | 
|---|
| 13 | __email__ = "joerg.steffens@dass-it.de"
 | 
|---|
| 14 | 
 | 
|---|
| 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 " + \
 | 
|---|
| 20 |         #computername + " " + depotName)
 | 
|---|
| 21 | 
 | 
|---|
| 22 | import argparse
 | 
|---|
| 23 | from   datetime import datetime, timedelta
 | 
|---|
| 24 | from   dateutil import parser as dateparser
 | 
|---|
| 25 | import logging
 | 
|---|
| 26 | import os
 | 
|---|
| 27 | from   pprint import pprint, pformat
 | 
|---|
| 28 | import sys
 | 
|---|
| 29 | import ssl
 | 
|---|
| 30 | import time
 | 
|---|
| 31 | 
 | 
|---|
| 32 | from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
 | 
|---|
| 33 | from tinyrpc.transports.http import HttpPostClientTransport
 | 
|---|
| 34 | from tinyrpc import RPCClient
 | 
|---|
| 35 | 
 | 
|---|
| 36 | UrlJsonRpc="https://<username>:<password>@opsi:4447/rpc"
 | 
|---|
| 37 | 
 | 
|---|
| 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."
 | 
|---|
| 39 | 
 | 
|---|
| 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 | 
 | 
|---|
| 78 | class OpsiRpc:
 | 
|---|
| 79 | 
 | 
|---|
| 80 |     UrlJsonRpcDefault="https://opsi:4447/rpc"
 | 
|---|
| 81 | 
 | 
|---|
| 82 |     ProductAttributesCopy = ['actionRequest','actionResult','installationStatus','packageVersion','productVersion']
 | 
|---|
| 83 | 
 | 
|---|
| 84 |     def __init__(self, urlJsonRpc = UrlJsonRpcDefault, debug=False, nagios=False):
 | 
|---|
| 85 |         self.logger=logging.getLogger(__name__)
 | 
|---|
| 86 |         self.debug=debug
 | 
|---|
| 87 |         self.nagios=nagios
 | 
|---|
| 88 |         self.urlJsonRpc=urlJsonRpc
 | 
|---|
| 89 |         self.rpc = RPCClient(JSONRPCProtocol(), HttpPostClientTransport(self.urlJsonRpc, verify=False)).get_proxy()
 | 
|---|
| 90 | 
 | 
|---|
| 91 |         self.logger.debug( "initialized: " + self.urlJsonRpc )
 | 
|---|
| 92 |         self.logger.debug(dir(self.rpc))
 | 
|---|
| 93 | 
 | 
|---|
| 94 | 
 | 
|---|
| 95 |     def list(self):
 | 
|---|
| 96 |         exceptions = []
 | 
|---|
| 97 |         if 'jsonrpc' in sys.modules:
 | 
|---|
| 98 |             exceptions = [ jsonrpc.json.JSONDecodeException ]
 | 
|---|
| 99 |         try:
 | 
|---|
| 100 |             print( "\n".join( self.rpc.getClientIds_list() ) )
 | 
|---|
| 101 |         except exceptions as e:
 | 
|---|
| 102 |             self.logger.debug( pformat(self.rpc.getClientIds_list()) )
 | 
|---|
| 103 |             self.logger.exception( "failed" )
 | 
|---|
| 104 |             return True
 | 
|---|
| 105 | 
 | 
|---|
| 106 | 
 | 
|---|
| 107 |     def getClientsWithProduct( self, product ):
 | 
|---|
| 108 |         return self.rpc.productOnClient_getObjects( [], { "productId": product, "installationStatus": "installed" } )
 | 
|---|
| 109 | 
 | 
|---|
| 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)
 | 
|---|
| 122 | 
 | 
|---|
| 123 |     def listClients( self, product ):
 | 
|---|
| 124 |         if product:
 | 
|---|
| 125 |             for client in self.getClientsWithProduct( product ):
 | 
|---|
| 126 |                 print(client['clientId'])
 | 
|---|
| 127 |         else:
 | 
|---|
| 128 |             return self.list()
 | 
|---|
| 129 |         return True
 | 
|---|
| 130 | 
 | 
|---|
| 131 |     def exists(self, src):
 | 
|---|
| 132 |         return len( self.rpc.host_getObjects( [], {"id":src} ) ) == 1
 | 
|---|
| 133 | 
 | 
|---|
| 134 |     def info(self, src):
 | 
|---|
| 135 |         if not self.exists( src ):
 | 
|---|
| 136 |             print("failed: opsi client", src, "does not exist")
 | 
|---|
| 137 |             return False
 | 
|---|
| 138 |         print(src + ":")
 | 
|---|
| 139 |         host = self.rpc.host_getHashes( [], {"id":src} )[0]
 | 
|---|
| 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))
 | 
|---|
| 147 | 
 | 
|---|
| 148 |         print("  products:")
 | 
|---|
| 149 |         products = self.getProductOnClient( src, [] )
 | 
|---|
| 150 |         for i in products:
 | 
|---|
| 151 |             print("    " + i['productId'] + ":")
 | 
|---|
| 152 |             print("      " + i['installationStatus'], "(", end='')
 | 
|---|
| 153 |             if i['actionRequest']:
 | 
|---|
| 154 |                 print(i['actionRequest'], end='')
 | 
|---|
| 155 |                 if i['actionProgress']:
 | 
|---|
| 156 |                     print(i['actionProgress'], end='')
 | 
|---|
| 157 |             print(")")
 | 
|---|
| 158 |             print("      ", end='')
 | 
|---|
| 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 )
 | 
|---|
| 167 | 
 | 
|---|
| 168 |         products = self.rpc.productPropertyState_getObjects( [], { 'objectId': src } )
 | 
|---|
| 169 |         self.rpc.productPropertyState_deleteObjects( products )
 | 
|---|
| 170 | 
 | 
|---|
| 171 |         if self.debug:
 | 
|---|
| 172 |             pprint( self.getProductOnClient( src ) )
 | 
|---|
| 173 |         return True
 | 
|---|
| 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 | 
 | 
|---|
| 183 |     def createClient(self, name, opsiHostKey, description, notes, hardwareAddress, ipAddress):
 | 
|---|
| 184 |         #    self.rpc.host_createOpsiClient( name, opsiHostKey, description, notes, hardwareAddress, ipAddress )
 | 
|---|
| 185 |         self.updateClient( name, opsiHostKey, description, notes, None, hardwareAddress, ipAddress )
 | 
|---|
| 186 | 
 | 
|---|
| 187 |     def deleteClient(self, name):
 | 
|---|
| 188 |         self.rpc.host_delete( name )
 | 
|---|
| 189 | 
 | 
|---|
| 190 |     def updateClient(self, src, opsiHostKey = None, description = None, notes = None, inventoryNumber = None, hardwareAddress = None, ipAddress = None, depot = None ):
 | 
|---|
| 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:
 | 
|---|
| 200 |             obj['notes'] = notes
 | 
|---|
| 201 |         if inventoryNumber:
 | 
|---|
| 202 |             obj['inventoryNumber'] = inventoryNumber
 | 
|---|
| 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)
 | 
|---|
| 212 | 
 | 
|---|
| 213 |         if depot:
 | 
|---|
| 214 |             self.clientSetDepot(src,depot)
 | 
|---|
| 215 |         return True
 | 
|---|
| 216 | 
 | 
|---|
| 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()
 | 
|---|
| 225 | 
 | 
|---|
| 226 |     def clientSetDepot(self, name, depot):
 | 
|---|
| 227 |         self.rpc.configState_create( "clientconfig.depot.id", name, depot )
 | 
|---|
| 228 | 
 | 
|---|
| 229 |     def copyClient( self, src, dst, ipAddress = None, hardwareAddress = None, depot = None, description = "", copyProperties = True ):
 | 
|---|
| 230 | 
 | 
|---|
| 231 |         print("create/update", dst, "from template", src + ":", end='')
 | 
|---|
| 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)
 | 
|---|
| 248 | 
 | 
|---|
| 249 |         if depot:
 | 
|---|
| 250 |             self.clientSetDepot(dst,depot)
 | 
|---|
| 251 | 
 | 
|---|
| 252 |         if self.debug:
 | 
|---|
| 253 |             pprint( self.getProductOnClient( src ) )
 | 
|---|
| 254 |         self.copyProductOnClient( src, dst )
 | 
|---|
| 255 |         if copyProperties:
 | 
|---|
| 256 |             if self.debug:
 | 
|---|
| 257 |                 print("copy product properties")
 | 
|---|
| 258 |             if not depot:
 | 
|---|
| 259 |                 # get default Properties from Master Depot Server (OpsiConfigserver)
 | 
|---|
| 260 |                 depot = self.getOpsiConfigserverId()
 | 
|---|
| 261 |             self.copyProductPropertyState( src, dst, depot )
 | 
|---|
| 262 |         print("done")
 | 
|---|
| 263 |         return True
 | 
|---|
| 264 | 
 | 
|---|
| 265 |     def getProductOnClient( self, client, attributes = ProductAttributesCopy ):
 | 
|---|
| 266 |         return self.rpc.productOnClient_getHashes( [], { 'clientId': client } )
 | 
|---|
| 267 | 
 | 
|---|
| 268 |     def copyProductOnClient( self, src, dst, attributes = ProductAttributesCopy ):
 | 
|---|
| 269 |         products_src = self.rpc.productOnClient_getHashes( attributes, { 'clientId': src } )
 | 
|---|
| 270 |         products_dst = []
 | 
|---|
| 271 |         for i in products_src:
 | 
|---|
| 272 |             if self.debug: 
 | 
|---|
| 273 |                 print(i['productId'])
 | 
|---|
| 274 |                 pprint( i )
 | 
|---|
| 275 |             i['clientId'] = dst
 | 
|---|
| 276 |             products_dst.append(i)
 | 
|---|
| 277 |         self.rpc.productOnClient_createObjects( products_dst )
 | 
|---|
| 278 |         if self.debug:
 | 
|---|
| 279 |             pprint( self.getProductOnClient( dst ) )
 | 
|---|
| 280 | 
 | 
|---|
| 281 | 
 | 
|---|
| 282 |     def getProductPropertyState( self, client, attributes = [] ):
 | 
|---|
| 283 |         return self.rpc.productPropertyState_getHashes( [], { 'objectId': client } )
 | 
|---|
| 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 = []
 | 
|---|
| 291 |         productProperties_src = self.getProductPropertyState( src, attributes )
 | 
|---|
| 292 |         productProperties_dst = []
 | 
|---|
| 293 |         for i in productProperties_src:
 | 
|---|
| 294 |             use_default=False
 | 
|---|
| 295 |             default_value=None
 | 
|---|
| 296 |             for j in productProperties_default:
 | 
|---|
| 297 |                 if i['productId'] == j['productId'] and i["propertyId"] == j["propertyId"]:
 | 
|---|
| 298 |                     default_value = j['values']
 | 
|---|
| 299 |                     if i['values'] == j['values']:
 | 
|---|
| 300 |                         use_default=True
 | 
|---|
| 301 |             if self.debug:
 | 
|---|
| 302 |                 print(i['productId'], "-", i["propertyId"] + ": ", pformat(i["values"]), end='')
 | 
|---|
| 303 |                 if use_default:
 | 
|---|
| 304 |                     print("(use default)")
 | 
|---|
| 305 |                 else:
 | 
|---|
| 306 |                     print("(set, default:", default_value, ")")
 | 
|---|
| 307 |             if not use_default:
 | 
|---|
| 308 |                 i['objectId'] = dst
 | 
|---|
| 309 |                 productProperties_dst.append(i)
 | 
|---|
| 310 |         self.rpc.productPropertyState_createObjects( productProperties_dst )
 | 
|---|
| 311 |         if self.debug:
 | 
|---|
| 312 |             pprint( self.getProductPropertyState( dst ) )
 | 
|---|
| 313 | 
 | 
|---|
| 314 | 
 | 
|---|
| 315 |     def getClientProductProperty( self, client, product ):
 | 
|---|
| 316 |         return self.rpc.getProductProperties_hash( product, [ client ] )
 | 
|---|
| 317 | 
 | 
|---|
| 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 | 
 | 
|---|
| 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 | 
 | 
|---|
| 343 |     def write_client_conf( self, fd, client, properties ):
 | 
|---|
| 344 |         #Client {
 | 
|---|
| 345 |         #Name = ting-fd
 | 
|---|
| 346 |         #Address = ting.dass-it
 | 
|---|
| 347 |         #FDPort = 9102
 | 
|---|
| 348 |         #Password = "D5w2V5w6B8a9H5Z"
 | 
|---|
| 349 |         #Catalog = MyCatalog
 | 
|---|
| 350 |         #File Retention = 6 months
 | 
|---|
| 351 |         #Job Retention = 6 months
 | 
|---|
| 352 |         #AutoPrune = yes
 | 
|---|
| 353 |         #}
 | 
|---|
| 354 |         params = [ 'catalog', "FDPort", "FileRetention", "JobRetention", "AutoPrune" ]
 | 
|---|
| 355 |         fd.write( "Client {\n" )
 | 
|---|
| 356 |         fd.write( '  Name     = "' + properties['filedaemon_full_name'] + '"' + "\n" )
 | 
|---|
| 357 |         fd.write( '  Address  = "' + properties['filedaemon_client_address'] + '"' + "\n" )
 | 
|---|
| 358 |         # ipAddress: method host_getObjects [] '{"id":client['clientId']}'
 | 
|---|
| 359 |         #print("  # Address =", ipAddress)
 | 
|---|
| 360 |         fd.write( '  Password = "' + properties['filedaemon_full_password'] + '"' + "\n" )
 | 
|---|
| 361 |         for i in params:
 | 
|---|
| 362 |             self.write_value_conf(fd, i, properties)
 | 
|---|
| 363 |         fd.write( "}\n")
 | 
|---|
| 364 |         fd.write( "\n" )
 | 
|---|
| 365 | 
 | 
|---|
| 366 | 
 | 
|---|
| 367 |     def write_job_conf(self, fd, client, properties, defaultjobdefs, defaultfileset):
 | 
|---|
| 368 |         #Job {
 | 
|---|
| 369 |         #FileSet = "tingfileset"
 | 
|---|
| 370 |         #Name = "ting"
 | 
|---|
| 371 |         #Client = ting-fd
 | 
|---|
| 372 |         #JobDefs = "LaptopJob"
 | 
|---|
| 373 |         ## Write Bootstrap = "/var/lib/bacula/ting.bsr"
 | 
|---|
| 374 |         #}
 | 
|---|
| 375 |         params = [ "JobDefs", "FileSet" ]
 | 
|---|
| 376 |         fd.write( "Job {" + "\n" )
 | 
|---|
| 377 |         fd.write( '  Name    = "' + client['clientId'] + '-job"' + "\n" )
 | 
|---|
| 378 |         fd.write( '  Client  = "' + properties['filedaemon_full_name'] + '"' + "\n" )
 | 
|---|
| 379 |         self.write_value_conf(fd, 'JobDefs', properties, defaultjobdefs)
 | 
|---|
| 380 |         self.write_value_conf(fd, 'FileSet', properties, defaultfileset)
 | 
|---|
| 381 |         fd.write( "}" + "\n" )
 | 
|---|
| 382 |         fd.write( "\n" )
 | 
|---|
| 383 | 
 | 
|---|
| 384 | 
 | 
|---|
| 385 |     def write_config_file_header( self, fd ):
 | 
|---|
| 386 |         try:
 | 
|---|
| 387 |             fd.write( "#\n" )
 | 
|---|
| 388 |             fd.write( "# automatically generated at {0}\n".format( time.asctime() ) )
 | 
|---|
| 389 |             fd.write( "#\n\n" )
 | 
|---|
| 390 |         except BaseException as e:
 | 
|---|
| 391 |             self.logger.exception( "failed to create files" )
 | 
|---|
| 392 |             return False
 | 
|---|
| 393 |         return True
 | 
|---|
| 394 | 
 | 
|---|
| 395 | 
 | 
|---|
| 396 | 
 | 
|---|
| 397 |     def createBareosConfigFiles(self, defaultjobdefs, defaultfileset):
 | 
|---|
| 398 |         bareosDirConfigPath = '/etc/bareos/bareos-dir.d/'
 | 
|---|
| 399 |         clientsWithBacula=self.getClientsWithProduct('winbareos')
 | 
|---|
| 400 |         if clientsWithBacula:
 | 
|---|
| 401 |             try:
 | 
|---|
| 402 |                 configfile = bareosDirConfigPath + 'client/opsi-clients-generated.conf'
 | 
|---|
| 403 |                 file_opsi_clients = open(configfile, 'w')
 | 
|---|
| 404 |                 self.write_config_file_header( file_opsi_clients )
 | 
|---|
| 405 |             except (BaseException, IOError) as e:
 | 
|---|
| 406 |                 self.logger.exception( "failed to create configuration file {}".format(configfile) )
 | 
|---|
| 407 |                 return False
 | 
|---|
| 408 | 
 | 
|---|
| 409 |             try:
 | 
|---|
| 410 |                 configfile = bareosDirConfigPath + 'job/opsi-jobs-generated.conf'
 | 
|---|
| 411 |                 file_opsi_jobs = open(configfile, 'w')
 | 
|---|
| 412 |                 self.write_config_file_header( file_opsi_jobs )
 | 
|---|
| 413 |             except (BaseException, IOError) as e:
 | 
|---|
| 414 |                 self.logger.exception( "failed to create configuration file {}".format(configfile) )
 | 
|---|
| 415 |                 return False
 | 
|---|
| 416 | 
 | 
|---|
| 417 |             for client in clientsWithBacula:
 | 
|---|
| 418 |                 clientId = client['clientId']
 | 
|---|
| 419 |                 try:
 | 
|---|
| 420 |                     clientBaculaProperties=self.getClientProductProperty( clientId, 'winbareos' )
 | 
|---|
| 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 )
 | 
|---|
| 426 |                         self.write_client_conf(file_opsi_clients, client, clientBaculaProperties)
 | 
|---|
| 427 |                         self.write_job_conf(file_opsi_jobs, client, clientBaculaProperties, defaultjobdefs, defaultfileset)
 | 
|---|
| 428 |                         self.logger.info( "%s: OK" % clientId )
 | 
|---|
| 429 |                     else:
 | 
|---|
| 430 |                         self.logger.warn( "%s: failed: no product properties defined" %(clientId) )
 | 
|---|
| 431 |             return True
 | 
|---|
| 432 | 
 | 
|---|
| 433 |     def __getVersionString(self, product):
 | 
|---|
| 434 |         return '{productVersion}-{packageVersion}'.format(**product)
 | 
|---|
| 435 | 
 | 
|---|
| 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
 | 
|---|
| 442 | 
 | 
|---|
| 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'
 | 
|---|
| 457 |                 print('{clientId}: {version} (proposed action={proposedAction}) request={actionRequest}, result={actionResult}'.format(**i))
 | 
|---|
| 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 | 
 | 
|---|
| 527 | if __name__ == '__main__':
 | 
|---|
| 528 |     logging.basicConfig(format='%(message)s')
 | 
|---|
| 529 |     logger = logging.getLogger(__name__)
 | 
|---|
| 530 |     logger.setLevel(logging.INFO)
 | 
|---|
| 531 |     
 | 
|---|
| 532 |     parser = argparse.ArgumentParser(
 | 
|---|
| 533 |         description='Command line tool for OPSI configuration.',
 | 
|---|
| 534 |         epilog=HelpEpilog
 | 
|---|
| 535 |     )
 | 
|---|
| 536 | 
 | 
|---|
| 537 |     parser.add_argument('--debug', action='store_true', help="enable debugging output")
 | 
|---|
| 538 |     parser.add_argument('--nagios', action='store_true', help='output in Nagios NRPE format')
 | 
|---|
| 539 | 
 | 
|---|
| 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 |     
 | 
|---|
| 549 |     subparsers = parser.add_subparsers(title='subcommands',
 | 
|---|
| 550 |         description='valid subcommands',
 | 
|---|
| 551 |         help='additional help',
 | 
|---|
| 552 |         dest='subcommand' )
 | 
|---|
| 553 | 
 | 
|---|
| 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" )
 | 
|---|
| 556 | 
 | 
|---|
| 557 |     parser_copy = subparsers.add_parser('copy', help='copy/create a opsi client from a template opsi client')
 | 
|---|
| 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" )
 | 
|---|
| 563 |     #parser_copy.add_argument( '--no-properties', action='store_false', help="don't copy product properties" )
 | 
|---|
| 564 | 
 | 
|---|
| 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 |     )
 | 
|---|
| 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' )
 | 
|---|
| 585 | 
 | 
|---|
| 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" )
 | 
|---|
| 588 | 
 | 
|---|
| 589 |     parser_info = subparsers.add_parser('info', help='print information about a opsi client' )
 | 
|---|
| 590 |     parser_info.add_argument( 'src', help="opsi client" )
 | 
|---|
| 591 |     
 | 
|---|
| 592 |     parser_clientLastSeen = subparsers.add_parser('clientLastSeen', help='print information about a opsi client' )
 | 
|---|
| 593 |     parser_clientLastSeen.add_argument( 'client', help="opsi client" )
 | 
|---|
| 594 | 
 | 
|---|
| 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 | 
 | 
|---|
| 602 |     parser_update = subparsers.add_parser('update', help='update/create a opsi client')
 | 
|---|
| 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 | 
 | 
|---|
| 611 |     args = parser.parse_args()
 | 
|---|
| 612 | 
 | 
|---|
| 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" )
 | 
|---|
| 627 | 
 | 
|---|
| 628 |     opsi=OpsiRpc(url, args.debug, args.nagios)
 | 
|---|
| 629 | 
 | 
|---|
| 630 |     result = True
 | 
|---|
| 631 | 
 | 
|---|
| 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 )
 | 
|---|
| 637 |         elif args.subcommand == "createBareosConfigFiles": 
 | 
|---|
| 638 |             result = opsi.createBareosConfigFiles(args.defaultjobdefs, args.defaultfileset)
 | 
|---|
| 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": 
 | 
|---|
| 648 |             result = opsi.updateClient( args.src, None, args.description, args.notes, args.inventory, args.mac, args.ip, args.depot )
 | 
|---|
| 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)
 | 
|---|
| 655 |         else:
 | 
|---|
| 656 |             print("not yet implemented")
 | 
|---|
| 657 |     except IOError as e:
 | 
|---|
| 658 |         result = False
 | 
|---|
| 659 |         # connection refused
 | 
|---|
| 660 |         print("failed:", e)
 | 
|---|
| 661 | 
 | 
|---|
| 662 |     if args.debug: print(result)
 | 
|---|
| 663 | 
 | 
|---|
| 664 |     if result:
 | 
|---|
| 665 |         exit(0)
 | 
|---|
| 666 |     else:
 | 
|---|
| 667 |         exit(1)
 | 
|---|