2016-07-21 19:15:35 +03:00
#!/usr/bin/env python
#
# Copyright (C) 2016 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 <http://www.gnu.org/licenses/>.
import os
import json
2016-07-21 21:17:36 +03:00
import asyncio
2016-07-21 19:15:35 +03:00
import aiohttp
import zipfile
2016-07-21 21:17:36 +03:00
import tempfile
2016-07-21 19:15:35 +03:00
import zipstream
2016-10-26 17:50:01 +03:00
import logging
log = logging . getLogger ( __name__ )
2016-07-21 21:17:36 +03:00
@asyncio.coroutine
2016-07-25 15:47:37 +03:00
def export_project ( project , temporary_dir , include_images = False , keep_compute_id = False , allow_all_nodes = False ) :
2016-07-21 19:15:35 +03:00
"""
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
2016-07-21 21:17:36 +03:00
: param temporary_dir : A temporary dir where to store intermediate data
2016-07-25 15:47:37 +03:00
: param keep_compute_id : If false replace all compute id by local it ' s the standard behavior for .gns3project to make them portable
: param allow_all_nodes : Allow all nodes type to be include in the zip even if not portable default False
2016-07-21 19:15:35 +03:00
: returns : ZipStream object
"""
# To avoid issue with data not saved we disallow the export of a running topologie
if project . is_running ( ) :
raise aiohttp . web . HTTPConflict ( text = " Running topology could not be exported " )
2017-02-27 12:08:58 +02:00
# Make sure we save the project
project . dump ( )
2016-09-08 12:52:16 +03:00
z = zipstream . ZipFile ( allowZip64 = True )
2016-07-21 19:15:35 +03:00
2016-11-25 18:18:23 +02:00
if not os . path . exists ( project . _path ) :
raise aiohttp . web . HTTPNotFound ( text = " The project doesn ' t exist at location {} " . format ( project . _path ) )
2016-07-21 19:15:35 +03:00
# First we process the .gns3 in order to be sure we don't have an error
for file in os . listdir ( project . _path ) :
if file . endswith ( " .gns3 " ) :
2016-07-25 15:47:37 +03:00
_export_project_file ( project , os . path . join ( project . _path , file ) , z , include_images , keep_compute_id , allow_all_nodes )
2016-07-21 19:15:35 +03:00
for root , dirs , files in os . walk ( project . _path , topdown = True ) :
2016-07-21 21:17:36 +03:00
files = [ f for f in files if not _filter_files ( os . path . join ( root , f ) ) ]
2016-07-21 19:15:35 +03:00
for file in files :
path = os . path . join ( root , file )
# Try open the file
try :
open ( path ) . close ( )
except OSError as e :
msg = " Could not export file {} : {} " . format ( path , e )
log . warn ( msg )
2016-12-01 11:27:05 +02:00
project . controller . notification . emit ( " log.warning " , { " message " : msg } )
2016-07-21 19:15:35 +03:00
continue
2016-07-25 15:47:37 +03:00
if file . endswith ( " .gns3 " ) :
pass
else :
z . write ( path , os . path . relpath ( path , project . _path ) , compress_type = zipfile . ZIP_DEFLATED )
2016-07-21 21:17:36 +03:00
for compute in project . computes :
2016-07-22 12:43:14 +03:00
if compute . id != " local " :
2016-07-21 21:17:36 +03:00
compute_files = yield from compute . list_files ( project )
for compute_file in compute_files :
if not _filter_files ( compute_file [ " path " ] ) :
2016-07-22 12:43:14 +03:00
( fd , temp_path ) = tempfile . mkstemp ( dir = temporary_dir )
f = open ( fd , " wb " , closefd = True )
2016-09-19 17:51:15 +03:00
response = yield from compute . download_file ( project , compute_file [ " path " ] )
2016-07-21 21:17:36 +03:00
while True :
2016-09-19 17:51:15 +03:00
data = yield from response . content . read ( 512 )
2016-07-21 21:17:36 +03:00
if not data :
break
2016-07-22 12:43:14 +03:00
f . write ( data )
2016-09-19 17:51:15 +03:00
response . close ( )
2016-07-22 12:43:14 +03:00
f . close ( )
2016-07-21 21:17:36 +03:00
z . write ( temp_path , arcname = compute_file [ " path " ] , compress_type = zipfile . ZIP_DEFLATED )
2016-07-21 19:15:35 +03:00
return z
2016-07-21 21:17:36 +03:00
def _filter_files ( path ) :
"""
: returns : True if file should not be included in the final archive
"""
s = os . path . normpath ( path ) . split ( os . path . sep )
2016-07-26 11:32:43 +03:00
if path . endswith ( " snapshots " ) :
return True
2016-07-21 21:17:36 +03:00
try :
i = s . index ( " project-files " )
if s [ i + 1 ] in ( " tmp " , " captures " , " snapshots " ) :
return True
except ( ValueError , IndexError ) :
pass
file_name = os . path . basename ( path )
# Ignore log files and OS noises
if file_name . endswith ( ' _log.txt ' ) or file_name . endswith ( ' .log ' ) or file_name == ' .DS_Store ' :
return True
return False
2016-07-25 15:47:37 +03:00
def _export_project_file ( project , path , z , include_images , keep_compute_id , allow_all_nodes ) :
2016-07-21 19:15:35 +03:00
"""
Take a project file ( . gns3 ) and patch it for the export
We rename the . gns3 project . gns3 to avoid the task to the client to guess the file name
: param path : Path of the . gns3
"""
# Image file that we need to include in the exported archive
images = set ( )
with open ( path ) as f :
topology = json . load ( f )
2016-07-22 14:39:57 +03:00
if " topology " in topology :
2016-07-25 15:47:37 +03:00
if " nodes " in topology [ " topology " ] :
for node in topology [ " topology " ] [ " nodes " ] :
2017-02-14 17:41:31 +02:00
if node [ " node_type " ] == " virtualbox " and node . get ( " properties " , { } ) . get ( " linked_clone " ) :
raise aiohttp . web . HTTPConflict ( text = " Topology with a linked {} clone could not be exported. Use qemu instead. " . format ( node [ " node_type " ] ) )
2016-07-25 15:47:37 +03:00
if not allow_all_nodes and node [ " node_type " ] in [ " virtualbox " , " vmware " , " cloud " ] :
raise aiohttp . web . HTTPConflict ( text = " Topology with a {} could not be exported " . format ( node [ " node_type " ] ) )
if not keep_compute_id :
node [ " compute_id " ] = " local " # To make project portable all node by default run on local
2016-12-14 19:57:59 +02:00
if " properties " in node and node [ " node_type " ] != " docker " :
2016-07-25 15:47:37 +03:00
for prop , value in node [ " properties " ] . items ( ) :
if prop . endswith ( " image " ) :
if not keep_compute_id : # If we keep the original compute we can keep the image path
node [ " properties " ] [ prop ] = os . path . basename ( value )
if include_images is True :
images . add ( value )
if not keep_compute_id :
topology [ " topology " ] [ " computes " ] = [ ] # Strip compute informations because could contain secret info like password
2016-07-22 14:39:57 +03:00
2016-07-21 19:15:35 +03:00
for image in images :
_export_images ( project , image , z )
2016-07-22 14:39:57 +03:00
2016-07-21 19:15:35 +03:00
z . writestr ( " project.gns3 " , json . dumps ( topology ) . encode ( ) )
def _export_images ( project , image , z ) :
"""
Take a project file ( . gns3 ) and export images to the zip
: param image : Image path
: param z : Zipfile instance for the export
"""
from . . compute import MODULES
for module in MODULES :
try :
img_directory = module . instance ( ) . get_images_directory ( )
except NotImplementedError :
# Some modules don't have images
continue
directory = os . path . split ( img_directory ) [ - 1 : ] [ 0 ]
if os . path . exists ( image ) :
path = image
else :
path = os . path . join ( img_directory , image )
if os . path . exists ( path ) :
arcname = os . path . join ( " images " , directory , os . path . basename ( image ) )
z . write ( path , arcname )
2017-05-11 18:59:57 +03:00
return
raise aiohttp . web . HTTPConflict ( text = " Topology could not be exported because the image {} is not available. If you use multiple server, we need a copy of the image on the main server. " . format ( image ) )