diff --git a/.gitignore b/.gitignore
index 309de57..93008cd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
__pycache__
*.pyc
.venv
+build
diff --git a/AUTHORS b/AUTHORS
index 608cba3..6f0d20c 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1 +1,2 @@
-Jeremy Grossmann
\ No newline at end of file
+Jeremy Grossmann
+Julien Duponchelle
diff --git a/README.rst b/README.rst
index d1cdaed..f97398a 100644
--- a/README.rst
+++ b/README.rst
@@ -4,35 +4,20 @@ GNS3-registry
This is the GNS3 devices registry.
-You need python 3.4 installed
-
-Add an image
-************
+Build website
+#############
.. code:: bash
- bin/gns3-get --add ~/Downloads/linux-microcore-3.4.1.img
+ python build.py
-Search an image
-****************
+
+Run website
+#############
.. code:: bash
-
- bin/gns3-get --search core
-
- Micro Core Linux:
- * hda_disk_image:
- * 3.4.1 linux-microcore-3.4.1.img: fa2ec4b1fffad67d8103c3391bbf9df2
- * 4.0.2 linux-microcore-4.0.2-clean.img: e13d0d1c0b3999ae2386bba70417930c
-
-
-Install a remote image
-**************************
-
-If the image is available from the internet you can download and install it:
-
-.. code:: bash
-
- bin/gns3-get --install e13d0d1c0b3999ae2386bba70417930c
- Download http://downloads.sourceforge.net/project/gns-3/Qemu%20Appliances/linux-microcore-4.0.2-clean.img to /Users/noplay/GNS3/images/linux-microcore-4.0.2-clean.img
+ python server.py
+
+
+
diff --git a/bin/gns3-get b/bin/gns3-get
deleted file mode 100755
index eb851fe..0000000
--- a/bin/gns3-get
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/bin/bash
-
-SCRIPT_PATH="${BASH_SOURCE[0]}";
-GNS3_REPOSITORY_DIR=`dirname ${SCRIPT_PATH}`/..
-
-# Detect Python 3.4 installation
-
-PY34_TEST="import sys; sys.exit(1) if sys.version_info[0] != 3 or sys.version_info[1] < 4 else sys.exit(0)"
-
-if python -c "$PY34_TEST" 2> /dev/null
-then
- PYTHON="python"
-elif python3 -c "$PY34_TEST" 2> /dev/null
-then
- PYTHON="python3"
-elif python3.4 -c "$PY34_TEST" 2> /dev/null
-then
- PYTHON="python3.4"
-elif python34 -c "$PY34_TEST" 2> /dev/null
-then
- PYTHON="python34"
-else
- echo "No Python 3.4 installation"
-
- echo "On debian based distribution":
- echo "apt-get install python3"
-
- echo "On MacOSX:"
- echo "brew install python3"
- echo "port install python34"
- exit 1
-fi
-
-$PYTHON -m venv $GNS3_REPOSITORY_DIR/.venv
-source $GNS3_REPOSITORY_DIR/.venv/bin/activate
-
-$PYTHON $GNS3_REPOSITORY_DIR/gns3registry/main.py --test
-if [ $? -ne 0 ]
-then
- $PYTHON -m pip install -r $GNS3_REPOSITORY_DIR/requirements.txt
-fi
-
-$PYTHON $GNS3_REPOSITORY_DIR/gns3registry/main.py $*
-
diff --git a/build.py b/build.py
new file mode 100644
index 0000000..d78c76e
--- /dev/null
+++ b/build.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2015 GNS3 Technologies Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import os
+import sys
+import json
+import shutil
+
+from jinja2 import Environment, FileSystemLoader
+
+import logging
+log = logging.getLogger(__name__)
+log.setLevel(logging.DEBUG)
+
+handler = logging.StreamHandler(sys.stdout)
+formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
+handler.setFormatter(formatter)
+log.addHandler(handler)
+
+if os.path.exists('build'):
+ for file in os.listdir('build'):
+ if os.path.isdir(os.path.join('build', file)):
+ shutil.rmtree(os.path.join('build', file))
+ else:
+ os.remove(os.path.join('build', file))
+else:
+ os.mkdir('build')
+os.mkdir(os.path.join('build', 'devices'))
+
+
+def render(template_file, out, **kwargs):
+ log.info('Build %s', out)
+ env = Environment(loader=FileSystemLoader('templates'))
+ env.filters['jsonify'] = json.dumps
+ template = env.get_template(template_file)
+ template.stream(**kwargs).dump(os.path.join('build', out))
+
+
+render('index.html', 'index.html')
+
+
+devices = []
+for file in os.listdir('devices'):
+ filename = file[:-5]
+ with open(os.path.join('devices', file)) as f:
+ device = json.load(f)
+ device['id'] = filename
+ render('device.html', os.path.join('devices', filename + '.html'), device=device)
+ devices.append(device)
+
+
+render('devices.html', os.path.join('devices', 'index.html'), devices=devices)
diff --git a/gns3registry/__init__.py b/gns3registry/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/gns3registry/config.py b/gns3registry/config.py
deleted file mode 100644
index 6a58c26..0000000
--- a/gns3registry/config.py
+++ /dev/null
@@ -1,218 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2015 GNS3 Technologies Inc.
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-
-import json
-import sys
-import os
-import shlex
-import subprocess
-from gns3registry.image import Image
-
-
-class ConfigException(Exception):
- pass
-
-
-class Config:
- """
- GNS3 config file
- """
-
- def __init__(self, path=None):
- """
- :params path: Path of the configuration file otherwise detect it on the system
- """
-
- #TODO: Manage errors
- self.path = path
- if self.path is None:
- self.path = self._get_standard_config_file_path()
-
- with open(self.path) as f:
- self._config = json.load(f)
-
- @property
- def images_dir(self):
- """
- :returns: Localion of the images directory on the server
- """
- return self._config["LocalServer"]["images_path"]
-
- def _get_standard_config_file_path(self):
- if sys.platform.startswith("win"):
- filename = "gns3_gui.ini"
- else:
- filename = "gns3_gui.conf"
-
- appname = "GNS3"
-
- if sys.platform.startswith("win"):
- appdata = os.path.expandvars("%APPDATA%")
- return os.path.join(appdata, appname, filename)
- else:
- home = os.path.expanduser("~")
- return os.path.join(home, ".config", appname, filename)
-
- def add_images(self, device_config):
- """
- Add images to the user configuration
- """
- new_config = {
- "server": "local",
- "name": device_config["name"]
- }
- if device_config["category"] == "guest":
- new_config["category"] = 2
- elif device_config["category"] == "router":
- new_config["category"] = 0
-
- if "qemu" in device_config:
- self._add_qemu_config(new_config, device_config)
-
- def _add_qemu_config(self, new_config, device_config):
-
- new_config["adapter_type"] = device_config["qemu"]["adapter_type"]
- new_config["adapters"] = device_config["qemu"]["adapters"]
- new_config["cpu_throttling"] = 0
- new_config["ram"] = device_config["qemu"]["ram"]
- new_config["legacy_networking"] = False
- new_config["process_priority"] = "normal"
-
- new_config["initrd"] = ""
- new_config["kernel_command_line"] = ""
- new_config["kernel_image"] = ""
-
- if device_config["qemu"].get("graphic", False):
- options = ""
- else:
- options = "-nographic "
- options += device_config["qemu"].get("options", "")
-
- new_config["options"] = options.strip()
-
- new_config["hda_disk_image"] = device_config["qemu"].get("hda_disk_image", "")
- new_config["hdb_disk_image"] = device_config["qemu"].get("hdb_disk_image", "")
- new_config["hdc_disk_image"] = device_config["qemu"].get("hdc_disk_image", "")
- new_config["hdd_disk_image"] = device_config["qemu"].get("hdd_disk_image", "")
-
- new_config["qemu_path"] = self._get_qemu_binary(device_config)
-
- if device_config["category"] == "guest":
- new_config["default_symbol"] = ":/symbols/qemu_guest.normal.svg"
- new_config["hover_symbol"] = ":/symbols/qemu_guest.selected.svg"
- elif device_config["category"] == "router":
- new_config["default_symbol"] = ":/symbols/router.normal.svg"
- new_config["hover_symbol"] = ":/symbols/router.selected.svg"
-
- disks = ["hda_disk_image", "hdb_disk_image", "hdc_disk_image", "hdd_disk_image", "cdrom"]
- for disk in disks:
- if disk in device_config["images"]:
- if isinstance(device_config["images"][disk], list):
- require_images = ""
- for image in device_config["images"][disk]:
- require_images += "* {}\n".format(image["filename"])
- raise ConfigException("Missing image for {} you should provide one of the following images:\n{}".format(disk, require_images))
- else:
- new_config["name"] += " {}".format(device_config["images"][disk].version)
- new_config[disk] = self._relative_image_path(device_config["images"][disk].path)
-
-
- if device_config["qemu"].get("install_cdrom_to_hda", False):
- new_config["hda_disk_image"] = self._create_qemu_img(device_config, new_config)
- if "cdrom" in new_config:
- self._install_qemu_cdrom(device_config, new_config)
- del new_config["cdrom"]
-
- # Remove VM with the same Name
- self._config["Qemu"]["vms"] = [item for item in self._config["Qemu"]["vms"] if item["name"] != new_config["name"]]
-
- self._config["Qemu"]["vms"].append(new_config)
-
- def _relative_image_path(self, path):
- """
- :returns: Path relative to image directory if image is inside or full path
- """
- print(os.path.dirname(path))
- print(self.images_dir)
- if os.path.dirname(path) == self.images_dir:
- return os.path.basename(path)
- return path
-
- def _get_qemu_binary(self, device_config):
- """
- Create a blank hda disk image
-
- :param device_config: The require device configuration
- """
- #TODO: Manage Windows
- if device_config["qemu"]["processor"] == "i386":
- return "qemu-system-i386"
- elif device_config["qemu"]["processor"] == "x64":
- return "qemu-system-x86_64"
-
- def _create_qemu_img(self, device_config, new_config):
- """
- Create a blank hda disk image
-
- :param device_config: The require device configuration
- :param new_config: The GNS3 device configuration
- :returns: Return new disk path
- """
- #TODO: Manage error
- image_path = os.path.join(self.images_dir, "QEMU", device_config["qemu"]["hda_disk_image"])
- #TODO: raise an error if size is missing
- cmd = ["qemu-img", "create", "-f", "qcow2", image_path, device_config["qemu"]["hda_disk_size"]]
- print(" ".join(cmd))
- subprocess.call(cmd)
- return image_path
-
- def _install_qemu_cdrom(self, device_config, new_config):
- """
- Install the cdrom to disk
-
- :param device_config: The require device configuration
- :param new_config: The GNS3 device configuration
- """
-
- print("Starting cdrom installation. Please follow the instructions in the qemu Windows and close qemu when install is finish in order to finish the process.")
- print("\nInstall instructions:")
- print(device_config["qemu"]["install_instructions"])
- cmd = "{options} -cdrom {cdrom} -m {ram} {hda}".format(
- options=device_config.get("options", ""),
- cdrom=device_config["images"]["cdrom"].path,
- ram=device_config["qemu"]["ram"],
- hda=new_config["hda_disk_image"])
- self._qemu_run(device_config, cmd.strip())
-
- def _qemu_run(self, device_config, cmd):
- """
- Run the qemu command
- """
- cmd = shlex.split(cmd)
- cmd.insert(0, self._get_qemu_binary(device_config))
- print(" ".join(cmd))
- subprocess.call(cmd)
-
- def save(self):
- """
- Save the configuration file
- """
- with open(self.path, "w+") as f:
- json.dump(self._config, f, indent=4)
-
diff --git a/gns3registry/image.py b/gns3registry/image.py
deleted file mode 100644
index f75212a..0000000
--- a/gns3registry/image.py
+++ /dev/null
@@ -1,65 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2015 GNS3 Technologies Inc.
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-import hashlib
-
-
-class Image:
- """
- A disk image
- """
-
- def __init__(self, path):
- """
- :params: path of the image
- """
- self.path = path
- self._md5sum = None
- self._version = None
-
- @property
- def version(self):
- """
- :returns: Get the file version / release
- """
- return self._version
-
- @version.setter
- def version(self, version):
- """
- :returns: Set the file version / release
- """
- self._version = version
-
- @property
- def md5sum(self):
- """
- Compute a md5 hash for file
-
- :returns: hexadecimal md5
- """
-
- if self._md5sum is None:
- m = hashlib.md5()
- with open(self.path, "rb") as f:
- while True:
- buf = f.read(4096)
- if not buf:
- break
- m.update(buf)
- self._md5sum = m.hexdigest()
- return self._md5sum
diff --git a/gns3registry/main.py b/gns3registry/main.py
deleted file mode 100644
index e3c0f6e..0000000
--- a/gns3registry/main.py
+++ /dev/null
@@ -1,107 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2015 GNS3 Technologies Inc.
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-import argparse
-import sys
-import os
-import math
-from distutils.util import strtobool
-
-sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
-
-from gns3registry.registry import Registry
-from gns3registry.config import Config, ConfigException
-
-
-def yes_no(message):
- while True:
- try:
- return strtobool(input("{} (y/n) ".format(message)).lower())
- except ValueError:
- pass
-
-def download_progress_callback(count, blockSize, totalSize):
- """
- Callback called when a file is downloading
- """
-
- if totalSize == -1:
- sys.stdout.write("Unknow size downloading...\n")
-
- percent = int(count * blockSize * 100/totalSize)
- sys.stdout.write("\r[{}{}] {} %".format("#" * math.floor(percent / 2), " " * math.ceil(100 / 2 - percent / 2), percent))
- if count * blockSize == totalSize:
- sys.stdout.write("\n")
- sys.stdout.flush()
-
-registry = Registry(download_progress_callback=download_progress_callback)
-config = Config()
-
-
-def add_images(images):
- print("WARNING WARNING WARNING")
- print("It's experimental")
- print("")
-
- confs = registry.detect_images(images)
- if len(confs) > 0:
- print("Found: {} devices configuration".format(len(confs)))
- for conf in confs:
- if yes_no("Add {}?".format(conf["name"])):
- try:
- config.add_images(conf)
- except ConfigException as e:
- print(e, file=sys.stderr)
- sys.exit(1)
- config.save()
-
-
-if __name__ == "__main__":
- parser = argparse.ArgumentParser(description="Manage GNS3 registry")
- parser.add_argument("--add", dest="add_images", action="store", nargs='+',
- help="Add images to GNS3")
- parser.add_argument("--search", dest="search", action="store",
- help="Search an image for GNS3")
- parser.add_argument("--install", dest="install", action="store",
- help="Download and install an image for GNS3")
- parser.add_argument("--test", dest="test", action="store_true",
- help="Test if installation of gns3 registry is OK")
-
- args = parser.parse_args()
-
- if args.test:
- sys.exit(0)
-
- if args.add_images:
- add_images(args.add_images)
- elif args.search:
- print("Available images\n")
- for res in registry.search_device(args.search):
- print("{}: ".format(res["name"]))
- for image_type in res["images"]:
- print(" * {}:".format(image_type))
- for file in res["images"][image_type]:
- print(" * {} {}: {}".format(file["version"], file["filename"], file["md5sum"]))
- elif args.install:
- image = registry.download_image(args.install, config.images_dir)
- if image:
- add_images([image])
- else:
- parser.print_help()
- sys.exit(1)
-
diff --git a/gns3registry/registry.py b/gns3registry/registry.py
deleted file mode 100644
index 19ebb0a..0000000
--- a/gns3registry/registry.py
+++ /dev/null
@@ -1,121 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2015 GNS3 Technologies Inc.
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-
-import hashlib
-import json
-import os
-import re
-import urllib.request
-
-from gns3registry.image import Image
-
-
-class Registry:
- def __init__(self, download_progress_callback=None):
- """
- :param download_progress_callback: Callback called when a file is downloaded
- """
- self._download_progress_callback = download_progress_callback
-
- def detect_images(self, images_path):
- """
- :param images_path: Array of path to images
- :returns: Array of configuration corresponding to the image
- """
-
- images = []
-
- for path in images_path:
- images.append(Image(path))
-
- configurations = []
-
- #TODO: Manage open error
- for config in self._all_configs():
-
- matched = False
- for image in images:
- if self._image_match(image, config):
- matched = True
- if matched:
- configurations.append(config)
-
- return configurations
-
- def download_image(self, md5sum, images_dir):
- for config in self._all_configs():
- for image_type in config.get("images", {}):
- for file in config["images"].get(image_type, []):
- if file["md5sum"] == md5sum:
- path = os.path.join(images_dir, "QEMU", file["filename"])
-
- if "direct_download_url" in file:
- print("Download {} to {}".format(file["direct_download_url"], path))
- #TODO: Skip download if file already exist with same sha1
- urllib.request.urlretrieve(file["direct_download_url"], path, reporthook=self._download_progress_callback)
- return path
- else:
- print("You need to manually download the image {filename} from:\n{download_url}\n\nAnd run: ./bin/gns3-get --add {filename}".format(filename=file["filename"], download_url=file["download_url"]))
- return None
-
- def search_device(self, query):
- results = []
- for config in self._all_configs():
- if re.match(r".*{}.*".format(query), config["name"], flags=re.IGNORECASE):
- results.append(config)
- return results
-
- def _all_configs(self):
- """
- Iterate on all configs available on devices
- """
- devices_path = self._get_devices_path()
- for (dirpath, dirnames, filenames) in os.walk(devices_path):
- for filename in filenames:
- file = os.path.join(dirpath, filename)
- if file.endswith(".json"):
- with open(os.path.join(devices_path, file)) as f:
- config = json.load(f)
- yield config
-
- def _image_match(self, image, config):
- """
- :returns: True if image is present in configuration
- """
- matched = False
-
- for image_type in config.get("images", {}):
- images = config["images"].get(image_type, [])
- # If it's not a list it's mean we have already detect the image
- if not isinstance(images, list):
- continue
- for file in images:
- if isinstance(file, dict):
- if file.get("md5sum", None) == image.md5sum:
- image.version = file["version"]
- config["images"][image_type] = image
- matched = True
- return matched
-
- def _get_devices_path(self):
- """
- Get the path where the registry files are located
- """
- path = os.path.abspath(os.path.dirname(__file__))
- return os.path.join(path, "..", "devices")
diff --git a/requirements.txt b/requirements.txt
index 68c1bb9..d1f1d25 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1 +1 @@
-jsonschema==2.4.0
+Jinja2==2.7.3
diff --git a/test/conftest.py b/server.py
similarity index 56%
rename from test/conftest.py
rename to server.py
index 2f5ab07..6ae30cc 100644
--- a/test/conftest.py
+++ b/server.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python
#
# Copyright (C) 2015 GNS3 Technologies Inc.
#
@@ -15,24 +15,15 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
-import pytest
-import urllib.request
-import tempfile
import os
+import http.server
+import socketserver
-@pytest.fixture
-def linux_microcore_img():
+PORT = 8001
- path = os.path.join(tempfile.tempdir, "linux-microcore-3.4.1.img")
- if not os.path.exists(path):
- urllib.request.urlretrieve("http://downloads.sourceforge.net/project/gns-3/Qemu%20Appliances/linux-microcore-3.4.1.img?r=&ts=1432209459&use_mirror=heanet", path)
- return path
+os.chdir('build')
+Handler = http.server.SimpleHTTPRequestHandler
+httpd = socketserver.TCPServer(('', PORT), Handler)
-
-@pytest.fixture
-def empty_file(tmpdir):
-
- path = str(tmpdir / "a")
- open(path, "w+").close()
- return path
+print('Serving at port', PORT)
+httpd.serve_forever()
diff --git a/templates/device.html b/templates/device.html
new file mode 100644
index 0000000..76bc502
--- /dev/null
+++ b/templates/device.html
@@ -0,0 +1,20 @@
+{% extends "layout/default.html" %}
+{% block body %}
+