NetWorker REST API - Python
In the last article we looked at the basics of NetWorker REST API, we saw how to authenticate and also saw the endpoints available. In case you have missed that video/article, click here, I strongly suggest you take a look at it before going ahead with this.
There are a number of approaches and tools that you can use to make use of the NetWorker REST API, here we are going to use python to create a web app. The application that I currently have is very basic but lays a foundation that you can build upon and customize as you like. The code is available and can be downloaded/cloned from my github page here - https://github.com/crazyrov/NW_API_Python. The code is public so you can contribute to make it better, this can be our little community project that we can all improve so that everyone in our community can use it. I will add details of any updates to the end of this page as I enhance the code. You can also send me suggestions via the youtube comments , twitter or by email and I will try to implement those into this code.
NW_API_Python
https://github.com/crazyrov/NW_API_PythonIf anyone out there is planning on creating a Mobile application I say go for it, this app can be used as a middleware between the externally exposed endpoint of your own and the NetWorker API endpoints. I would defintly like to listen to your ideas.
In the last video we used curl for interaction with the API. There is another very handy tool when it comes to working with an API endpoint called Postman. This is free software and very easy to use. I am suggesting postman for testing because it renders the JSON output that the API sends back beautifully which is very important as we will need to understand the structure of the JSON so that we can code accordingly to render it in python.
Alright, let me give you a quick overview on how I am using python and NetWorker REST API. I am creating a custom portal or web app that we can customize to any extent that we need. In the framework that I have created I just display the clients configured and the backups but sky is the limit (and the limitations in the API ;D ) with what you can with this. I have used bootstrap with HTML to create clear crisp visuals. I am using the python Flask library to create a web server and jinja templates to create the web pages.
Authentication
The authentication code implemented here is not enterprise grade so in case you want to use this with your customers the authentication part of the app needs to be re-written. With the current version as of May 21st 2021 I am using a login page to take in the credentials for NetWorker but since I am saving the encoded string on the server anyone else trying to use the app from any other system will be automatically authenticated.
Backups
On this page I am listing all the backups that I get from the backups endpoint of the API. There are many more details that you can get
from the JSON returned by the API and can be customized to your requirement.
Clients
On this page I am listing all the clients that I get from the “clients” endpoint of the API. There are many more details that you can get from the JSON returned by the API and can be customized to your requirement.
To understand the code better I recommend you to watch the video which is available on the top of this page. It contains a walk through the code to help understand the code better.
Code
In this section I am just putting all the current code with respect to the file from the application at its current state.
This is the folders and files that I am using.
I:\PROJECTS\NW_API_PYTHON | .gitignore | backups.py | clients.py | main.py | requirments.txt | +---templates | backups.html | clients.html | index.html | layout.html | navigator.html |
main.py
from flask import Flask, render_template, request, redirect, url_for import base64 from backups import backup_list from clients import client_list app = Flask(__name__) credentials = "" # Route for the login page. @app.route('/') def default(): if len(credentials) > 0: return redirect("/backups", code=302) return render_template('index.html') # Create a base64 encoded string for the credentails @app.route('/home',methods=['GET','POST']) def home(): # Check if user and password are not 0 length string global credentials if request.method == "POST": user = request.form.get('login') password = request.form.get('password') message = user+":"+password message_bytes = message.encode('ascii') base64_bytes = base64.b64encode(message_bytes) credentials = base64_bytes.decode('ascii') return redirect("/backups", code=302) else: if len(credentials) == 0: return redirect("/", code=302) return "Home Page " # Route to get the list of all Backups @app.route('/backups',methods=['GET']) def backups(): if len(credentials) == 0: return redirect("/", code=302) return backup_list(credentials, request) # Route to get the list of clients configured @app.route('/clients',methods=['GET']) def client(): if len(credentials) == 0: return redirect("/", code=302) return client_list(credentials, request) # Route to clear the authentication string @app.route('/log_out', methods=['GET']) def log_out(): global credentials credentials = "" return redirect("/", code=302) if __name__ == '__main__': app.run()
backups.py
from flask import render_template, redirect import requests as re import json # # curl -X GET -k -u Administrator:password@123 -H "Content-Type: application/json" https://nsr-linux:9090/nwrestapi/v3/global/alerts # def backup_list(creds, request): headers = { 'Authorization': 'Basic ' + creds + '' } r = re.get('https://192.168.31.50:9090/nwrestapi/v2/global/backups', headers=headers, verify=False) #incase of any errors redirect to the default page of the app if r.status_code >= 400: return redirect("/") # Sort the list based on the clieants hostname and savetime of the client content = sorted(json.loads(r.content)['backups'], key = lambda i: (i['clientHostname'], i['creationTime']), reverse=True) return render_template('backups.html', content=content)
clients.py
from flask import render_template, redirect import requests as re import json import numpy def client_list(creds, request): headers = { 'Authorization': 'Basic ' + creds + '' } r = re.get('https://192.168.31.50:9090/nwrestapi/v3/global/clients', headers=headers, verify=False) #incase of any errors redirect to the default page of the app if r.status_code >= 400: return redirect("/") # Sort the list based on the hostname content = sorted(json.loads(r.content)['clients'], key = lambda i: i['hostname'], reverse=True) return render_template('clients.html', content=content)
requirments.txt
Flask
navigator.html
<nav class="navbar navbar-expand-lg navbar-light bg-light"> <div class="container-fluid"> <a class="navbar-brand" href="https://crazyrov.com/"> <img src="https://lh3.googleusercontent.com/a-/AOh14GiElJWN9Dx3ICSpt3iux4hRqAvELpnS0vgRNy1U=s600-k-no-rp-mo" alt="crazyrov_logo" width="30" height="30" class="d-inline-block align-text-top"> CRAZYROV STUDIO </a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarNav"> <ul class="navbar-nav "> <li class="nav-item"> <a class="nav-link" aria-current="page" href="/backups">Backups</a> </li> <li class="nav-item"> <a class="nav-link" href="/clients">Clients</a> </li> <li class="nav-item"> <a class="nav-link" href="https://crazyrov.com/prod/articles/articles_networker_restapi_python.php">Blog</a> </li> <li class="nav-item"> <a class="nav-link" href="https://www.youtube.com/channel/UC4JLsVyXMyjbQ4i1NaJj5IQ">YouTube Channel</a> </li>a <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false"> NetWorker Command App </a> <ul class="dropdown-menu" aria-labelledby="navbarDropdown"> <li><a class="dropdown-item" href="https://qrgo.page.link/nBvSt">Google Play Store</a></li> <li><a class="dropdown-item" href="https://apps.apple.com/us/app/networker-commands/id1553582875#?platform=iphone">Apple Store</a></li> </ul> </li> </ul> <ul class="navbar-nav d-flex d-flex justify-content-end"> <li class="nav-item justify-content-end"> <a class="nav-link" href="/log_out">Log Out</a> </li> </ul> </div> </div> </nav>
layout.html
<!doctype html> <html lang="en"> <head> <!-- Required meta tags --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- Bootstrap CSS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-wEmeIV1mKuiNpC+IOBjI7aAzPcEZeedi5yW5f2yOq55WWLwNGmvvx4Um1vskeMj0" crossorigin="anonymous"> <title>NW API - DEMO</title> <style> .content { max-width: 500px; margin: auto; } </style> </head> <body class="d-flex flex-column min-vh-100"> {% include 'navigator.html' %} <!-- Page content --> {% block content %}{% endblock %} <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-p34f1UUtsS3wqzfto5wAAmdvj+osOnFyQFpp4Ua3gs/ZVWx6oOypYoCJhGGScy+8" crossorigin="anonymous"></script> </body> <footer> <hr class="mt-2 mb-3"/> <figure class="text-center"> <blockquote class="blockquote"> <p>“Our only limitations are those we set up in our own minds”</p> </blockquote> <figcaption class="blockquote-footer"> Napoleon Hill in <cite title="Source Title">Think and Grow Rich</cite> </figcaption> </figure> </footer> </html>
index.html
{% extends 'layout.html' %} {% block content %} <div class="content"> <div class="wrapper fadeInDown"> <div id="formContent"> <div class="card"> <div class="card-header"> Login </div> <div class="card-body"> <!-- Login Form --> <form method="POST" action="/home"> <div class="row mb-3"> <input type="text" id="login" class="fadeIn second" name="login" placeholder="Username"> </div> <div class="row mb-3"> <input type="password" id="password" class="fadeIn third" name="password" placeholder="Password"> </div> <button type="submit" class="btn btn-primary" value="Log In">Log in</button> </form> </div> </div> </div> </div> </div> {% endblock %}
backups.html
{% extends 'layout.html' %} {% block content %} <div class="container"> <h1>Backups</h1> <table class="table table-striped table-hover"> <thead> <tr class="table-dark"> <th scope="col">Client Name</th> <th scope="col">Saveset name</th> <th scope="col">level</th> <th scope="col">size(MB)</th> <th scope="col">Save Time</th> <th scope="col">SSID</th> <th scope="col">Status</th> </tr> </thead> {% for backup in content %} {% if backup.instances[0]["status"] == "Recyclable"%} <tr class="table-danger"> {% else %} <tr> {% endif %} <td>{{backup.clientHostname}}</td> <td>{{backup.name}}</td> <td>{{backup.level}}</td> <td>{{"%.2f"|format(backup.size['value']/1000/1000)}}</td> <td>{{backup.creationTime}}</td> <td>{{backup.shortId}}</td> <td>{{backup.instances[0]["status"]}}</td> </tr> {% endfor %} </table> </div> {% endblock %}
clients.html
{% extends 'layout.html' %} {% block content %} <div class="container"> <h1>Clients</h1> <table class="table table-striped table-hover"> <thead> <tr class="table-dark"> <th scope="col">Client Name</th> <th scope="col">Saveset name</th> <th scope="col">Protection Groups</th> </tr> </thead> {% for client in content %} <tr> <td>{{client.hostname}}</td> <td> {% for saveset in client.saveSets %} {{saveset}} <br> {% endfor %} </td> <td> {% for group in client.protectionGroups %} {{group}} <br> {% endfor %} </td> </tr> {% endfor %} </table> </div> {% endblock %}
Thank you for visting www.crazyrov.com, you can also check out my YouTube channel - crazyRov Studios for Data protection and cloud related technical videos.