1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3# BAREOS - Backup Archiving REcovery Open Sourced 4# 5# Copyright (C) 2020-2020 Bareos GmbH & Co. KG 6# 7# This program is Free Software; you can redistribute it and/or 8# modify it under the terms of version three of the GNU Affero General Public 9# License as published by the Free Software Foundation, which is 10# listed in the file LICENSE. 11# 12# This program is distributed in the hope that it will be useful, but 13# WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15# Affero General Public License for more details. 16# 17# You should have received a copy of the GNU Affero General Public License 18# along with this program; if not, write to the Free Software 19# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 20# 02110-1301, USA. 21# 22# Author: Maik Aussendorf 23# 24 25from datetime import datetime, timedelta 26from typing import Optional, List 27from fastapi import Depends, FastAPI, HTTPException, status, Response, Path, Body, Query 28from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm 29from jose import JWTError, jwt 30from passlib.context import CryptContext 31from pydantic import BaseModel, Field, PositiveInt 32from enum import Enum 33import pathlib 34from packaging import version 35import bareos.bsock 36import configparser 37import yaml 38from bareosRestapiModels import * 39 40# Read config from api.ini 41config = configparser.ConfigParser() 42config.read("api.ini") 43 44CONFIG_DIRECTOR_ADDRESS = config.get("Director", "Address") 45CONFIG_DIRECTOR_NAME = config.get("Director", "Name") 46CONFIG_DIRECTOR_PORT = config.getint("Director", "Port") 47 48SECRET_KEY = config.get("JWT", "secret_key") 49ALGORITHM = config.get("JWT", "algorithm") 50ACCESS_TOKEN_EXPIRE_MINUTES = config.getint("JWT", "access_token_expire_minutes") 51 52userDirectors = {} 53users_db = {} 54 55with open("metatags.yaml", "r") as stream: 56 tags_metadata = yaml.safe_load(stream) 57 58pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") 59 60oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") 61 62app = FastAPI( 63 title="Bareos REST API", 64 description="Bareos REST API built on python-bareos. Experimental and subject to enhancements and changes. **Note** swagger does not support GET methods with bodies, however, the CURL statements displayed by swagger do work.", 65 version="0.0.1", 66 openapi_tags=tags_metadata, 67) 68 69 70class UserObject(object): 71 def __init__(self, username, password): 72 # self.id = id 73 self.username = username 74 self.password = password 75 self.directorName = CONFIG_DIRECTOR_NAME 76 self.director = bareos.bsock.BSock 77 self.jsonDirector = bareos.bsock.DirectorConsoleJson 78 self.directorVersion = "" # Format: xx.yy.zz, example: 19.02.06 79 80 def __str__(self): 81 return "User(username='%s')" % (self.username) 82 83 def __iter__(self): 84 yield "username", self.username 85 yield "directorName", self.directorName 86 yield "directorVersion", self.directorVersion 87 88 def getDirectorVersion(self): 89 return self.directorVersion 90 91 92def verify_password(plain_password, hashed_password): 93 return pwd_context.verify(plain_password, hashed_password) 94 95 96def get_password_hash(password): 97 return pwd_context.hash(password) 98 99 100def get_user(username: str): 101 if username in users_db: 102 # print(users_db[username]) 103 # return {'username':username, 'directorName': users_db[username].directorName} 104 return users_db[username] 105 106 107def authenticate_user(username: str, password: str): 108 jsonDirector = None 109 try: 110 jsonDirector = bareos.bsock.DirectorConsoleJson( 111 address=CONFIG_DIRECTOR_ADDRESS, 112 port=CONFIG_DIRECTOR_PORT, 113 dirname=CONFIG_DIRECTOR_NAME, 114 name=username, 115 password=bareos.bsock.Password(password), 116 ) 117 except Exception as e: 118 print( 119 "Could not authorize %s at director %s. %s" 120 % (username, CONFIG_DIRECTOR_NAME, e) 121 ) 122 return False 123 user = UserObject(username, password) 124 user.jsonDirector = jsonDirector 125 user.username = username 126 users_db[username] = user 127 return user 128 129 130def create_access_token(data: dict, expires_delta: Optional[timedelta] = None): 131 to_encode = data.copy() 132 if expires_delta: 133 expire = datetime.utcnow() + expires_delta 134 else: 135 expire = datetime.utcnow() + timedelta(minutes=15) 136 to_encode.update({"exp": expire}) 137 encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) 138 return encoded_jwt 139 140 141async def get_current_user(token: str = Depends(oauth2_scheme)): 142 credentials_exception = HTTPException( 143 status_code=status.HTTP_401_UNAUTHORIZED, 144 detail="Could not validate credentials", 145 headers={"WWW-Authenticate": "Bearer"}, 146 ) 147 try: 148 payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) 149 username: str = payload.get("sub") 150 if username is None: 151 raise credentials_exception 152 token_data = TokenData(username=username) 153 except JWTError: 154 raise credentials_exception 155 user = get_user(username=token_data.username) 156 if user is None: 157 raise credentials_exception 158 return user 159 160 161@app.post("/token", response_model=Token) 162async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()): 163 user = authenticate_user(form_data.username, form_data.password) 164 if not user: 165 raise HTTPException( 166 status_code=status.HTTP_401_UNAUTHORIZED, 167 detail="Incorrect username or password", 168 headers={"WWW-Authenticate": "Bearer"}, 169 ) 170 access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) 171 access_token = create_access_token( 172 data={"sub": user.username}, expires_delta=access_token_expires 173 ) 174 return {"access_token": access_token, "token_type": "bearer"} 175 176 177@app.get("/users/me/", response_model=User) 178async def read_users_me(current_user: User = Depends(get_current_user)): 179 return current_user 180 181 182## Generic Methods 183 184 185def versionCheck( 186 *, 187 response: Response, 188 current_user: User = Depends(get_current_user), 189 minVersion: Optional[str] = "16.1.1", 190): 191 myVersion = "" 192 if current_user.directorVersion > "": 193 myVersion = current_user.directorVersion 194 else: 195 result = read_director_version(response=response, current_user=current_user) 196 if "version" in result: 197 myVersion = result["version"] 198 current_user.directorVersion = myVersion 199 else: 200 raise HTTPException( 201 status_code=500, 202 detail="Could not read version from director. Need at least version %s" 203 % (minVersion), 204 ) 205 # print (myVersion) 206 if not (version.parse(myVersion) >= version.parse(minVersion)): 207 raise HTTPException( 208 status_code=501, 209 detail="Not implemented in Bareos %s. Need at least version %s" 210 % (myVersion, minVersion), 211 ) 212 else: 213 return True 214 215 216def configure_add_standard_component( 217 *, 218 componentDef: BaseModel, 219 response: Response, 220 current_user: User = Depends(get_current_user), 221 componentType=str, 222): 223 """ 224 Create a new Bareos standard component resource. 225 Console command used: _configure add component_ 226 227 """ 228 229 addCommand = "configure add %s" % componentType 230 componentDict = componentDef.dict() 231 232 for a in componentDict: 233 if componentDict[a] is not None: 234 addCommand += " %s=%s" % ( 235 a, 236 str(componentDict[a]).strip("[]").replace("'", "").replace(" ", ""), 237 ) 238 # print(addCommand) 239 # print(current_user) 240 try: 241 result = current_user.jsonDirector.call(addCommand) 242 except Exception as e: 243 response.status_code = 500 244 return { 245 "message": "Could not add %s with command '%s'. Message: '%s'" 246 % (componentType, addCommand, e) 247 } 248 249 if "configure" in result and "add" in result["configure"]: 250 return result 251 else: 252 response.status_code = 500 253 return { 254 "message": "Could not add %s with command '%s' on director %s. Message: '%s'" 255 % (componentType, addCommand, current_user.directorName, e) 256 } 257 258 259def switch_resource( 260 *, 261 response: Response, 262 current_user: User = Depends(get_current_user), 263 resourceName: str, 264 componentType=str, 265 enable=bool, 266): 267 """ 268 Enables or disables a client, job or schedule 269 Returns tuple: success(bool), json-return-string(dict) 270 """ 271 responseDict = {} 272 if enable: 273 command = "enable " 274 else: 275 command = "disable " 276 command += "%s=%s" % (componentType, resourceName) 277 # print(command) 278 try: 279 responseDict = current_user.jsonDirector.call(command) 280 except Exception as e: 281 response.status_code = 500 282 return ( 283 False, 284 { 285 "message": "Could not en/disable %s %s on director %s. Message: '%s'" 286 % (componentType, resourceName, CONFIG_DIRECTOR_NAME, e) 287 }, 288 ) 289 # json result from director is empty 290 response.status_code = 204 291 return (True, responseDict) 292 293 294def parseCommandOptions(queryDict): 295 optionString = "" 296 for q in queryDict: 297 if queryDict[q] is not None: 298 if type(queryDict[q]) != bareosFlag: 299 optionString += " %s=%s" % (q, str(queryDict[q])) 300 else: 301 if queryDict[q]: 302 optionString += " %s" % q 303 return optionString 304 305 306def show_configuration_items( 307 *, 308 response: Response, 309 current_user: User = Depends(get_current_user), 310 itemType: str, 311 byName: Optional[str] = None, 312 verbose: Optional[bareosBool] = "yes", 313): 314 """ 315 Uses _show_ command to provide configuration setting 316 """ 317 versionCheck( 318 response=response, 319 current_user=current_user, 320 minVersion="20.0.0~pre996.de46d0b15", 321 ) 322 # Sometimes config type identificator differs from key returned by director, we need to map 323 itemKey = itemType 324 # itemTypeKeyMap = {"clients":"client", "jobs":"job", "pools":"pool", "schedules": "schedule", "storages":"storage", "users":"user", "profiles":"profile", "consoles":"console"} 325 # if itemType in itemTypeKeyMap: 326 # itemKey = itemTypeKeyMap[itemType] 327 328 foundItems = 0 329 showCommand = "show %s" % itemType 330 if byName: 331 showCommand += "=%s" % byName 332 if verbose: 333 # print ("verbose on") 334 showCommand += " verbose" 335 try: 336 responseDict = current_user.jsonDirector.call(showCommand) 337 except Exception as e: 338 raise HTTPException( 339 status_code=500, 340 detail="Could not read %s from director %s. Message: '%s'" 341 % (itemType, CONFIG_DIRECTOR_NAME, e), 342 ) 343 # print(responseDict) 344 if itemKey in responseDict: 345 foundItems = len(responseDict[itemKey]) 346 if foundItems > 0 and byName: 347 return responseDict[itemKey] 348 elif foundItems > 0: 349 return {"totalItems": foundItems, itemType: responseDict[itemKey]} 350 else: 351 raise HTTPException(status_code=404, detail="No %s found." % itemKey) 352 353 354def list_catalog_items( 355 *, 356 response: Response, 357 current_user: User = Depends(get_current_user), 358 itemType: str, 359 limit: Optional[int] = None, 360 offset: Optional[int] = None, 361 jobQuery: Optional[jobQuery] = None, 362 verbose: Optional[bareosBool] = "yes", 363 hasCountOption: Optional[bareosBool] = "no", 364): 365 itemKey = itemType 366 itemTypeKeyMap = {"files": "filenames"} 367 if itemType in itemTypeKeyMap: 368 itemKey = itemTypeKeyMap[itemType] 369 if verbose: 370 listCommand = "llist " 371 else: 372 listCommand = "list " 373 listCommand += itemType 374 queryDict = {} 375 countDict = {} 376 results = {} 377 countCommand = listCommand 378 if jobQuery is not None: 379 queryDict = jobQuery.dict() 380 for q in queryDict: 381 if queryDict[q] is not None: 382 listCommand += " %s=%s" % (q, str(queryDict[q])) 383 countCommand += " %s=%s" % (q, str(queryDict[q])) 384 if limit is not None: 385 listCommand += " limit=%d" % limit 386 if offset is not None: 387 listCommand += " offset=%d" % offset 388 countCommand += " count" 389 try: 390 responseDict = current_user.jsonDirector.call(listCommand) 391 if hasCountOption: 392 countDict = current_user.jsonDirector.call(countCommand) 393 except Exception as e: 394 raise HTTPException( 395 status_code=500, 396 detail={ 397 "message": "Could not read %s list from director %s. Message: '%s'" 398 % (itemType, CONFIG_DIRECTOR_NAME, e) 399 }, 400 ) 401 foundItems = len(responseDict) 402 if ( 403 hasCountOption == "yes" 404 and itemKey in countDict 405 and "count" in countDict[itemKey][0] 406 ): 407 results["totalItems"] = countDict[itemKey][0]["count"] 408 else: 409 results["totalItems"] = foundItems 410 if foundItems > 0: 411 # check for limit / offset 412 if limit is not None: 413 results["limit"] = limit 414 if offset is not None: 415 results["offset"] = offset 416 return {**results, itemKey: responseDict[itemKey]} 417 else: 418 raise HTTPException( 419 status_code=404, detail={"message": "No %s found." % itemType} 420 ) 421 422 423### Clients ### 424@app.get("/control/clients", status_code=200, tags=["clients", "control"]) 425def read_catalog_info_for_all_clients( 426 response: Response, 427 current_user: User = Depends(get_current_user), 428 name: Optional[str] = None, 429): 430 """ 431 Read status information from catalog about all clients or just one client by name. 432 Built on console command _llist client_ 433 """ 434 if name: 435 listCommand = "llist client=%s" % name 436 else: 437 listCommand = "llist clients" 438 try: 439 responseDict = current_user.jsonDirector.call(listCommand) 440 except Exception as e: 441 response.status_code = 500 442 return { 443 "message": "Could not read client list from director %s. Message: '%s'" 444 % (CONFIG_DIRECTOR_NAME, e) 445 } 446 if "clients" in responseDict: 447 totalItems = len(responseDict["clients"]) 448 return {"totalItems": totalItems, "clients": responseDict["clients"]} 449 else: 450 response.status_code = 404 451 return {"message": "No clients found."} 452 453 454@app.get("/control/clients/{client_id}", tags=["clients", "control"]) 455def read_catalog_info_for_particular_client( 456 *, 457 client_id: int = Path(..., title="The ID of client to get", ge=1), 458 response: Response, 459 current_user: User = Depends(get_current_user), 460): 461 """ 462 Read catalog information for one client by id. 463 Built on console command _llist client_ 464 465 **Warning** Director does not support direct query by _id_ we query all clients and filter the result. 466 Maybe more time consuming than expected in large settings. 467 """ 468 allClients = read_catalog_info_for_all_clients(response, current_user) 469 result = None 470 for c in allClients["clients"]: 471 if c["clientid"] == str(client_id): 472 result = c 473 break 474 if result: 475 return result 476 else: 477 response.status_code = 404 478 return { 479 "message": "Client with Client ID {client_id} not found".format( 480 client_id=client_id 481 ) 482 } 483 return {"item_id": item_id} 484 485 486@app.put( 487 "/control/clients/enable/{client_name}", 488 status_code=204, 489 tags=["clients", "control"], 490) 491def enable_client( 492 *, 493 client_name: str = Path(..., title="The client (name) to enable"), 494 response: Response, 495 current_user: User = Depends(get_current_user), 496): 497 (result, jsonMessage) = switch_resource( 498 response=response, 499 current_user=current_user, 500 resourceName=client_name, 501 componentType="client", 502 enable=True, 503 ) 504 if result: 505 response.status_code = 204 # ok, but empty return-string 506 else: 507 response.status_code = 500 508 return jsonMessage 509 510 511@app.put( 512 "/control/clients/disable/{client_name}", 513 status_code=204, 514 tags=["clients", "control"], 515) 516def disable_client( 517 *, 518 client_name: str = Path(..., title="The client (name) to disable"), 519 response: Response, 520 current_user: User = Depends(get_current_user), 521): 522 (result, jsonMessage) = switch_resource( 523 response=response, 524 current_user=current_user, 525 resourceName=client_name, 526 componentType="client", 527 enable=False, 528 ) 529 if result: 530 response.status_code = 204 # ok, but empty return-string 531 else: 532 response.status_code = 500 533 return jsonMessage 534 535 536@app.get("/configuration/clients", tags=["clients", "configuration"]) 537def read_all_clients( 538 *, 539 response: Response, 540 current_user: User = Depends(get_current_user), 541 verbose: Optional[bareosBool] = Query("yes", title="Verbose output"), 542): 543 """ 544 Read all jobdef resources. Built on console command _show clients_. 545 546 Needs at least Bareos Version >= 20.0.0 547 """ 548 return show_configuration_items( 549 response=response, 550 current_user=current_user, 551 itemType="clients", 552 verbose=verbose, 553 ) 554 555 556@app.get("/configuration/clients/{clients_name}", tags=["clients", "configuration"]) 557def read_client_by_name( 558 *, 559 response: Response, 560 current_user: User = Depends(get_current_user), 561 clients_name: str = Path(..., title="Client name to look for"), 562 verbose: Optional[bareosBool] = Query("yes", title="Verbose output"), 563): 564 """ 565 Read all jobdef resources. Built on console command _show clients_. 566 567 Needs at least Bareos Version >= 20.0.0 568 """ 569 return show_configuration_items( 570 response=response, 571 current_user=current_user, 572 itemType="clients", 573 byName=clients_name, 574 verbose=verbose, 575 ) 576 577 578@app.post("/configuration/clients", tags=["clients", "configuration"]) 579def post_client( 580 *, 581 clientDef: clientResource = Body(..., title="The client to create"), 582 response: Response, 583 current_user: User = Depends(get_current_user), 584): 585 return configure_add_standard_component( 586 componentDef=clientDef, 587 response=response, 588 current_user=current_user, 589 componentType="client", 590 ) 591 592 593### filesets 594 595 596@app.get("/configuration/filesets", tags=["filesets", "configuration"]) 597def read_all_filesets( 598 *, 599 response: Response, 600 current_user: User = Depends(get_current_user), 601 verbose: Optional[bareosBool] = Query("yes", title="Verbose output"), 602): 603 """ 604 Read all jobdef resources. Built on console command _show filesets_. 605 606 Needs at least Bareos Version >= 20.0.0 607 """ 608 return show_configuration_items( 609 response=response, 610 current_user=current_user, 611 itemType="filesets", 612 verbose=verbose, 613 ) 614 615 616@app.get("/configuration/filesets/{filesets_name}", tags=["filesets", "configuration"]) 617def read_fileset_by_name( 618 *, 619 response: Response, 620 current_user: User = Depends(get_current_user), 621 filesets_name: str = Path(..., title="fileset name to look for"), 622 verbose: Optional[bareosBool] = Query("yes", title="Verbose output"), 623): 624 """ 625 Read all jobdef resources. Built on console command _show filesets_. 626 627 Needs at least Bareos Version >= 20.0.0 628 """ 629 return show_configuration_items( 630 response=response, 631 current_user=current_user, 632 itemType="filesets", 633 byName=filesets_name, 634 verbose=verbose, 635 ) 636 637 638#### Job Control 639 640 641@app.post("/control/jobs/run", tags=["jobcontrol", "control", "jobs"]) 642def runJob( 643 *, 644 jobControl: jobControl = Body(..., title="Job control information", embed=True), 645 response: Response, 646 current_user: User = Depends(get_current_user), 647): 648 """ 649 Run a job, defined by jobControl record. 650 651 **Note**: Swagger throws a weird error when running this command by the UI, 652 while the given curl statement works fine. 653 """ 654 result = None 655 jobCommand = "run" 656 args = jobControl.dict() 657 for a in args: 658 if args[a] is not None: 659 jobCommand += " %s=%s" % (a, str(args[a])) 660 # print(jobCommand) 661 try: 662 result = current_user.jsonDirector.call(jobCommand) 663 except Exception as e: 664 response.status_code = 500 665 return { 666 "message": "Could not start job '%s' on director %s. Message: '%s'" 667 % (jobCommand, current_user.directorName, e) 668 } 669 if "run" in result and "jobid" in result["run"]: 670 return {"jobid": int(result["run"]["jobid"])} 671 else: 672 response.status_code = 500 673 return {"message": "Job '%s' triggered but no jobId returned" % jobCommand} 674 675 676@app.post("/control/jobs/rerun/{job_id}", tags=["jobcontrol", "control", "jobs"]) 677def rerun_Job_by_jobid( 678 *, 679 job_id: int = Path(..., title="The ID of job to rerun", ge=1), 680 response: Response, 681 current_user: User = Depends(get_current_user), 682): 683 """ 684 Rerun a specific job given bei jobid 685 """ 686 result = None 687 rerunCommand = "rerun jobid=%d" % job_id 688 try: 689 result = current_user.jsonDirector.call(rerunCommand) 690 except Exception as e: 691 response.status_code = 500 692 return { 693 "message": "Could not rerun jobid %d on director %s. Message: '%s'" 694 % (job_id, current_user.directorName, e) 695 } 696 if "run" in result and "jobid" in result["run"]: 697 return {"jobid": int(result["run"]["jobid"])} 698 else: 699 response.status_code = 500 700 return {"message": "Job '%s' triggered but no jobId returned" % jobCommand} 701 702 703@app.post("/control/jobs/rerun", tags=["jobcontrol", "control", "jobs"]) 704def rerun_Job( 705 *, 706 job_range: jobRange = Body(..., title="Job range to rerun"), 707 response: Response, 708 current_user: User = Depends(get_current_user), 709): 710 """ 711 Rerun jobs given by the following parameters (at least one) 712 713 - **since_jobid**=_jobid_ rerun failed jobs since this jobid 714 - **until_jobid**=_jobid_ - in conjunction with _since_jobid_ 715 - **days**=_nr_days_ - since a number of days 716 - **hours**=_nr_hours_ - since a number of hours 717 718 Built on console command _rerun_ 719 """ 720 result = None 721 rerunCommand = "rerun" 722 args = job_range.dict() 723 for a in args: 724 if args[a] is not None: 725 rerunCommand += " %s=%s" % (a, args[a]) 726 rerunCommand += " yes" 727 try: 728 result = current_user.jsonDirector.call(rerunCommand) 729 except Exception as e: 730 response.status_code = 500 731 return { 732 "message": "Could not %s on director %s. Message: '%s'" 733 % (rerunCommand, current_user.directorName, e) 734 } 735 # print(result) 736 if "run" in result and "jobid" in result["run"]: 737 return {"jobid": int(result["run"]["jobid"])} 738 else: 739 response.status_code = 500 740 return {"message": "Job '%s' triggered but no jobId returned" % rerunCommand} 741 742 743@app.post("/control/jobs/restore", tags=["jobcontrol", "control", "jobs"]) 744def runRestoreJob( 745 *, 746 jobControl: restoreJobControl = Body( 747 ..., title="Restore Job control information", embed=True 748 ), 749 response: Response, 750 current_user: User = Depends(get_current_user), 751): 752 """ 753 Run a restore-job, defined by jobControl record. 754 755 """ 756 result = None 757 jobCommand = "restore" 758 args = jobControl.dict() 759 for a in args: 760 # print(" %s=%s" % (a, str(args[a]))) 761 if args[a] is not None: 762 if a != "selectAllDone": 763 jobCommand += " %s=%s" % (a, str(args[a])) 764 elif args[a] == "yes": 765 jobCommand += " select all done" 766 # print(jobCommand) 767 try: 768 result = current_user.jsonDirector.call(jobCommand) 769 except Exception as e: 770 response.status_code = 500 771 return { 772 "message": "Could not start job '%s' on director %s. Message: '%s'" 773 % (jobCommand, current_user.directorName, e) 774 } 775 if "run" in result and "jobid" in result["run"]: 776 return {"jobid": int(result["run"]["jobid"])} 777 else: 778 response.status_code = 500 779 return {"message": "Job '%s' triggered but no jobId returned" % jobCommand} 780 781 782@app.put("/control/jobs/cancel/{job_id}", tags=["jobcontrol", "control", "jobs"]) 783def cancelJob( 784 *, 785 job_id: int = Path(..., title="The ID of job to cancel", ge=1), 786 response: Response, 787 current_user: User = Depends(get_current_user), 788): 789 """ 790 Cancel a specific job given bei jobid 791 """ 792 # cancel a specific job given bei jobid 793 cancelCommand = "cancel jobid=%d" % job_id 794 result = None 795 try: 796 result = current_user.jsonDirector.call(cancelCommand) 797 except Exception as e: 798 response.status_code = 500 799 return { 800 "message": "Could not cancel jobid %d on director %s. Message: '%s'" 801 % (job_id, current_user.directorName, e) 802 } 803 return result 804 805 806@app.put( 807 "/control/jobs/enable/{job_name}", 808 status_code=204, 809 tags=["jobcontrol", "jobs", "control"], 810) 811def enable_job( 812 *, 813 job_name: str = Path(..., title="The job (name) to enable"), 814 response: Response, 815 current_user: User = Depends(get_current_user), 816): 817 (result, jsonMessage) = switch_resource( 818 response=response, 819 current_user=current_user, 820 resourceName=job_name, 821 componentType="job", 822 enable=True, 823 ) 824 if result: 825 response.status_code = 204 # ok, but empty return-string 826 else: 827 response.status_code = 500 828 return jsonMessage 829 830 831@app.put( 832 "/control/jobs/disable/{job_name}", 833 status_code=204, 834 tags=["jobcontrol", "jobs", "control"], 835) 836def disable_job( 837 *, 838 job_name: str = Path(..., title="The job (name) to disable"), 839 response: Response, 840 current_user: User = Depends(get_current_user), 841): 842 (result, jsonMessage) = switch_resource( 843 response=response, 844 current_user=current_user, 845 resourceName=job_name, 846 componentType="job", 847 enable=False, 848 ) 849 if result: 850 response.status_code = 204 # ok, but empty return-string 851 else: 852 response.status_code = 500 853 return jsonMessage 854 855 856#### Job Status 857 858 859@app.get("/control/jobs/totals", tags=["jobcontrol", "control", "jobs"]) 860def read_all_jobs_totals( 861 *, response: Response, current_user: User = Depends(get_current_user) 862): 863 listCommand = "llist jobtotals" 864 results = {} 865 try: 866 responseDict = current_user.jsonDirector.call(listCommand) 867 except Exception as e: 868 response.status_code = 500 869 return { 870 "message": "Could not read job totals from director %s. Message: '%s'" 871 % (CONFIG_DIRECTOR_NAME, e) 872 } 873 if "jobtotals" in responseDict: 874 return responseDict 875 else: 876 response.status_code = 404 877 return {"message": "No jobtotals found."} 878 879 880@app.get("/control/jobs/{job_id}", tags=["jobcontrol", "control", "jobs"]) 881def read_job_status( 882 *, 883 job_id: int = Path(..., title="The ID of job to get", ge=1), 884 response: Response, 885 current_user: User = Depends(get_current_user), 886): 887 """ 888 Read information about a specific job defined by jobid 889 Returns output of command _llist jobid=id_ 890 """ 891 result = None 892 listCommand = "llist jobid=%d" % job_id 893 try: 894 result = current_user.jsonDirector.call(listCommand) 895 except Exception as e: 896 response.status_code = 500 897 return { 898 "message": "Could not query jobs on director %s. Message: '%s'" 899 % (current_user.directorName, e) 900 } 901 if result and "jobs" in result: 902 return result["jobs"][0] 903 else: 904 response.status_code = 404 905 return {"message": "Job with Job ID {jobid} not found".format(jobid=job_id)} 906 907 908@app.get("/control/jobs", tags=["jobcontrol", "control", "jobs"]) 909def read_all_jobs_status( 910 *, 911 response: Response, 912 current_user: User = Depends(get_current_user), 913 limit: Optional[int] = Query(None, title="Result items limit", gt=1), 914 offset: Optional[int] = Query(None, title="Result items offset", gt=0), 915 jobQuery: Optional[jobQuery] = Body(None, title="Query parameter"), 916): 917 return list_catalog_items( 918 itemType="jobs", 919 current_user=current_user, 920 response=response, 921 limit=limit, 922 offset=offset, 923 jobQuery=jobQuery, 924 hasCountOption="yes", 925 ) 926 927 928@app.delete("/control/jobs/{job_id}", tags=["jobcontrol", "control", "jobs"]) 929def delete_job( 930 *, 931 job_id: int = Path(..., title="The ID of job to delete", ge=1), 932 response: Response, 933 current_user: User = Depends(get_current_user), 934): 935 """ 936 Delete job record from catalog 937 """ 938 # Director gives no success nor failed information 939 # We implemente validation here (check if job exists before and after deletion) 940 jobStatusResponse = read_job_status( 941 job_id=job_id, response=response, current_user=current_user 942 ) 943 if not "jobid" in jobStatusResponse: 944 response.status_code = 404 945 return { 946 "message": "No job with id %d found on director %s." 947 % (job_id, current_user.directorName) 948 } 949 # delete a specific job record given bei jobid 950 deleteCommand = "delete jobid=%d" % job_id 951 try: 952 result = current_user.jsonDirector.call(deleteCommand) 953 except Exception as e: 954 response.status_code = 500 955 return { 956 "message": "Could not delete jobid %d on director %s. Message: '%s'" 957 % (job_id, current_identity.directorName, e) 958 } 959 jobStatusResponse = read_job_status( 960 job_id=job_id, response=response, current_user=current_user 961 ) 962 if "jobid" in jobStatusResponse: 963 response.status_code = 500 964 return { 965 "message": "Job with id %d still exists on director %s. Delete failed" 966 % (job_id, current_user.directorName) 967 } 968 response.status_code = 200 969 return {"message": "Job %d succesfully deleted." % job_id} 970 971 972@app.get("/control/jobs/logs/{job_id}", tags=["jobcontrol", "control", "jobs"]) 973def read_one_job_log( 974 *, 975 job_id: int = Path(..., title="The ID of job to get the logs", ge=1), 976 response: Response, 977 current_user: User = Depends(get_current_user), 978): 979 """ 980 Read logs from a specific job defined by jobid 981 Returns output of command _list joblog jobid=id_ 982 """ 983 result = None 984 listCommand = "list joblog jobid=%d" % job_id 985 try: 986 result = current_user.jsonDirector.call(listCommand) 987 except Exception as e: 988 response.status_code = 500 989 return { 990 "message": "Could not read joblogs on director %s. Message: '%s'" 991 % (current_user.directorName, e) 992 } 993 if result and "joblog" in result: 994 totalItems = len(result["joblog"]) 995 return {"totalItems": totalItems, "joblog": result["joblog"]} 996 else: 997 response.status_code = 404 998 return {"message": "Joblogs Job with ID {jobid} not found".format(jobid=job_id)} 999 1000 1001@app.get("/control/jobs/files/{job_id}", tags=["jobcontrol", "control", "jobs"]) 1002def read_files_of_job( 1003 *, 1004 job_id: int = Path(..., title="The ID of job to get the files", ge=1), 1005 response: Response, 1006 current_user: User = Depends(get_current_user), 1007): 1008 """ 1009 Read files from a specific job defined by jobid 1010 Returns output of command _list joblog jobid=id_ 1011 """ 1012 result = None 1013 listCommand = "list files jobid=%d" % job_id 1014 try: 1015 result = current_user.jsonDirector.call(listCommand) 1016 except Exception as e: 1017 response.status_code = 500 1018 return { 1019 "message": "Could not read jobfiles on director %s. Message: '%s'" 1020 % (current_user.directorName, e) 1021 } 1022 if result and "filenames" in result: 1023 totalItems = len(result["filenames"]) 1024 return {"totalItems": totalItems, "filenames": result["filenames"]} 1025 else: 1026 response.status_code = 404 1027 return { 1028 "message": "Files for job with ID {jobid} not found".format(jobid=job_id) 1029 } 1030 1031 1032#### JobDefs 1033 1034 1035@app.post("/confguration/jobdefs", tags=["jobdefs", "configuration"]) 1036def post_jobdef( 1037 *, 1038 jobDef: jobDefs = Body(..., title="Jobdef resource"), 1039 response: Response, 1040 current_user: User = Depends(get_current_user), 1041): 1042 """ 1043 Create a new jobdefs resource. 1044 Console command used: _configure add jobdefs_ 1045 """ 1046 return configure_add_standard_component( 1047 componentDef=jobDef, 1048 response=response, 1049 current_user=current_user, 1050 componentType="jobdefs", 1051 ) 1052 1053 1054@app.get("/configuration/jobdefs", tags=["jobdefs", "configuration"]) 1055def read_all_jobdefs( 1056 *, 1057 response: Response, 1058 current_user: User = Depends(get_current_user), 1059 verbose: Optional[bareosBool] = Query("yes", title="Verbose output"), 1060): 1061 """ 1062 Read all jobdef resources. Built on console command _show jobdefs_. 1063 1064 Needs at least Bareos Version >= 20.0.0 1065 """ 1066 return show_configuration_items( 1067 response=response, 1068 current_user=current_user, 1069 itemType="jobdefs", 1070 verbose=verbose, 1071 ) 1072 1073 1074@app.get("/configuration/jobdefs/{jobdefs_name}", tags=["jobdefs", "configuration"]) 1075def read_jobdef_by_name( 1076 *, 1077 response: Response, 1078 current_user: User = Depends(get_current_user), 1079 jobdefs_name: str = Path(..., title="JobDef name to look for"), 1080 verbose: Optional[bareosBool] = Query("yes", title="Verbose output"), 1081): 1082 """ 1083 Read all jobdef resources. Built on console command _show jobdefs_. 1084 1085 Needs at least Bareos Version >= 20.0.0 1086 """ 1087 return show_configuration_items( 1088 response=response, 1089 current_user=current_user, 1090 itemType="jobdefs", 1091 byName=jobdefs_name, 1092 verbose=verbose, 1093 ) 1094 1095 1096#### Job Resource 1097 1098 1099@app.get("/configuration/jobs", tags=["jobs", "configuration"]) 1100def read_all_jobs( 1101 *, 1102 response: Response, 1103 current_user: User = Depends(get_current_user), 1104 verbose: Optional[bareosBool] = Query("yes", title="Verbose output"), 1105): 1106 """ 1107 Read all jobdef resources. Built on console command _show jobs_. 1108 1109 Needs at least Bareos Version >= 20.0.0 1110 """ 1111 return show_configuration_items( 1112 response=response, current_user=current_user, itemType="jobs", verbose=verbose 1113 ) 1114 1115 1116@app.get("/configuration/jobs/{jobs_name}", tags=["jobs", "configuration"]) 1117def read_job_by_name( 1118 *, 1119 response: Response, 1120 current_user: User = Depends(get_current_user), 1121 jobs_name: str = Path(..., title="Client name to look for"), 1122 verbose: Optional[bareosBool] = Query("yes", title="Verbose output"), 1123): 1124 """ 1125 Read all jobdef resources. Built on console command _show jobs_. 1126 1127 Needs at least Bareos Version >= 20.0.0 1128 """ 1129 return show_configuration_items( 1130 response=response, 1131 current_user=current_user, 1132 itemType="jobs", 1133 byName=jobs_name, 1134 verbose=verbose, 1135 ) 1136 1137 1138@app.post("/configuration/jobs", tags=["jobs", "configuration"]) 1139def post_job( 1140 *, 1141 jobDef: jobResource = Body(..., title="Job resource"), 1142 response: Response, 1143 current_user: User = Depends(get_current_user), 1144): 1145 """ 1146 Create a new job resource. 1147 Console command used: _configure add job_ 1148 1149 """ 1150 return configure_add_standard_component( 1151 componentDef=jobDef, 1152 response=response, 1153 current_user=current_user, 1154 componentType="job", 1155 ) 1156 1157 1158### Volumes 1159 1160 1161@app.get("/control/volumes", tags=["volumes", "control"]) 1162def read_volumes( 1163 *, 1164 response: Response, 1165 current_user: User = Depends(get_current_user), 1166 limit: Optional[int] = Query(None, title="Result items limit", gt=0), 1167 offset: Optional[int] = Query(None, title="Result items offset", gt=0), 1168 myQuery: Optional[volumeQuery] = Body(None, title="Query parameter"), 1169): 1170 queryDict = {} 1171 countDict = {} 1172 responseDict = {} 1173 results = {} 1174 results["volumes"] = {} 1175 volumeKeyName = "volumes" 1176 volumeCommand = "llist volumes" 1177 countCommand = "list volumes" 1178 if myQuery is not None: 1179 queryDict = myQuery.dict() 1180 for q in queryDict: 1181 if queryDict[q] is not None: 1182 volumeCommand += " %s=%s" % (q, str(queryDict[q])) 1183 countCommand += " %s=%s" % (q, str(queryDict[q])) 1184 countCommand += " count" 1185 if limit is not None: 1186 volumeCommand += " limit=%d" % limit 1187 if offset is not None: 1188 volumeCommand += " offset=%d" % offset 1189 # if volume name is in queryDict, we have to remove the other filters 1190 # and use a different comman 1191 if "volume" in queryDict and queryDict["volume"] is not None: 1192 volumeKeyName = "volume" 1193 volumeCommand = "llist volume=%s" % queryDict["volume"] 1194 countCommand = None 1195 try: 1196 responseDict = current_user.jsonDirector.call(volumeCommand) 1197 if countCommand is not None: 1198 countDict = current_user.jsonDirector.call(countCommand) 1199 except Exception as e: 1200 response.status_code = 500 1201 return { 1202 "message": "Could not read volume list from director %s. Message: '%s'" 1203 % (CONFIG_DIRECTOR_NAME, e) 1204 } 1205 if "volumes" in responseDict: 1206 counter = 0 1207 # countDict/response dict has different structures, if filtered by pool or not 1208 if isinstance(countDict["volumes"], dict): 1209 results["volumes"] = responseDict[volumeKeyName] 1210 for p in countDict["volumes"]: 1211 counter += int(countDict["volumes"][p][0]["count"]) 1212 else: 1213 # check for empty pool 1214 if len(responseDict[volumeKeyName]) == 0: 1215 response.status_code = 404 1216 return {"message": "Nothing found. Command: %s" % volumeCommand} 1217 poolName = responseDict[volumeKeyName][0]["pool"] 1218 # print(responseDict[volumeKeyName]) 1219 results["volumes"][poolName] = responseDict[volumeKeyName] 1220 counter = int(countDict["volumes"][0]["count"]) 1221 results["totalItems"] = counter 1222 foundItems = len(responseDict) 1223 elif "volume" in responseDict and "pool" in responseDict["volume"]: 1224 foundItems = 1 1225 poolName = responseDict["volume"]["pool"] 1226 results["totalItems"] = 1 1227 results["volumes"] = {poolName: [responseDict["volume"]]} 1228 else: 1229 response.status_code = 404 1230 return {"message": "nothing found for command: %s" % volumeCommand} 1231 1232 if foundItems > 0: 1233 # check for limit / offset 1234 if limit is not None: 1235 results["limit"] = limit 1236 if offset is not None: 1237 results["offset"] = offset 1238 return results 1239 else: 1240 response.status_code = 404 1241 return {"message": "No volumes found."} 1242 1243 1244@app.get("/control/volumes/{volume_id}", tags=["volumes", "control"]) 1245def read_volume( 1246 *, 1247 response: Response, 1248 current_user: User = Depends(get_current_user), 1249 volume_id: int = Path(..., title="Volume ID to look for", gt=0, example=1), 1250 volumeQuery: Optional[volumeQuery] = Body(None, title="Query parameter"), 1251): 1252 volumeCommand = "llist volumes" 1253 try: 1254 responseDict = current_user.jsonDirector.call(volumeCommand) 1255 except Exception as e: 1256 response.status_code = 500 1257 return { 1258 "message": "Could not read volume list from director %s. Message: '%s'" 1259 % (CONFIG_DIRECTOR_NAME, e) 1260 } 1261 if "volumes" in responseDict: 1262 for p in responseDict["volumes"]: 1263 for v in responseDict["volumes"][p]: 1264 if v["mediaid"] == str(volume_id): 1265 response.status_code = 200 1266 return v 1267 response.status_code = 404 1268 return {"message": "No volume with id %d found" % volume_id} 1269 1270 1271@app.post("/control/volumes", status_code=204, tags=["volumes", "control"]) 1272def label_volume( 1273 *, 1274 response: Response, 1275 current_user: User = Depends(get_current_user), 1276 volumeLabel: volumeLabelDef = Body(..., title="Volume label properties"), 1277): 1278 """ 1279 Label a new volume using the _label_" command 1280 """ 1281 responseDict = {} 1282 labelCommand = "label" 1283 labelCommand += parseCommandOptions(volumeLabel.dict()) 1284 try: 1285 responseDict = current_user.jsonDirector.call(labelCommand) 1286 except Exception as e: 1287 response.status_code = 500 1288 return { 1289 "message": "Could not label volume on director %s. Message: '%s'" 1290 % (CONFIG_DIRECTOR_NAME, e) 1291 } 1292 return responseDict 1293 1294 1295@app.patch("/control/volumes/{volume_name}", tags=["volumes", "control"]) 1296def update_volume( 1297 *, 1298 response: Response, 1299 current_user: User = Depends(get_current_user), 1300 volume_name: str = Path(..., title="Volume Name to update", example="Full-1742"), 1301 volumeProps: volumeProperties = Body(..., title="Volume properties"), 1302): 1303 """ 1304 Update a volume 1305 TODO: verify, that parameter are quoted correct 1306 """ 1307 responseDict = {} 1308 updateCommand = "update volume=%s" % volume_name 1309 updateCommand += parseCommandOptions(volumeProps.dict()) 1310 # print(updateCommand) 1311 try: 1312 responseDict = current_user.jsonDirector.call(updateCommand) 1313 except Exception as e: 1314 response.status_code = 500 1315 return { 1316 "message": "Could not update volume on director %s. Message: '%s'" 1317 % (CONFIG_DIRECTOR_NAME, e) 1318 } 1319 # Director delivers empty response, we want to return the changed volume's properties 1320 volQuery = volumeQuery() 1321 volQuery.volume = volume_name 1322 responseDict = read_volumes( 1323 response=response, 1324 current_user=current_user, 1325 myQuery=volQuery, 1326 limit=None, 1327 offset=None, 1328 ) 1329 # TODO: responseDict is structured: {volumes:{poolname:[{volume}]}} - we just want to return the volume without list and pool dict around 1330 return responseDict 1331 1332 1333@app.put("/control/volumes/move", status_code=200, tags=["volumes", "control"]) 1334def move_volume( 1335 *, 1336 response: Response, 1337 current_user: User = Depends(get_current_user), 1338 moveParams: volumeMove = Body(..., title="Volume move parameters"), 1339): 1340 """ 1341 Move a volume, using the _move_ command 1342 TODO: handle encrypt flag 1343 """ 1344 responseDict = {} 1345 updateCommand = "move" 1346 updateCommand += parseCommandOptions(moveParams.dict()) 1347 # print (updateCommand) 1348 try: 1349 responseDict = current_user.jsonDirector.call(updateCommand) 1350 except Exception as e: 1351 response.status_code = 500 1352 return { 1353 "message": "Could not move volumes on director %s. Message: '%s'" 1354 % (CONFIG_DIRECTOR_NAME, e) 1355 } 1356 # Director delivers empty response 1357 return responseDict 1358 1359 1360@app.put("/control/volumes/export", status_code=200, tags=["volumes", "control"]) 1361def export_volume( 1362 *, 1363 response: Response, 1364 current_user: User = Depends(get_current_user), 1365 exportParams: volumeExport = Body(..., title="Volume Export parameters"), 1366): 1367 """ 1368 Export volumes the _export_ command 1369 """ 1370 responseDict = {} 1371 updateCommand = "export" 1372 updateCommand += parseCommandOptions(exportParams.dict()) 1373 # print(updateCommand) 1374 try: 1375 responseDict = current_user.jsonDirector.call(updateCommand) 1376 except Exception as e: 1377 response.status_code = 500 1378 return { 1379 "message": "Could not export volumes on director %s. Message: '%s'" 1380 % (CONFIG_DIRECTOR_NAME, e) 1381 } 1382 # Director delivers empty response 1383 response.status_code = 200 1384 return responseDict 1385 1386 1387@app.put("/control/volumes/import", status_code=200, tags=["volumes", "control"]) 1388def import_volume( 1389 *, 1390 response: Response, 1391 current_user: User = Depends(get_current_user), 1392 importParams: volumeImport = Body(..., title="Volume import parameters"), 1393): 1394 """ 1395 import volumes the _import_ command 1396 """ 1397 responseDict = {} 1398 updateCommand = "import" 1399 updateCommand += parseCommandOptions(importParams.dict()) 1400 # print(updateCommand) 1401 try: 1402 responseDict = current_user.jsonDirector.call(updateCommand) 1403 except Exception as e: 1404 response.status_code = 500 1405 return { 1406 "message": "Could not import volumes on director %s. Message: '%s'" 1407 % (CONFIG_DIRECTOR_NAME, e) 1408 } 1409 # Director delivers empty response 1410 response.status_code = 200 1411 return responseDict 1412 1413 1414@app.put("/control/volumes/{volume_name}", status_code=204, tags=["volumes", "control"]) 1415def relabel_volume( 1416 *, 1417 response: Response, 1418 current_user: User = Depends(get_current_user), 1419 volume_name: str = Path( 1420 ..., title="Old Volume Name to relabel", example="Full-1742" 1421 ), 1422 volumeRelabel: volumeRelabelDef = Body(..., title="New label properties"), 1423): 1424 """ 1425 Relabel a volume, using the _relabel_ command 1426 TODO: handle encrypt flag 1427 """ 1428 responseDict = {} 1429 updateCommand = "relabel oldvolume=%s" % volume_name 1430 updateCommand += parseCommandOptions(volumeRelabel.dict()) 1431 # print(updateCommand) 1432 try: 1433 responseDict = current_user.jsonDirector.call(updateCommand) 1434 except Exception as e: 1435 response.status_code = 500 1436 return { 1437 "message": "Could not relabel volume on director %s. Message: '%s'" 1438 % (CONFIG_DIRECTOR_NAME, e) 1439 } 1440 # Director delivers empty response 1441 response.status_code = 204 1442 return responseDict 1443 1444 1445@app.delete( 1446 "/control/volumes/{volume_name}", status_code=204, tags=["volumes", "control"] 1447) 1448def delete_volume( 1449 *, 1450 response: Response, 1451 current_user: User = Depends(get_current_user), 1452 volume_name: str = Path(..., title="Volume Name to delete", example="Full-1742"), 1453): 1454 """ 1455 Delete a volume from catalog using the _delete volume_ command. 1456 """ 1457 responseDict = {} 1458 deleteCommand = "delete volume=%s yes" % volume_name 1459 try: 1460 responseDict = current_user.jsonDirector.call(deleteCommand) 1461 except Exception as e: 1462 response.status_code = 500 1463 return { 1464 "message": "Could not delete volume on director %s. Message: '%s'" 1465 % (CONFIG_DIRECTOR_NAME, e) 1466 } 1467 # Director delivers empty response 1468 response.status_code = 204 1469 return responseDict 1470 1471 1472### Pools 1473 1474 1475@app.get("/configuration/pools", tags=["pools", "configuration"]) 1476def read_all_pools( 1477 *, 1478 response: Response, 1479 current_user: User = Depends(get_current_user), 1480 verbose: Optional[bareosBool] = Query("yes", title="Verbose output"), 1481): 1482 """ 1483 Read all jobdef resources. Built on console command _show pools_. 1484 1485 Needs at least Bareos Version >= 20.0.0 1486 """ 1487 return show_configuration_items( 1488 response=response, current_user=current_user, itemType="pools", verbose=verbose 1489 ) 1490 1491 1492@app.get("/configuration/pools/{pools_name}", tags=["pools", "configuration"]) 1493def read_pool_by_name( 1494 *, 1495 response: Response, 1496 current_user: User = Depends(get_current_user), 1497 pools_name: str = Path(..., title="Client name to look for"), 1498 verbose: Optional[bareosBool] = Query("yes", title="Verbose output"), 1499): 1500 """ 1501 Read all jobdef resources. Built on console command _show pools_. 1502 1503 Needs at least Bareos Version >= 20.0.0 1504 """ 1505 return show_configuration_items( 1506 response=response, 1507 current_user=current_user, 1508 itemType="pools", 1509 byName=pools_name, 1510 verbose=verbose, 1511 ) 1512 1513 1514@app.post("/configuration/pools", tags=["pools", "configuration"]) 1515def post_pool( 1516 *, 1517 poolDef: poolResource = Body(..., title="pool resource"), 1518 response: Response, 1519 current_user: User = Depends(get_current_user), 1520): 1521 """ 1522 Create a new pool resource. 1523 Console command used: _configure add pool_ 1524 """ 1525 return configure_add_standard_component( 1526 componentDef=poolDef, 1527 response=response, 1528 current_user=current_user, 1529 componentType="pool", 1530 ) 1531 1532 1533@app.get("/control/pools", status_code=200, tags=["pools", "control"]) 1534def read_all_pools( 1535 response: Response, 1536 current_user: User = Depends(get_current_user), 1537 name: Optional[str] = None, 1538): 1539 """ 1540 Read settings for all pools or just one pool by name from catalog. 1541 Built on console command _llist pool_ 1542 """ 1543 listCommand = "" 1544 if name: 1545 listCommand = "llist pool=%s" % name 1546 else: 1547 listCommand = "llist pools" 1548 # print(listCommand) 1549 try: 1550 responseDict = current_user.jsonDirector.call(listCommand) 1551 except Exception as e: 1552 response.status_code = 500 1553 return { 1554 "message": "Could not read pool list from director %s. Message: '%s'" 1555 % (CONFIG_DIRECTOR_NAME, e) 1556 } 1557 if "pools" in responseDict: 1558 totalItems = len(responseDict["pools"]) 1559 return {"totalItems": totalItems, "pools": responseDict["pools"]} 1560 else: 1561 response.status_code = 404 1562 return {"message": "No pools found."} 1563 1564 1565@app.get("/control/pools/{pool_id}", tags=["pools", "control"]) 1566def read_pool( 1567 *, 1568 pool_id: int = Path(..., title="The ID of pool to get", ge=1), 1569 response: Response, 1570 current_user: User = Depends(get_current_user), 1571): 1572 """ 1573 Read catalog information abour just one pool by id. 1574 Built on console command _llist pool_ 1575 1576 **Warning** Director does not support direct query by _id_ we query all pools and filter the result. 1577 Maybe more time consuming than expected in large settings. 1578 """ 1579 allpools = read_all_pools(response, current_user) 1580 result = None 1581 for c in allpools["pools"]: 1582 if c["poolid"] == str(pool_id): 1583 result = c 1584 break 1585 if result: 1586 return result 1587 else: 1588 response.status_code = 404 1589 return { 1590 "message": "pool with pool ID {pool_id} not found".format(pool_id=pool_id) 1591 } 1592 return {"item_id": item_id} 1593 1594 1595### Schedules 1596 1597 1598@app.get("/configuration/schedules", tags=["schedules", "configuration"]) 1599def read_all_schedules( 1600 *, 1601 response: Response, 1602 current_user: User = Depends(get_current_user), 1603 verbose: Optional[bareosBool] = Query("yes", title="Verbose output"), 1604): 1605 """ 1606 Read all jobdef resources. Built on console command _show schedules_. 1607 1608 Needs at least Bareos Version >= 20.0.0 1609 """ 1610 return show_configuration_items( 1611 response=response, 1612 current_user=current_user, 1613 itemType="schedules", 1614 verbose=verbose, 1615 ) 1616 1617 1618@app.get( 1619 "/configuration/schedules/{schedules_name}", tags=["schedules", "configuration"] 1620) 1621def read_schedule_by_name( 1622 *, 1623 response: Response, 1624 current_user: User = Depends(get_current_user), 1625 schedules_name: str = Path(..., title="Client name to look for"), 1626 verbose: Optional[bareosBool] = Query("yes", title="Verbose output"), 1627): 1628 """ 1629 Read all jobdef resources. Built on console command _show schedules_. 1630 1631 Needs at least Bareos Version >= 20.0.0 1632 """ 1633 return show_configuration_items( 1634 response=response, 1635 current_user=current_user, 1636 itemType="schedules", 1637 byName=schedules_name, 1638 verbose=verbose, 1639 ) 1640 1641 1642@app.post("/configuration/schedules", tags=["schedules", "configuration"]) 1643def create_schedule( 1644 *, 1645 response: Response, 1646 current_user: User = Depends(get_current_user), 1647 scheduleDef: scheduleResource = Body(..., title="Name for new schedule"), 1648): 1649 """ 1650 Create a new schedule resource. 1651 Console command used _configure add schedule_ 1652 """ 1653 return configure_add_standard_component( 1654 response=response, 1655 componentDef=scheduleDef, 1656 componentType="schedule", 1657 current_user=current_user, 1658 ) 1659 1660 1661@app.put( 1662 "/control/schedules/enable/{schedule_name}", 1663 status_code=204, 1664 tags=["schedules", "control"], 1665) 1666def enable_schedule( 1667 *, 1668 schedule_name: str = Path(..., title="The schedule (name) to enable"), 1669 response: Response, 1670 current_user: User = Depends(get_current_user), 1671): 1672 (result, jsonMessage) = switch_resource( 1673 response=response, 1674 current_user=current_user, 1675 resourceName=schedule_name, 1676 componentType="schedule", 1677 enable=True, 1678 ) 1679 if result: 1680 response.status_code = 204 # ok, but empty return-string 1681 else: 1682 response.status_code = 500 1683 return jsonMessage 1684 1685 1686@app.put( 1687 "/control/schedules/disable/{schedule_name}", 1688 status_code=204, 1689 tags=["schedules", "control"], 1690) 1691def disable_schedule( 1692 *, 1693 schedule_name: str = Path(..., title="The schedule (name) to disable"), 1694 response: Response, 1695 current_user: User = Depends(get_current_user), 1696): 1697 (result, jsonMessage) = switch_resource( 1698 response=response, 1699 current_user=current_user, 1700 resourceName=schedule_name, 1701 componentType="schedule", 1702 enable=False, 1703 ) 1704 if result: 1705 response.status_code = 204 # ok, but empty return-string 1706 else: 1707 response.status_code = 500 1708 return jsonMessage 1709 1710 1711### Storages 1712 1713 1714@app.get("/configuration/storages", tags=["storages", "configuration"]) 1715def read_all_storages( 1716 *, 1717 response: Response, 1718 current_user: User = Depends(get_current_user), 1719 verbose: Optional[bareosBool] = Query("yes", title="Verbose output"), 1720): 1721 """ 1722 Read all jobdef resources. Built on console command _show storages_. 1723 1724 Needs at least Bareos Version >= 20.0.0 1725 """ 1726 return show_configuration_items( 1727 response=response, 1728 current_user=current_user, 1729 itemType="storages", 1730 verbose=verbose, 1731 ) 1732 1733 1734@app.get("/configuration/storages/{storages_name}", tags=["storages", "configuration"]) 1735def read_storage_by_name( 1736 *, 1737 response: Response, 1738 current_user: User = Depends(get_current_user), 1739 storages_name: str = Path(..., title="Client name to look for"), 1740 verbose: Optional[bareosBool] = Query("yes", title="Verbose output"), 1741): 1742 """ 1743 Read all jobdef resources. Built on console command _show storages_. 1744 1745 Needs at least Bareos Version >= 20.0.0 1746 """ 1747 return show_configuration_items( 1748 response=response, 1749 current_user=current_user, 1750 itemType="storages", 1751 byName=storages_name, 1752 verbose=verbose, 1753 ) 1754 1755 1756@app.post("/configuration/storage", tags=["storages", "configuration"]) 1757def post_storage( 1758 *, 1759 storageDef: storageResource = Body(..., title="storage resource"), 1760 response: Response, 1761 current_user: User = Depends(get_current_user), 1762): 1763 """ 1764 Create a new storage resource. 1765 Console command used: _configure add storage_ 1766 """ 1767 return configure_add_standard_component( 1768 response=response, 1769 componentDef=storageDef, 1770 componentType="storage", 1771 current_user=current_user, 1772 ) 1773 1774 1775### Users, profiles, consoles 1776 1777 1778@app.get("/configuration/users", tags=["users", "configuration"]) 1779def read_all_users( 1780 *, 1781 response: Response, 1782 current_user: User = Depends(get_current_user), 1783 verbose: Optional[bareosBool] = Query("yes", title="Verbose output"), 1784): 1785 """ 1786 Read all users resources. Built on console command _show users_. 1787 1788 Needs at least Bareos Version >= 20.0.0 1789 """ 1790 return show_configuration_items( 1791 response=response, current_user=current_user, itemType="users", verbose=verbose 1792 ) 1793 1794 1795@app.get("/configuration/users/{users_name}", tags=["users", "configuration"]) 1796def read_user_by_name( 1797 *, 1798 response: Response, 1799 current_user: User = Depends(get_current_user), 1800 users_name: str = Path(..., title="Client name to look for"), 1801 verbose: Optional[bareosBool] = Query("yes", title="Verbose output"), 1802): 1803 """ 1804 Read all jobdef resources. Built on console command _show users_. 1805 1806 Needs at least Bareos Version >= 20.0.0 1807 """ 1808 return show_configuration_items( 1809 response=response, 1810 current_user=current_user, 1811 itemType="users", 1812 byName=users_name, 1813 verbose=verbose, 1814 ) 1815 1816 1817@app.post("/configuration/users", tags=["users", "configuration"]) 1818def post_user( 1819 *, 1820 userDef: userResource = Body(..., title="user resource"), 1821 response: Response, 1822 current_user: User = Depends(get_current_user), 1823): 1824 """ 1825 Create a new Bareos user resource. 1826 Console command used: _configure add user_ 1827 1828 """ 1829 return configure_add_standard_component( 1830 response=response, 1831 componentDef=userDef, 1832 componentType="user", 1833 current_user=current_user, 1834 ) 1835 1836 1837@app.get("/configuration/profiles", tags=["profiles", "configuration"]) 1838def read_all_profiles( 1839 *, 1840 response: Response, 1841 current_user: User = Depends(get_current_user), 1842 verbose: Optional[bareosBool] = Query("yes", title="Verbose output"), 1843): 1844 """ 1845 Read all jobdef resources. Built on console command _show profiles_. 1846 1847 Needs at least Bareos Version >= 20.0.0 1848 """ 1849 return show_configuration_items( 1850 response=response, 1851 current_user=current_user, 1852 itemType="profiles", 1853 verbose=verbose, 1854 ) 1855 1856 1857@app.get("/configuration/profiles/{profiles_name}", tags=["profiles", "configuration"]) 1858def read_client_by_name( 1859 *, 1860 response: Response, 1861 current_user: User = Depends(get_current_user), 1862 profiles_name: str = Path(..., title="Client name to look for"), 1863 verbose: Optional[bareosBool] = Query("yes", title="Verbose output"), 1864): 1865 """ 1866 Read all jobdef resources. Built on console command _show profiles_. 1867 1868 Needs at least Bareos Version >= 20.0.0 1869 """ 1870 return show_configuration_items( 1871 response=response, 1872 current_user=current_user, 1873 itemType="profiles", 1874 byName=profiles_name, 1875 verbose=verbose, 1876 ) 1877 1878 1879@app.post("/configuration/profiles", tags=["users", "configuration"]) 1880def post_profile( 1881 *, 1882 profileDef: profileResource = Body(..., title="profile resource"), 1883 response: Response, 1884 current_user: User = Depends(get_current_user), 1885): 1886 """ 1887 Create a new Bareos profile resource. 1888 Console command used: _configure add profile_ 1889 1890 """ 1891 return configure_add_standard_component( 1892 response=response, 1893 componentDef=profileDef, 1894 componentType="profile", 1895 current_user=current_user, 1896 ) 1897 1898 1899@app.get("/configuration/consoles", tags=["consoles", "configuration"]) 1900def read_all_consoles( 1901 *, 1902 response: Response, 1903 current_user: User = Depends(get_current_user), 1904 verbose: Optional[bareosBool] = Query("yes", title="Verbose output"), 1905): 1906 """ 1907 Read all jobdef resources. Built on console command _show consoles_. 1908 1909 Needs at least Bareos Version >= 20.0.0 1910 """ 1911 return show_configuration_items( 1912 response=response, 1913 current_user=current_user, 1914 itemType="consoles", 1915 verbose=verbose, 1916 ) 1917 1918 1919@app.get("/configuration/consoles/{consoles_name}", tags=["consoles", "configuration"]) 1920def read_console_by_name( 1921 *, 1922 response: Response, 1923 current_user: User = Depends(get_current_user), 1924 consoles_name: str = Path(..., title="Console name to look for"), 1925 verbose: Optional[bareosBool] = Query("yes", title="Verbose output"), 1926): 1927 """ 1928 Read all jobdef resources. Built on console command _show consoles_. 1929 1930 Needs at least Bareos Version >= 20.0.0 1931 """ 1932 return show_configuration_items( 1933 response=response, 1934 current_user=current_user, 1935 itemType="consoles", 1936 byName=consoles_name, 1937 verbose=verbose, 1938 ) 1939 1940 1941@app.post("/configuration/consoles", tags=["users", "configuration"]) 1942def post_console( 1943 *, 1944 consoleDef: consoleResource = Body(..., title="console resource"), 1945 response: Response, 1946 current_user: User = Depends(get_current_user), 1947): 1948 """ 1949 Create a new Bareos console resource. 1950 Console command used: _configure add console_ 1951 1952 """ 1953 return configure_add_standard_component( 1954 response=response, 1955 componentDef=consoleDef, 1956 componentType="console", 1957 current_user=current_user, 1958 ) 1959 1960 1961### Director 1962 1963 1964@app.get("/control/directors/version", tags=["directors", "control"]) 1965def read_director_version( 1966 *, response: Response, current_user: User = Depends(get_current_user) 1967): 1968 """ 1969 Read director version. Command used: _version_ 1970 """ 1971 result = None 1972 dirCommand = "version" 1973 try: 1974 result = current_user.jsonDirector.call(dirCommand) 1975 except Exception as e: 1976 raise HTTPException( 1977 status_code=500, detail="Could not read version from director" 1978 ) 1979 if result and "version" in result: 1980 return result["version"] 1981 else: 1982 response.status_code = 404 1983 return {"message": "No version info returned"} 1984 1985 1986@app.get("/control/directors/time", tags=["directors", "control"]) 1987def read_director_time( 1988 *, response: Response, current_user: User = Depends(get_current_user) 1989): 1990 """ 1991 Read director time. Command used: _time_ 1992 """ 1993 result = None 1994 dirCommand = "time" 1995 try: 1996 result = current_user.jsonDirector.call(dirCommand) 1997 except Exception as e: 1998 response.status_code = 500 1999 return { 2000 "message": "Could not read director time %s. Message: '%s'" 2001 % (current_user.directorName, e) 2002 } 2003 if result and "time" in result: 2004 return result["time"] 2005 else: 2006 response.status_code = 404 2007 return {"message": "No time info returned"} 2008 2009 2010@app.put("/control/directors/reload", tags=["directors", "control"]) 2011def read_director_time( 2012 *, response: Response, current_user: User = Depends(get_current_user) 2013): 2014 """ 2015 Reload director configuration from files. Command used: _reload_ 2016 """ 2017 result = None 2018 dirCommand = "reload" 2019 try: 2020 result = current_user.jsonDirector.call(dirCommand) 2021 except Exception as e: 2022 response.status_code = 500 2023 return { 2024 "message": "Could not reload director %s. Message: '%s'" 2025 % (current_user.directorName, e) 2026 } 2027 if result and "reload" in result: 2028 return result["reload"] 2029 else: 2030 response.status_code = 500 2031 return result 2032