gns3-server 3.0.1__py3-none-any.whl → 3.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of gns3-server might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: gns3-server
3
- Version: 3.0.1
3
+ Version: 3.0.2
4
4
  Summary: GNS3 graphical interface for the GNS3 server.
5
5
  Author-email: Jeremy Grossmann <developers@gns3.com>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -690,35 +690,36 @@ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
690
690
  Classifier: Natural Language :: English
691
691
  Classifier: Operating System :: POSIX :: Linux
692
692
  Classifier: Programming Language :: Python :: 3 :: Only
693
- Classifier: Programming Language :: Python :: 3.8
694
693
  Classifier: Programming Language :: Python :: 3.9
695
694
  Classifier: Programming Language :: Python :: 3.10
696
695
  Classifier: Programming Language :: Python :: 3.11
697
696
  Classifier: Programming Language :: Python :: 3.12
697
+ Classifier: Programming Language :: Python :: 3.13
698
698
  Classifier: Programming Language :: Python :: Implementation :: CPython
699
- Requires-Python: >=3.8
699
+ Requires-Python: >=3.9
700
700
  Description-Content-Type: text/markdown
701
701
  License-File: LICENSE
702
- Requires-Dist: uvicorn==0.32.0
703
- Requires-Dist: pydantic==2.9.2
704
- Requires-Dist: fastapi==0.115.5
705
- Requires-Dist: python-multipart==0.0.16
706
- Requires-Dist: websockets==13.1
702
+ Requires-Dist: uvicorn==0.33.0
703
+ Requires-Dist: pydantic==2.10.4
704
+ Requires-Dist: fastapi==0.115.6
705
+ Requires-Dist: python-multipart==0.0.20
706
+ Requires-Dist: websockets==14.1
707
707
  Requires-Dist: aiohttp<3.11,>=3.10.10
708
- Requires-Dist: async-timeout==4.0.3
708
+ Requires-Dist: async-timeout==5.0.1; python_version < "3.11"
709
709
  Requires-Dist: aiofiles<25.0,>=24.1.0
710
- Requires-Dist: Jinja2<3.2,>=3.1.4
711
- Requires-Dist: sentry-sdk<2.18,>=2.17
712
- Requires-Dist: psutil>=6.1.0
710
+ Requires-Dist: Jinja2<3.2,>=3.1.5
711
+ Requires-Dist: sentry-sdk<2.20,>=2.19.2
712
+ Requires-Dist: psutil>=6.1.1
713
713
  Requires-Dist: distro>=1.9.0
714
714
  Requires-Dist: py-cpuinfo<10.0,>=9.0.0
715
+ Requires-Dist: greenlet==3.1.1
715
716
  Requires-Dist: sqlalchemy==2.0.36
716
717
  Requires-Dist: aiosqlite==0.20.0
717
- Requires-Dist: alembic==1.13.3
718
- Requires-Dist: bcrypt==4.2.0
719
- Requires-Dist: python-jose[cryptography]==3.3.0
718
+ Requires-Dist: alembic==1.14.0
719
+ Requires-Dist: bcrypt==4.2.1
720
+ Requires-Dist: joserfc==1.0.1
720
721
  Requires-Dist: email-validator==2.2.0
721
- Requires-Dist: watchfiles==0.24.0
722
+ Requires-Dist: watchdog==6.0.0
722
723
  Requires-Dist: zstandard==0.23.0
723
724
  Requires-Dist: platformdirs<3,>=2.4.0
724
725
  Requires-Dist: importlib-resources>=1.3; python_version <= "3.9"
@@ -2,11 +2,11 @@ gns3server/__init__.py,sha256=7t93V0ASczMyzTR7up2g0FBQ6Rm1eC8wxyysMKUeAKE,716
2
2
  gns3server/__main__.py,sha256=5CO8WDwpzZ2BRo3Ct95MhMGxyXQ3QsHBcWt3jPlcLb0,736
3
3
  gns3server/alembic.ini,sha256=XWHHdJ3QOHO3NJh5LK6bMHT6kv7GzAr26X1hs43DH4o,3086
4
4
  gns3server/config.py,sha256=3UmjwcthW-gMKbLEPzygzrEVJYza-H1IWRjJZ_L1-tg,9244
5
- gns3server/crash_report.py,sha256=nUDfhsd9WLbYOEXpvgVJR0--mggDB4fpy-U3SDCiRPA,6021
5
+ gns3server/crash_report.py,sha256=WLpcAtoTZ_MFDuPpJo6L6MW0OqGjMTrIilSc7oditOE,6021
6
6
  gns3server/logger.py,sha256=zjHjyyqK74YMVPdjjznj03xWO42Kx3RDeVnZflT-0eI,5309
7
7
  gns3server/main.py,sha256=w16TCAYfTmdKsrCjm7rt4hlwsaMfLiWxw8598K4B3zA,2286
8
- gns3server/server.py,sha256=6wtW4I0nKXkuIbJAn0h_xgpiOrGjQpnLgs1Ngj8p5Dw,13362
9
- gns3server/version.py,sha256=62q_NlnEdUU9OlLnmYb4TXBYGDYIqieOYV-jSQOp8bY,1462
8
+ gns3server/server.py,sha256=ZoqI8I1UI_gQgescnEiT0XFijJVo86vM3aowGLUOXHw,13362
9
+ gns3server/version.py,sha256=QxUxoSMx508tXlMSHuEUDgxjrmPrqRyrX3VxGhKmlbE,1462
10
10
  gns3server/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  gns3server/api/server.py,sha256=LQAKxtBaVxylgVTqRpVhdwACdkaibDryOSUT2us5PUk,7972
12
12
  gns3server/api/routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -40,7 +40,7 @@ gns3server/api/routes/controller/controller.py,sha256=6S8pMz5aj47RSrFeGP8CTuGYs8
40
40
  gns3server/api/routes/controller/drawings.py,sha256=E76kffF_AqXtdY684snU507uAalLNVv-0l2eXe0b5WM,4076
41
41
  gns3server/api/routes/controller/gns3vm.py,sha256=7uwJYx7PSkol6_trM8uN4g_XL7SNds8_r0EMC8UyNHM,2035
42
42
  gns3server/api/routes/controller/groups.py,sha256=Haqy3lzYRS1-FcS15yXEeAPz6k7-sD1_RaPxPVZc85A,6962
43
- gns3server/api/routes/controller/images.py,sha256=YnMf_WL_u5k0SMsOVulTVyJPIov2N6PLgP4hA79EqyM,8993
43
+ gns3server/api/routes/controller/images.py,sha256=MJiMxvmG6P3BNd71tQ5EYbpd7xotm2VJRBYujY1hYV8,10334
44
44
  gns3server/api/routes/controller/links.py,sha256=LgqmdVUUwtpipDhDzRdzUyKpsr1Or-t-E05SXAzGNkA,8464
45
45
  gns3server/api/routes/controller/nodes.py,sha256=WeWmKVDU7aG9GeW4ZbvD79P12Ov0jTIZ5lw4fJjWq8s,18855
46
46
  gns3server/api/routes/controller/pools.py,sha256=FCSqEmRxRvjMKM_iGwERxUrukJEGczV_XRpa8mSZmFw,7806
@@ -49,7 +49,7 @@ gns3server/api/routes/controller/projects.py,sha256=1I5QL3rB35POkfBoObPH0xbUz-Nk
49
49
  gns3server/api/routes/controller/roles.py,sha256=IQ-9BcUDOWqFpxSU7CEZWuEZ6H0VaWv4AfHxT0NZ8ME,6089
50
50
  gns3server/api/routes/controller/snapshots.py,sha256=GMBL2nQJp8VJRpo7LpzHSBhPn1SJbvjs0HugDFqCOYE,3559
51
51
  gns3server/api/routes/controller/symbols.py,sha256=D_fD-j421LJjiO0Rtgo82psxm4WLgN1psVqTdc4psSw,3825
52
- gns3server/api/routes/controller/templates.py,sha256=jivFmT0JsPwRbixv4urF4CEknt5dX_bjezTpgVTIDvc,6280
52
+ gns3server/api/routes/controller/templates.py,sha256=ZeD3eT2x__FvCqRLc85AukOkpHnBCwg1msYL826uqBI,7296
53
53
  gns3server/api/routes/controller/users.py,sha256=cwf11_V1gZ-DThKILGlPZq-g6C4PuAJPU8HyEu5FdVU,8056
54
54
  gns3server/api/routes/controller/dependencies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
55
  gns3server/api/routes/controller/dependencies/authentication.py,sha256=-XeUWr21hYkQovqUhcctinPuLqk-q6803evBSTMHtNM,3979
@@ -284,7 +284,7 @@ gns3server/compute/builtin/nodes/cloud.py,sha256=r3tq0b-7uvM9xHRVtNx65Dv3D69_TDO
284
284
  gns3server/compute/builtin/nodes/ethernet_hub.py,sha256=_aPL_bKpvYY_REHRcUlkSLMobN4ZhhGGCJLgQNHUvtc,2708
285
285
  gns3server/compute/builtin/nodes/ethernet_switch.py,sha256=Xc59N3kM9nuzLjHlgllRI3pzXtDQdc2SsCucdZxMebs,2733
286
286
  gns3server/compute/builtin/nodes/nat.py,sha256=rs1M_VZ42zhtb0RmukD7zHEEpR80WAACZykY9ETN0MQ,3804
287
- gns3server/compute/docker/__init__.py,sha256=Lk5Xc4zND2oD9gp-780oGEyj0JfN9gxGkzJ9feibDqg,12470
287
+ gns3server/compute/docker/__init__.py,sha256=DqR1b_qZQnS9Ej4kN6_w3fmYzNdf3N1Hw_UudcmIwOs,12476
288
288
  gns3server/compute/docker/docker_error.py,sha256=Rj5QDgOFt8ENZy23nDI8J-gmtt8-lDDtlNdmK70d_tg,904
289
289
  gns3server/compute/docker/docker_vm.py,sha256=4IQdr-5rL5ylnhPNPaCzU65EZLRSH1yKTWzwuu3cuco,49601
290
290
  gns3server/compute/docker/resources/init.sh,sha256=7BxZQm5TXOmDk-TVc0zJmXOX5Jx3azOOnpbPJohF_x0,2868
@@ -388,11 +388,11 @@ gns3server/configs/ios_etherswitch_startup-config.txt,sha256=zf1QafnlBe729AsDcqL
388
388
  gns3server/configs/iou_l2_base_startup-config.txt,sha256=LYtQ1pOsmLzEg6DjObmB7PvOhMP93DZkQDFvXh1b2IU,1726
389
389
  gns3server/configs/iou_l3_base_startup-config.txt,sha256=zF3Ovq-jKZY0UpiAe8VqhlTzcA7ms1JHWY67vj5gmJQ,1495
390
390
  gns3server/configs/vpcs_base_config.txt,sha256=HsThZLGuqANwIjYrCUklQlvHQq6owMdzlXDDE2aWrBQ,199
391
- gns3server/controller/__init__.py,sha256=Hw1Af7wEi6obaiaQtp5a3D-DejwX94kcvPCbKEpN5gA,24898
391
+ gns3server/controller/__init__.py,sha256=VRKkV0SqZOs99A4LbZV7Is_705_K62PVj7vcg0rsEes,25423
392
392
  gns3server/controller/appliance.py,sha256=xxHdtgBHFN85hxeg3kS6SLyFYDJp3llTSr0LXupByr0,2128
393
- gns3server/controller/appliance_manager.py,sha256=sJD7A9J1bvaLfSycvxaISCVER8AVUvFcW0EytqHqehQ,20984
393
+ gns3server/controller/appliance_manager.py,sha256=cOCHFdxFhpjnoW1de2LdcE0K_ptVw6DKPxedbXALt3A,20996
394
394
  gns3server/controller/appliance_to_template.py,sha256=nM4PIbPxXqYUq7B1oyLhyGz3ZC2QAdk7qClMVTdlJQo,5224
395
- gns3server/controller/compute.py,sha256=rvhJq5GlmBKD9a4bmaDM3rQHmhC0u-ctRaF7JvXYE1k,25469
395
+ gns3server/controller/compute.py,sha256=OamRdq5cZDXSx5QpDIHeq7RWvTWtXtSvcJUua2S9pzE,25581
396
396
  gns3server/controller/controller_error.py,sha256=pYW8eY3VZeBl6nSUOUQh-47pbeKfRtSwbkLiWpleHoo,2071
397
397
  gns3server/controller/drawing.py,sha256=yMN46Ev0dV-wBA4P4g0xzq-XU1qHVcKGmwriO_0GvsU,7179
398
398
  gns3server/controller/export_project.py,sha256=uN5NeMCy1ajMhrEWOli6lHsJNQCI6eRatMss4_kX8vE,12917
@@ -426,7 +426,7 @@ gns3server/controller/ports/serial_port.py,sha256=FdyfxZN1VGn6_DqEE4p8OBkO-IUaFM
426
426
  gns3server/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
427
427
  gns3server/core/tasks.py,sha256=Zo5pCs4LKBUb1I9mgWgQ_h0Bj-_n33-zeCNFZ5xQQl0,3329
428
428
  gns3server/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
429
- gns3server/db/tasks.py,sha256=MjyVRBvnirDhGSe7iPuei6hy7laojECdPfNyGo9dJBM,9334
429
+ gns3server/db/tasks.py,sha256=aK2USujpgxIP4nlEH8MgGp3CPpZc6B_3TudYJwD-t7Q,10145
430
430
  gns3server/db/models/__init__.py,sha256=X3jpiZd7WqERqYmrtalcAvWatfnSL5NaKrgvZPHRs8k,1192
431
431
  gns3server/db/models/acl.py,sha256=jpcCWTEMAEHDQG5mdHhAYkX80oiPc5pQ9LQ9UoJluFI,1776
432
432
  gns3server/db/models/base.py,sha256=E73cXMoRr85tOaDEPcMCtfBMxdd1H_vIPU0wKiCGC2w,3419
@@ -440,10 +440,10 @@ gns3server/db/models/users.py,sha256=78037IGFag5Fp7NlIWywycYF5otMWGmsMSROrPvmZK0
440
440
  gns3server/db/repositories/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
441
441
  gns3server/db/repositories/base.py,sha256=a6HZJ4mu9GVpdUCZseA65xy8nzrkN_M00WLFKYVmCzM,874
442
442
  gns3server/db/repositories/computes.py,sha256=Km00JeclL6xDZ_863keGTiU38lZ-ufQhKmiU8hvWEj8,3445
443
- gns3server/db/repositories/images.py,sha256=9q4iKsEybNyq2IilfTZ5HgWhJO92LuBEnQC6SueMFo8,4742
443
+ gns3server/db/repositories/images.py,sha256=UNNxXTxo04Ih-hwbJMmSwKom5XC88qcwNL3meamq9wI,5547
444
444
  gns3server/db/repositories/pools.py,sha256=2Iqwg1DPuEr1e47trjBtSHLPwuRP7PtxVpMUA6VyY3M,7569
445
445
  gns3server/db/repositories/rbac.py,sha256=vkWFp2OiehaETpaff_-_BHFQJZxX1wiukqmdj0rAbQk,15052
446
- gns3server/db/repositories/templates.py,sha256=ut-HtgblS_Sr8q1BxP0dGAXhOvpmwxK5B7N2yzj8T-E,6479
446
+ gns3server/db/repositories/templates.py,sha256=b6v7mxPWVhPTrF6J-fzwi86pdMDJIlotAz_gHSGRajI,6875
447
447
  gns3server/db/repositories/users.py,sha256=e1NCZFR8f00MvwovBwXbn0oEo8lAo1okEC01DSuJY2E,9914
448
448
  gns3server/db_migrations/README,sha256=JNt8RkbFUWF8Ba9uw0bCvMvNW7PONXJvDhREDLOy61o,171
449
449
  gns3server/db_migrations/env.py,sha256=mieif6012ww6wn9JPReZy1WvZHVsTyojbesYpqSE8qw,2782
@@ -524,7 +524,7 @@ gns3server/schemas/controller/templates/virtualbox_templates.py,sha256=8DZEUBe2l
524
524
  gns3server/schemas/controller/templates/vmware_templates.py,sha256=_W7ObQArm1aMd6ANGOzhV8A8C4IEuIGvOAvlVu5x3ag,2843
525
525
  gns3server/schemas/controller/templates/vpcs_templates.py,sha256=cAPdGve1OJCPaSdYLZfxFwidkTL4pzaJxfdtVKI8bFE,1400
526
526
  gns3server/services/__init__.py,sha256=Qdb0EuWkPSLx9HeWcV8lJ1qPez288e7vgpCNLNGbvxw,753
527
- gns3server/services/authentication.py,sha256=jOIrJW4VMaAf_0zz-1TCyN35sJ2ekDbjRhKFGj4MuGE,3473
527
+ gns3server/services/authentication.py,sha256=-8nABBBN0co-xDCBjeu9ZMVrlQNq-7QmX4JjAuKHZ_A,3630
528
528
  gns3server/services/computes.py,sha256=ZcHb7HXxdVjtV0N0ggxOkvDL7mFpemisXLwXMrNNmpQ,3411
529
529
  gns3server/services/templates.py,sha256=w_8grcTPVMXFWGqF40aBQ2OeXK5WDd-yQ4OD6q9jKIg,14211
530
530
  gns3server/static/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -560,8 +560,8 @@ gns3server/static/web-ui/NotoSans-Regular.bb384defbe36eaec.svg,sha256=uECuQtpAPz
560
560
  gns3server/static/web-ui/NotoSans-Regular.e6139cb1663a1c57.ttf,sha256=NPyxxROGVf4PlLBl3aiZC8bvHIpeR0bbt37H7hWtNGI,512588
561
561
  gns3server/static/web-ui/ReleaseNotes.txt,sha256=uag7ABM-yXleLBxhtetbpVOCssZPXcd_lLMyUAdQsn4,5527
562
562
  gns3server/static/web-ui/favicon.ico,sha256=ucy7cQDhOulawYo6ntAIV_MhtjtJjx-3q6tQb8HEDpk,5430
563
- gns3server/static/web-ui/index.html,sha256=Y5NmkKO3FodIF5PNfMyqjTzLoULkxbj0C9Xkt76k1zA,11546
564
- gns3server/static/web-ui/main.e55eeff5c0ba1cf4.js,sha256=TVRGMvBf99gE94FKPiDNYJ7acMu7nBFZXUSCAsx510k,4320056
563
+ gns3server/static/web-ui/index.html,sha256=mear17OnyP8egGZXvsUmuuClEa01X_YIFZZbb5OBPDc,11546
564
+ gns3server/static/web-ui/main.62c99707e4709a56.js,sha256=m0OxkO_z5Zosa-n7qxnT3OvXjtgMYCL348nJ4iZrpws,4319606
565
565
  gns3server/static/web-ui/polyfills.319c79dd175e50d0.js,sha256=sQPX0SyLyUHk_0gvODKsdL3-lQsQ_whJeMgxx6tO04A,38362
566
566
  gns3server/static/web-ui/roboto-latin-100.539f0a96b40596f7.woff2,sha256=EoI9WFYFI4EhVUr_i7BgojXcNvN-_Z-x5-bqGpYivDU,15808
567
567
  gns3server/static/web-ui/roboto-latin-100.5ba994dac3e79ea8.woff,sha256=xOrU3p96_yN9BrUw6thBPRNXQn9qkllENCu04rHc5tA,20368
@@ -1225,27 +1225,27 @@ gns3server/utils/file_watcher.py,sha256=CF-5dB-RVSj8xuVxJ9siSpf3QOnXpw3n2IkjCzRg
1225
1225
  gns3server/utils/get_resource.py,sha256=WWW4mejsye6Tpw5UmIT6drfcUDqs-YhLxSlQaNboRDg,1534
1226
1226
  gns3server/utils/hostname.py,sha256=XUswUi47n-LfUbMiZ5AOs_Crl6lln606nhttEaJanpA,3342
1227
1227
  gns3server/utils/http_client.py,sha256=MD-yZA2VjQonqOlPiU4SXo9AjQWDYFFP0ZeZzz680EI,2318
1228
- gns3server/utils/images.py,sha256=Jm-mCUSKwsXP1yWKjSwWOye0_473b0eSbWE0jGHOT8U,14439
1228
+ gns3server/utils/images.py,sha256=v7kPc3iDN6E84naQcS9OUHpZLQ2xfY5syeqY0p_bZ4Y,14840
1229
1229
  gns3server/utils/interfaces.py,sha256=DwsZXwPxmc5E-QwquzMqAfQRLrrt-GyYUjUDxFKqEZo,8981
1230
1230
  gns3server/utils/notification_queue.py,sha256=EO2vHr_Lkxiq9NP-6u6eDsVvFNKbe-QSZqHADU-Ynss,2688
1231
1231
  gns3server/utils/path.py,sha256=m4kj9NoFMLQfMAEG1HJ97x-zAdw0bxq-14tCwaIOSKc,2348
1232
1232
  gns3server/utils/picture.py,sha256=xC6KASep9RHUNzcb6scL5POPNZ04uROXJmTub3INpto,6160
1233
1233
  gns3server/utils/qt.py,sha256=rNABILwh9iqK6XCqBOjjk60ELPPaI7DFQWfKrORk2Kk,1461
1234
1234
  gns3server/utils/vmnet.py,sha256=_7foMqHldwH2kXMtgkpEqOKMJAU7reGI42v1Svnj3gw,9981
1235
- gns3server/utils/asyncio/__init__.py,sha256=59dy1xA7Uvmcmp5CWdiqteP8eRAiXdMdbj3ZwVQ-Vxc,4781
1235
+ gns3server/utils/asyncio/__init__.py,sha256=43PKts4ppmCaFgfepYsI3BfhuqV77gnIudzdWk0umaw,4522
1236
1236
  gns3server/utils/asyncio/aiozipstream.py,sha256=cZF-ASUuqLtXN1vJaDW2PfvSE9O4BjV55yGapelo1Ko,18984
1237
1237
  gns3server/utils/asyncio/embed_shell.py,sha256=J9a3ibTi9AlRJ4YvFAhPmjz68lla0pNgzN31JMMkJXY,11490
1238
1238
  gns3server/utils/asyncio/input_stream.py,sha256=hUnspj9z0X-mvTLi5EMTXpfQLAIIUvEfgjh-7zMug8Y,14560
1239
- gns3server/utils/asyncio/pool.py,sha256=syIRSzff_BE-JsVovFkdem9N0Gb8-5zWpS27DB_Ksnc,2171
1239
+ gns3server/utils/asyncio/pool.py,sha256=EV8ZMPmLe6vIsGjN9QrcxP1XZxjucxlIrWXtJkdd4Iw,2014
1240
1240
  gns3server/utils/asyncio/raw_command_server.py,sha256=Thr023djzxIriw5Au3Y5BMAeQ6R7KNttOy-1Gs_gPvI,4671
1241
1241
  gns3server/utils/asyncio/serial.py,sha256=VNhqaRkN9Mk3Z30CD6wMNfdTNqlY5YUbMyrAwuBsvXM,2333
1242
1242
  gns3server/utils/asyncio/telnet_server.py,sha256=dteUIryUX25iRr5-dMkdBYSTNZQZfLKnw00Y4C87ewc,16752
1243
1243
  gns3server/utils/zipfile_zstd/__init__.py,sha256=QXfnwyxx_CDJdAgOzqqBAD_7WDudc9VN03U0Gfccr5I,163
1244
1244
  gns3server/utils/zipfile_zstd/_patcher.py,sha256=RCMj1y8OrIJheEGnuERDH0l2hvLaFl5wa_IZNiTpBbU,380
1245
1245
  gns3server/utils/zipfile_zstd/_zipfile.py,sha256=KvAeNmWZHu2LHuUOZ8_Tnu_dRi3NOIWM6I3hg_c7NsQ,2189
1246
- gns3_server-3.0.1.dist-info/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
1247
- gns3_server-3.0.1.dist-info/METADATA,sha256=zaBKSX0ONVpKe4Ltl86liwChiiZ6ilUlYVSCcyREMLA,48284
1248
- gns3_server-3.0.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
1249
- gns3_server-3.0.1.dist-info/entry_points.txt,sha256=kgHc6rA0RfDOTAkTDSwgWXKHWG4r5mQ-SsmpCHNgXPI,92
1250
- gns3_server-3.0.1.dist-info/top_level.txt,sha256=GvKJnXIKLBmewtHjQXkvmtz79ZW51IsfCoPMbHoWehQ,11
1251
- gns3_server-3.0.1.dist-info/RECORD,,
1246
+ gns3_server-3.0.2.dist-info/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
1247
+ gns3_server-3.0.2.dist-info/METADATA,sha256=F5fxjfxloNsp52Ga_hxOtfUOYyQkBEV48WNwXadIMPk,48323
1248
+ gns3_server-3.0.2.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
1249
+ gns3_server-3.0.2.dist-info/entry_points.txt,sha256=kgHc6rA0RfDOTAkTDSwgWXKHWG4r5mQ-SsmpCHNgXPI,92
1250
+ gns3_server-3.0.2.dist-info/top_level.txt,sha256=GvKJnXIKLBmewtHjQXkvmtz79ZW51IsfCoPMbHoWehQ,11
1251
+ gns3_server-3.0.2.dist-info/RECORD,,
@@ -27,11 +27,11 @@ from fastapi.encoders import jsonable_encoder
27
27
  from starlette.requests import ClientDisconnect
28
28
  from sqlalchemy.orm.exc import MultipleResultsFound
29
29
  from typing import List, Optional
30
- from gns3server import schemas
31
30
 
31
+ from gns3server import schemas
32
32
  from gns3server.config import Config
33
33
  from gns3server.compute.qemu import Qemu
34
- from gns3server.utils.images import InvalidImageError, write_image, read_image_info, default_images_directory
34
+ from gns3server.utils.images import InvalidImageError, write_image, read_image_info, default_images_directory, get_builtin_disks
35
35
  from gns3server.db.repositories.images import ImagesRepository
36
36
  from gns3server.db.repositories.templates import TemplatesRepository
37
37
  from gns3server.db.repositories.rbac import RbacRepository
@@ -51,7 +51,6 @@ log = logging.getLogger(__name__)
51
51
 
52
52
  router = APIRouter()
53
53
 
54
-
55
54
  @router.post(
56
55
  "/qemu/{image_path:path}",
57
56
  response_model=schemas.Image,
@@ -175,6 +174,61 @@ async def upload_image(
175
174
  return image
176
175
 
177
176
 
177
+ @router.delete(
178
+ "/prune",
179
+ status_code=status.HTTP_204_NO_CONTENT,
180
+ dependencies=[Depends(has_privilege("Image.Allocate"))]
181
+ )
182
+ async def prune_images(
183
+ images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
184
+ ) -> None:
185
+ """
186
+ Prune images not attached to any template.
187
+
188
+ Required privilege: Image.Allocate
189
+ """
190
+
191
+ skip_images = get_builtin_disks()
192
+ await images_repo.prune_images(skip_images)
193
+
194
+
195
+ @router.post(
196
+ "/install",
197
+ status_code=status.HTTP_204_NO_CONTENT,
198
+ dependencies=[Depends(has_privilege("Image.Allocate"))]
199
+ )
200
+ async def install_images(
201
+ images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
202
+ templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository))
203
+ ) -> None:
204
+ """
205
+ Attempt to automatically create templates based on image checksums.
206
+
207
+ Required privilege: Image.Allocate
208
+ """
209
+
210
+ skip_images = get_builtin_disks()
211
+ images = await images_repo.get_images()
212
+ for image in images:
213
+ if skip_images and image.filename in skip_images:
214
+ log.debug(f"Skipping image '{image.path}' for image installation")
215
+ continue
216
+ templates = await images_repo.get_image_templates(image.image_id)
217
+ if templates:
218
+ # the image is already used by a template
219
+ log.warning(f"Image '{image.path}' is used by one or more templates")
220
+ continue
221
+ await Controller.instance().appliance_manager.install_appliances_from_image(
222
+ image.path,
223
+ image.checksum,
224
+ images_repo,
225
+ templates_repo,
226
+ None,
227
+ None,
228
+ os.path.dirname(image.path)
229
+ )
230
+
231
+
178
232
  @router.get(
179
233
  "/{image_path:path}",
180
234
  response_model=schemas.Image,
@@ -218,7 +272,7 @@ async def delete_image(
218
272
  image = await images_repo.get_image(image_path)
219
273
  except MultipleResultsFound:
220
274
  raise ControllerBadRequestError(f"Image '{image_path}' matches multiple images. "
221
- f"Please include the relative path of the image")
275
+ f"Please include the absolute path of the image")
222
276
 
223
277
  if not image:
224
278
  raise ControllerNotFoundError(f"Image '{image_path}' not found")
@@ -236,20 +290,3 @@ async def delete_image(
236
290
  success = await images_repo.delete_image(image_path)
237
291
  if not success:
238
292
  raise ControllerError(f"Image '{image_path}' could not be deleted")
239
-
240
-
241
- @router.post(
242
- "/prune",
243
- status_code=status.HTTP_204_NO_CONTENT,
244
- dependencies=[Depends(has_privilege("Image.Allocate"))]
245
- )
246
- async def prune_images(
247
- images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
248
- ) -> None:
249
- """
250
- Prune images not attached to any template.
251
-
252
- Required privilege: Image.Allocate
253
- """
254
-
255
- await images_repo.prune_images()
@@ -18,6 +18,7 @@
18
18
  API routes for templates.
19
19
  """
20
20
 
21
+ import os
21
22
  import hashlib
22
23
  import json
23
24
 
@@ -34,6 +35,8 @@ from gns3server.db.repositories.templates import TemplatesRepository
34
35
  from gns3server.services.templates import TemplatesService
35
36
  from gns3server.db.repositories.rbac import RbacRepository
36
37
  from gns3server.db.repositories.images import ImagesRepository
38
+ from gns3server.controller.controller_error import ControllerError
39
+ from gns3server.utils.images import get_builtin_disks
37
40
 
38
41
  from .dependencies.authentication import get_current_active_user
39
42
  from .dependencies.rbac import has_privilege
@@ -132,10 +135,28 @@ async def delete_template(
132
135
  Required privilege: Template.Allocate
133
136
  """
134
137
 
138
+ images = await templates_repo.get_template_images(template_id)
135
139
  await TemplatesService(templates_repo).delete_template(template_id)
136
140
  await rbac_repo.delete_all_ace_starting_with_path(f"/templates/{template_id}")
137
- if prune_images:
138
- await images_repo.prune_images()
141
+ if prune_images and images:
142
+ skip_images = get_builtin_disks()
143
+ for image in images:
144
+ if image.filename in skip_images:
145
+ continue
146
+ templates = await images_repo.get_image_templates(image.image_id)
147
+ if templates:
148
+ template_names = ", ".join([template.name for template in templates])
149
+ raise ControllerError(f"Image '{image.path}' is used by one or more templates: {template_names}")
150
+
151
+ try:
152
+ os.remove(image.path)
153
+ except OSError:
154
+ log.warning(f"Could not delete image file {image.path}")
155
+
156
+ print(f"Deleting image '{image.path}'")
157
+ success = await images_repo.delete_image(image.path)
158
+ if not success:
159
+ raise ControllerError(f"Image '{image.path}' could not removed from the database")
139
160
 
140
161
 
141
162
  @router.get(
@@ -115,7 +115,7 @@ class Docker(BaseManager):
115
115
  dst_path = self.resources_path()
116
116
  log.info(f"Installing Docker resources in '{dst_path}'")
117
117
  from gns3server.controller import Controller
118
- Controller.instance().install_resource_files(dst_path, "compute/docker/resources")
118
+ await Controller.instance().install_resource_files(dst_path, "compute/docker/resources")
119
119
  await self.install_busybox(dst_path)
120
120
  except OSError as e:
121
121
  raise DockerError(f"Could not install Docker resources to {dst_path}: {e}")
@@ -28,10 +28,10 @@ try:
28
28
  except ImportError:
29
29
  from importlib import resources as importlib_resources
30
30
 
31
-
32
31
  from ..config import Config
33
32
  from ..utils import parse_version, md5sum
34
33
  from ..utils.images import default_images_directory
34
+ from ..utils.asyncio import wait_run_in_executor
35
35
 
36
36
  from .project import Project
37
37
  from .appliance import Appliance
@@ -43,6 +43,7 @@ from .topology import load_topology
43
43
  from .gns3vm import GNS3VM
44
44
  from .gns3vm.gns3_vm_error import GNS3VMError
45
45
  from .controller_error import ControllerError, ControllerNotFoundError
46
+ from ..db.tasks import update_disk_checksums
46
47
  from ..version import __version__
47
48
 
48
49
  import logging
@@ -72,8 +73,11 @@ class Controller:
72
73
  async def start(self, computes=None):
73
74
 
74
75
  log.info("Controller is starting")
75
- self._install_base_configs()
76
- self._install_builtin_disks()
76
+ await self._install_base_configs()
77
+ installed_disks = await self._install_builtin_disks()
78
+ if installed_disks:
79
+ await update_disk_checksums(installed_disks)
80
+
77
81
  server_config = Config.instance().settings.Server
78
82
  Config.instance().listen_for_config_changes(self._update_config)
79
83
  name = server_config.name
@@ -86,7 +90,7 @@ class Controller:
86
90
  if host == "0.0.0.0":
87
91
  host = "127.0.0.1"
88
92
 
89
- self._load_controller_vars()
93
+ await self._load_controller_vars()
90
94
 
91
95
  if server_config.enable_ssl:
92
96
  self._ssl_context = self._create_ssl_context(server_config)
@@ -190,7 +194,7 @@ class Controller:
190
194
  async def reload(self):
191
195
 
192
196
  log.info("Controller is reloading")
193
- self._load_controller_vars()
197
+ await self._load_controller_vars()
194
198
 
195
199
  # remove all projects deleted from disk.
196
200
  for project in self._projects.copy().values():
@@ -234,7 +238,7 @@ class Controller:
234
238
  except OSError as e:
235
239
  log.error(f"Cannot write controller vars file '{self._vars_file}': {e}")
236
240
 
237
- def _load_controller_vars(self):
241
+ async def _load_controller_vars(self):
238
242
  """
239
243
  Reload the controller vars from disk
240
244
  """
@@ -274,9 +278,9 @@ class Controller:
274
278
  builtin_appliances_path = self._appliance_manager.builtin_appliances_path()
275
279
  if not previous_version or \
276
280
  parse_version(__version__.split("+")[0]) > parse_version(previous_version.split("+")[0]):
277
- self._appliance_manager.install_builtin_appliances()
281
+ await self._appliance_manager.install_builtin_appliances()
278
282
  elif not os.listdir(builtin_appliances_path):
279
- self._appliance_manager.install_builtin_appliances()
283
+ await self._appliance_manager.install_builtin_appliances()
280
284
  else:
281
285
  log.info(f"Built-in appliances are installed in '{builtin_appliances_path}'")
282
286
 
@@ -307,18 +311,21 @@ class Controller:
307
311
 
308
312
 
309
313
  @staticmethod
310
- def install_resource_files(dst_path, resource_name, upgrade_resources=True):
314
+ async def install_resource_files(dst_path, resource_name, upgrade_resources=True):
311
315
  """
312
316
  Install files from resources to user's file system
313
317
  """
314
318
 
315
- def should_copy(src, dst, upgrade_resources):
319
+ installed_resources = []
320
+ async def should_copy(src, dst, upgrade_resources):
316
321
  if not os.path.exists(dst):
317
322
  return True
318
323
  if upgrade_resources is False:
319
324
  return False
320
325
  # copy the resource if it is different
321
- return md5sum(src) != md5sum(dst)
326
+ src_md5 = await wait_run_in_executor(md5sum, src)
327
+ dst_md5 = await wait_run_in_executor(md5sum, dst)
328
+ return src_md5 != dst_md5
322
329
 
323
330
  if hasattr(sys, "frozen") and sys.platform.startswith("win"):
324
331
  resource_path = os.path.normpath(os.path.join(os.path.dirname(sys.executable), resource_name))
@@ -328,14 +335,16 @@ class Controller:
328
335
  else:
329
336
  for entry in importlib_resources.files('gns3server').joinpath(resource_name).iterdir():
330
337
  full_path = os.path.join(dst_path, entry.name)
331
- if entry.is_file() and should_copy(str(entry), full_path, upgrade_resources):
338
+ if entry.is_file() and await should_copy(str(entry), full_path, upgrade_resources):
332
339
  log.debug(f'Installing {resource_name} resource file "{entry.name}" to "{full_path}"')
333
- shutil.copy(str(entry), os.path.join(dst_path, entry.name))
340
+ shutil.copy(str(entry), os.path.join(full_path))
341
+ installed_resources.append(full_path)
334
342
  elif entry.is_dir():
335
343
  os.makedirs(full_path, exist_ok=True)
336
- Controller.install_resource_files(full_path, os.path.join(resource_name, entry.name))
344
+ await Controller.install_resource_files(full_path, os.path.join(resource_name, entry.name))
345
+ return installed_resources
337
346
 
338
- def _install_base_configs(self):
347
+ async def _install_base_configs(self):
339
348
  """
340
349
  At startup we copy base configs to the user location to allow
341
350
  them to customize it
@@ -345,11 +354,11 @@ class Controller:
345
354
  log.info(f"Installing base configs in '{dst_path}'")
346
355
  try:
347
356
  # do not overwrite base configs because they may have been customized by the user
348
- Controller.install_resource_files(dst_path, "configs", upgrade_resources=False)
357
+ await Controller.install_resource_files(dst_path, "configs", upgrade_resources=False)
349
358
  except OSError as e:
350
359
  log.error(f"Could not install base config files to {dst_path}: {e}")
351
360
 
352
- def _install_builtin_disks(self):
361
+ async def _install_builtin_disks(self):
353
362
  """
354
363
  At startup we copy built-in Qemu disks to the user location to allow
355
364
  them to use with appliances
@@ -358,7 +367,7 @@ class Controller:
358
367
  dst_path = self.disks_path()
359
368
  log.info(f"Installing built-in disks in '{dst_path}'")
360
369
  try:
361
- Controller.install_resource_files(dst_path, "disks")
370
+ return await Controller.install_resource_files(dst_path, "disks")
362
371
  except OSError as e:
363
372
  log.error(f"Could not install disk files to {dst_path}: {e}")
364
373
 
@@ -110,7 +110,7 @@ class ApplianceManager:
110
110
  os.makedirs(appliances_dir, exist_ok=True)
111
111
  return appliances_dir
112
112
 
113
- def install_builtin_appliances(self):
113
+ async def install_builtin_appliances(self):
114
114
  """
115
115
  At startup we copy the built-in appliances files.
116
116
  """
@@ -119,7 +119,7 @@ class ApplianceManager:
119
119
  log.info(f"Installing built-in appliances in '{dst_path}'")
120
120
  from . import Controller
121
121
  try:
122
- Controller.instance().install_resource_files(dst_path, "appliances")
122
+ await Controller.instance().install_resource_files(dst_path, "appliances")
123
123
  except OSError as e:
124
124
  log.error(f"Could not install built-in appliance files to {dst_path}: {e}")
125
125
 
@@ -18,14 +18,19 @@
18
18
  import ipaddress
19
19
  import aiohttp
20
20
  import asyncio
21
- import async_timeout
22
21
  import socket
23
22
  import json
24
23
  import sys
25
24
  import io
25
+
26
26
  from fastapi import HTTPException
27
27
  from aiohttp import web
28
28
 
29
+ if sys.version_info >= (3, 11):
30
+ from asyncio import timeout as asynctimeout
31
+ else:
32
+ from async_timeout import timeout as asynctimeout
33
+
29
34
  from ..utils import parse_version
30
35
  from ..utils.asyncio import locking
31
36
  from ..controller.controller_error import (
@@ -503,7 +508,7 @@ class Compute:
503
508
  return self._getUrl(path)
504
509
 
505
510
  async def _run_http_query(self, method, path, data=None, timeout=120, raw=False):
506
- async with async_timeout.timeout(delay=timeout):
511
+ async with asynctimeout(delay=timeout):
507
512
  url = self._getUrl(path)
508
513
  headers = {"content-type": "application/json"}
509
514
  chunked = None
@@ -58,7 +58,7 @@ class CrashReport:
58
58
  Report crash to a third party service
59
59
  """
60
60
 
61
- DSN = "https://847198b87dbd50ef8962901641918a08@o19455.ingest.us.sentry.io/38482"
61
+ DSN = "https://9cf53e6b9adfe49b867f1847b7cc4d72@o19455.ingest.us.sentry.io/38482"
62
62
  _instance = None
63
63
 
64
64
  def __init__(self):
@@ -18,7 +18,7 @@
18
18
  import os
19
19
 
20
20
  from typing import Optional, List
21
- from sqlalchemy import select, delete
21
+ from sqlalchemy import select, delete, update
22
22
  from sqlalchemy.ext.asyncio import AsyncSession
23
23
 
24
24
  from .base import BaseRepository
@@ -103,6 +103,22 @@ class ImagesRepository(BaseRepository):
103
103
  await self._db_session.refresh(db_image)
104
104
  return db_image
105
105
 
106
+ async def update_image(self, image_path: str, checksum: str, checksum_algorithm: str) -> models.Image:
107
+ """
108
+ Update an image.
109
+ """
110
+
111
+ query = update(models.Image).\
112
+ where(models.Image.path == image_path).\
113
+ values(checksum=checksum, checksum_algorithm=checksum_algorithm)
114
+
115
+ await self._db_session.execute(query)
116
+ await self._db_session.commit()
117
+ image_db = await self.get_image_by_checksum(checksum)
118
+ if image_db:
119
+ await self._db_session.refresh(image_db) # force refresh of updated_at value
120
+ return image_db
121
+
106
122
  async def delete_image(self, image_path: str) -> bool:
107
123
  """
108
124
  Delete an image.
@@ -119,7 +135,7 @@ class ImagesRepository(BaseRepository):
119
135
  await self._db_session.commit()
120
136
  return result.rowcount > 0
121
137
 
122
- async def prune_images(self) -> int:
138
+ async def prune_images(self, skip_images: list[str] = None) -> int:
123
139
  """
124
140
  Prune images not attached to any template.
125
141
  """
@@ -130,12 +146,15 @@ class ImagesRepository(BaseRepository):
130
146
  images = result.scalars().all()
131
147
  images_deleted = 0
132
148
  for image in images:
149
+ if skip_images and image.filename in skip_images:
150
+ log.debug(f"Skipping image '{image.path}' for pruning")
151
+ continue
133
152
  try:
134
153
  log.debug(f"Deleting image '{image.path}'")
135
154
  os.remove(image.path)
136
155
  except OSError:
137
156
  log.warning(f"Could not delete image file {image.path}")
138
- if await self.delete_image(image.filename):
157
+ if await self.delete_image(image.path):
139
158
  images_deleted += 1
140
159
  log.info(f"{images_deleted} image(s) have been deleted")
141
160
  return images_deleted
@@ -170,3 +170,14 @@ class TemplatesRepository(BaseRepository):
170
170
  await self._db_session.commit()
171
171
  await self._db_session.refresh(template_in_db)
172
172
  return template_in_db
173
+
174
+ async def get_template_images(self, template_id: UUID) -> List[models.Image]:
175
+ """
176
+ Return all images attached to a template.
177
+ """
178
+
179
+ query = select(models.Image).\
180
+ join(models.Image.templates).\
181
+ filter(models.Template.template_id == template_id)
182
+ result = await self._db_session.execute(query)
183
+ return result.scalars().all()