1.2+ Config export, backup and rollback

Is there a way to rollback the config that is entered via the Admin | Config page to a known good config, or at least the previous config?

Surely there should be a way to checkpoint the config and perform a rollback ?

At the very least how about providing a way to export the config to a text file as a form of backup that can then be imported to restore the config to its previous state?

There isn’t a built-in export/import function but the API exposes everything you need to do this. This is what I wrote to manage my configs from dev to production with git being used for version control. Just be careful if you’re using this to sync systems. If you’ve deleted a parameter it will still exist on the destination server and needs to be manually removed.

#!/bin/python
# export/import genieACS 1.2 configuration files using the UI API
# PGunter 20200122

import requests, json, os, getopt, sys, re

# Globals
apiURL = "http://localhost:3000/"
baseDIR = "/opt/genieacs/local/configs"

# List of configuration sections to operate on
configs = ["config","permissions","presets","provisions","users","variables","virtualParameters"]


def exportConfigs(webSession):
   for config in configs:
      # Create directories to save configuration files if they don't exist
      path = baseDIR + "/" + config
      if not os.path.exists(path):
         os.makedirs(path)

      URL = apiURL + "api/" + config + "/"
      # Download complete config
      request = webSession.get(URL)
      data = json.loads(request.content)
      for item in data:
         # Save each config item into a seperate file
         name = item["_id"]
         file = path + "/" + name + ".json"
         # JSON dump to disk
         with open(file, 'w') as json_file:
            json.dump(item, json_file)


def importConfigs(webSession):
   for config in configs:
      path = baseDIR + "/" + config
      for filename in os.listdir(path):
         # Walk the configuration directories looking for JSON files
         if filename.endswith(".json"):
            file = path + "/" + filename
            with open(file, "r") as json_file:
               conf = json.load(json_file)
            # Extract _id from the JSON file
            id = conf['_id']
            # Remove _id from the JSON object. Required by GenieACS API
            del conf['_id']
            # Build URL e.g. http://localhost:3000/api/presets/inform
            URL = apiURL + "api/" + config + "/" + id
            # Upload JSON file
            request = webSession.put(URL, json = conf)
            if str(request.status_code) != "200":
               # Display error if we don't receive a 200 OK response
               print str(request.status_code) + ": Failed to upload " + file

def usage():
   print "usage: configMgr [--password <password>] --import | --export"
   print "Options:"
   print "-e, --export      Save configruation files to /opt/genieacs/local/configs"
   print "-i, --import      Load configruation files from /opt/genieacs/local/configs"
   print "-p, --password    admin password for the GenieACS UI"
   print   

def main():
   # variables
   if len(sys.argv) <= 1:
      usage()
      sys.exit(1)
   try:
      opts, args = getopt.getopt(sys.argv[1:], "heip:", ["help", "export", "import", "password="])
   except getopt.GetoptError as err:
      # print help information and exit:
      print str(err)
      usage()
      sys.exit(2)
   apiPassword = "admin"
   apiAction = ""
   for o, a in opts:
      if o in ("-h", "--help"):
         usage()
         sys.exit()
      if o in ("-p", "--password"):
         apiPassword = a
      if o in ("-e", "--export"):
         apiAction = "export"
      if o in ("-i", "--import"):
         apiAction = "import"

   # Check which action to run
   if apiAction in ["export", "import"]:
      # Login to GenieACS UI and save session
      apiSession = requests.Session()
      URL = apiURL + "login"
      request = apiSession.post(URL, json={"username": "admin", "password": apiPassword})
      # Check request successful
      if str(request.status_code) != "200":
         # Display error if we don't receive a 200 OK response
         print ("Unable to login to GenieACS. Error %d: %s"% (request.status_code, request.text))
         sys.exit(2)

      # Which action
      if apiAction == "export":
         print "Exporting GenieACS configuration"
         exportConfigs(apiSession)
      elif apiAction == "import":
         print "Importing GenieACS configuration"
         importConfigs(apiSession)

   else:
        print "Unknown action"
        usage()

if __name__ == "__main__":
   main()
1 Like

Thank you, that is very helpful, I will give it a try.

Is this portable across python 2.7 and 3.x ?

I’m using RHEL7 boxes for GenieACS so I’ve only been using this with python 2.7.
That said I’ve just tested against python 3.6 and the only issue that I found was the print statements needed to be put in brackets
eg
< print(str(request.status_code) + ": Failed to upload " + file)

> print str(request.status_code) + ": Failed to upload " + file

Thank you.

My current test platform has python 2.7.

I get the following error.

Are you sure the GenieACS UI is running? Are you running the script on the GenieACS server? Look at the last line on your screen grab. Connection refused…

Thank you.

Yes, the UI is running. The problem seems to be related to the container plumbing.

I made this change to the script:

##apiURL = “http://localhost:3000/
apiURL = “http://192.16.90.160:3000/

Now I don’t get the connection error, but it fails with “[Errno 110] Connection timeout” instead of “[Errno 111] Connection refused”.

I am seeking help from the docker experts in my team.

OK, several problems sorted out to get the script running (see below) but now the usage is not quite as expected.

The ‘import’ action effectively merges the config with whatever is already present instead of replacing it.
Is this your intention?

In my case I am aiming for the ‘import’ action to completely replace the config on the target system.

Fixes for my context:

  1. Helps to use the correct IP address, I had a typo in the hard-coded address

  2. The list of Mongo tables included ‘variables’ and this would crash with a permissions problem, removing it solved the problem.

  3. Hard-coded directory path required sudo permissions to create. Created and modified the permissions from comamnd-line with sudo.

With these changes the script runs. I am uncertain of the implications of not including the ‘variables’ table. Any thoughts?