source: opsi/server/dass-opsi-tools/usr/bin/opsiclient

Last change on this file was 1255, checked in by joergs, 2 years ago

adapt outpput of listInstalled

  • Property svn:executable set to *
File size: 26.4 KB
Line 
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
7from __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
22import argparse
23from   datetime import datetime, timedelta
24from   dateutil import parser as dateparser
25import logging
26import os
27from   pprint import pprint, pformat
28import sys
29import ssl
30import time
31
32from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
33from tinyrpc.transports.http import HttpPostClientTransport
34from tinyrpc import RPCClient
35
36UrlJsonRpc="https://<username>:<password>@opsi:4447/rpc"
37
38HelpEpilog="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
40class 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
56class 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
78class 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
527if __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)
Note: See TracBrowser for help on using the repository browser.