pull/470/head
Julien Duponchelle 8 years ago
parent f39af9deb7
commit 879591eaf5
No known key found for this signature in database
GPG Key ID: F1E2485547D4595D

@ -342,3 +342,32 @@ class ProjectHandler:
raise aiohttp.web.HTTPNotFound() raise aiohttp.web.HTTPNotFound()
except PermissionError: except PermissionError:
raise aiohttp.web.HTTPForbidden raise aiohttp.web.HTTPForbidden
@classmethod
@Route.get(
r"/projects/{project_id}/export",
description="Export a project as a portable archive",
parameters={
"project_id": "The UUID of the project",
},
raw=True,
status_codes={
200: "Return the file",
404: "The path doesn't exist"
})
def export(request, response):
pm = ProjectManager.instance()
project = pm.get_project(request.match_info["project_id"])
response.content_type = 'application/gns3z'
response.headers['CONTENT-DISPOSITION'] = 'attachment; filename="{}.gns3z"'.format(project.name)
response.enable_chunked_encoding()
# Very important: do not send a content length otherwise QT close the connection but curl can consume the Feed
response.content_length = None
response.start(request)
for data in project.export():
response.write(data)
yield from response.drain()
yield from response.write_eof()

@ -20,6 +20,8 @@ import os
import shutil import shutil
import asyncio import asyncio
import hashlib import hashlib
import zipstream
import zipfile
from uuid import UUID, uuid4 from uuid import UUID, uuid4
from .port_manager import PortManager from .port_manager import PortManager
@ -507,3 +509,30 @@ class Project:
break break
m.update(buf) m.update(buf)
return m.hexdigest() return m.hexdigest()
def export(self):
"""
Export the project as zip. It's a ZipStream object.
The file will be read chunk by chunk when you iterate on
the zip.
It will ignore some files like snapshots and
:returns: ZipStream object
"""
z = zipstream.ZipFile()
# topdown allo to modify the list of directory in order to ignore
# directory
for root, dirs, files in os.walk(self._path, topdown=True):
# Remove snapshots
if "project-files" in root:
dirs[:] = [d for d in dirs if d != "snapshots"]
# Ignore log files and OS noise
files = [f for f in files if not f.endswith('_log.txt') and not f.endswith('.log') and f != '.DS_Store']
for file in files:
path = os.path.join(root, file)
z.write(path, os.path.relpath(path, self._path))
return z

@ -3,3 +3,4 @@ aiohttp==0.19.0
Jinja2>=2.7.3 Jinja2>=2.7.3
raven>=5.2.0 raven>=5.2.0
psutil>=3.0.0 psutil>=3.0.0
zipstream>=1.1.3

@ -23,6 +23,7 @@ import uuid
import os import os
import asyncio import asyncio
import aiohttp import aiohttp
import zipfile
from unittest.mock import patch from unittest.mock import patch
from tests.utils import asyncio_patch from tests.utils import asyncio_patch
@ -283,3 +284,23 @@ def test_write_file(server, tmpdir):
response = server.post("/projects/{project_id}/files/../hello".format(project_id=project.id), body="universe", raw=True) response = server.post("/projects/{project_id}/files/../hello".format(project_id=project.id), body="universe", raw=True)
assert response.status == 403 assert response.status == 403
def test_export(server, tmpdir, loop, project):
os.makedirs(project.path, exist_ok=True)
with open(os.path.join(project.path, 'a'), 'w+') as f:
f.write('hello')
response = server.get("/projects/{project_id}/export".format(project_id=project.id), raw=True)
assert response.status == 200
assert response.headers['CONTENT-TYPE'] == 'application/gns3z'
assert response.headers['CONTENT-DISPOSITION'] == 'attachment; filename="{}.gns3z"'.format(project.name)
with open(str(tmpdir / 'project.zip'), 'wb+') as f:
f.write(response.body)
with zipfile.ZipFile(str(tmpdir / 'project.zip')) as myzip:
with myzip.open("a") as myfile:
content = myfile.read()
assert content == b"hello"

@ -219,7 +219,6 @@ def test_backup_projects(server, tmpdir, loop):
assert response.headers['CONTENT-TYPE'] == 'application/x-gtar' assert response.headers['CONTENT-TYPE'] == 'application/x-gtar'
with open(str(tmpdir / 'projects.tar'), 'wb+') as f: with open(str(tmpdir / 'projects.tar'), 'wb+') as f:
print(len(response.body))
f.write(response.body) f.write(response.body)
tar = tarfile.open(str(tmpdir / 'projects.tar'), 'r') tar = tarfile.open(str(tmpdir / 'projects.tar'), 'r')

@ -20,6 +20,7 @@ import os
import asyncio import asyncio
import pytest import pytest
import aiohttp import aiohttp
import zipfile
from uuid import uuid4 from uuid import uuid4
from unittest.mock import patch from unittest.mock import patch
@ -258,3 +259,30 @@ def test_list_files(tmpdir, loop):
"md5sum": "098f6bcd4621d373cade4e832627b4f6" "md5sum": "098f6bcd4621d373cade4e832627b4f6"
} }
] ]
def test_export(tmpdir):
project = Project()
path = project.path
os.makedirs(os.path.join(path, "vm-1", "dynamips"))
with open(os.path.join(path, "vm-1", "dynamips", "test"), 'w+') as f:
f.write("HELLO")
with open(os.path.join(path, "vm-1", "dynamips", "test_log.txt"), 'w+') as f:
f.write("LOG")
os.makedirs(os.path.join(path, "project-files", "snapshots"))
with open(os.path.join(path, "project-files", "snapshots", "test"), 'w+') as f:
f.write("WORLD")
z = project.export()
with open(str(tmpdir / 'zipfile.zip'), 'wb') as f:
for data in z:
f.write(data)
with zipfile.ZipFile(str(tmpdir / 'zipfile.zip')) as myzip:
with myzip.open("vm-1/dynamips/test") as myfile:
content = myfile.read()
assert content == b"HELLO"
assert 'project-files/snapshots/test' not in myzip.namelist()
assert 'vm-1/dynamips/test_log.txt' not in myzip.namelist()

Loading…
Cancel
Save