mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-08-27 04:20:14 +03:00
Merge pull request #2741 from Volo6uev/issue-2735/api-base-config
api endpoint implemented
This commit is contained in:
commit
db605120fa
17
docs/api/examples/controller_get_templateidbaseconfig.txt
Normal file
17
docs/api/examples/controller_get_templateidbaseconfig.txt
Normal file
@ -0,0 +1,17 @@
|
||||
curl -i -X 'http://localhost:3080/v2/templates/19021f99-e36f-394d-b4a1-8aaa902ab9cc/base-config/vpcs_base_config.txt'
|
||||
|
||||
GET /v2/templates/19021f99-e36f-394d-b4a1-8aaa902ab9cc/base-config/vpcs_base_config.txt HTTP/1.1
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Connection: close
|
||||
X-Route: /v2/templates/{template_id}/base-config/{filename}
|
||||
Server: Python/3.14 GNS3/2.2.60.dev1+713dbb7f
|
||||
Content-Type: application/json
|
||||
Content-Length: 144
|
||||
Date: Mon, 18 May 2026 18:34:10 GMT
|
||||
|
||||
{
|
||||
"content": "# test config\n\ndhcp\n",
|
||||
"filename": "vpcs_base_config.txt",
|
||||
"template_id": "19021f99-e36f-394d-b4a1-8aaa902ab9cc"
|
||||
}
|
||||
44
docs/api/examples/controller_get_templatesbaseconfigs.txt
Normal file
44
docs/api/examples/controller_get_templatesbaseconfigs.txt
Normal file
@ -0,0 +1,44 @@
|
||||
curl -i -X 'http://localhost:3080/v2/templates/base-configs'
|
||||
|
||||
GET /v2/templates/base-configs HTTP/1.1
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Connection: close
|
||||
X-Route: /v2/templates/base-configs
|
||||
Server: Python/3.14 GNS3/2.2.60.dev1+713dbb7f
|
||||
Content-Type: application/json
|
||||
Content-Length: 576
|
||||
Date: Mon, 18 May 2026 18:28:53 GMT
|
||||
|
||||
[
|
||||
{
|
||||
"filename": "config1.txt"
|
||||
},
|
||||
{
|
||||
"filename": "config2.txt"
|
||||
},
|
||||
{
|
||||
"filename": "ios_base_startup-config.txt"
|
||||
},
|
||||
{
|
||||
"filename": "ios_etherswitch_startup-config.txt"
|
||||
},
|
||||
{
|
||||
"filename": "iou_l2_base_startup-config.txt"
|
||||
},
|
||||
{
|
||||
"filename": "iou_l3_base_startup-config.txt"
|
||||
},
|
||||
{
|
||||
"filename": "test_config.txt"
|
||||
},
|
||||
{
|
||||
"filename": "update_test.txt"
|
||||
},
|
||||
{
|
||||
"filename": "vpcs_base_config.txt"
|
||||
},
|
||||
{
|
||||
"filename": "vpcs_base_config2.txt"
|
||||
}
|
||||
]
|
||||
17
docs/api/examples/controller_put_templateidbaseconfig.txt
Normal file
17
docs/api/examples/controller_put_templateidbaseconfig.txt
Normal file
@ -0,0 +1,17 @@
|
||||
curl -i -X 'http://localhost:3080/v2/templates/19021f99-e36f-394d-b4a1-8aaa902ab9cc/base-config/vpcs_base_config.txt'
|
||||
|
||||
PUT /v2/templates/19021f99-e36f-394d-b4a1-8aaa902ab9cc/base-config/vpcs_base_config.txt HTTP/1.1
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Connection: close
|
||||
X-Route: /v2/templates/{template_id}/base-config/{filename}
|
||||
Server: Python/3.14 GNS3/2.2.60.dev1+713dbb7f
|
||||
Content-Type: application/json
|
||||
Content-Length: 144
|
||||
Date: Mon, 18 May 2026 18:37:57 GMT
|
||||
|
||||
{
|
||||
"content": "# test config\n\ndhcp\n",
|
||||
"filename": "vpcs_base_config.txt",
|
||||
"template_id": "19021f99-e36f-394d-b4a1-8aaa902ab9cc"
|
||||
}
|
||||
38
docs/api/v2/controller/template/templatesbaseconfigs.rst
Normal file
38
docs/api/v2/controller/template/templatesbaseconfigs.rst
Normal file
@ -0,0 +1,38 @@
|
||||
GET /v2/templates/base-configs
|
||||
------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
.. contents::
|
||||
|
||||
GET /v2/templates/base-configs
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
List all available base configuration files
|
||||
|
||||
Response status codes
|
||||
**********************
|
||||
- **200**: List of base configuration files returned
|
||||
|
||||
Output
|
||||
*******
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>filename</td>
|
||||
<td>string</td>
|
||||
<td>Name of the base configuration file</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
Sample session
|
||||
***************
|
||||
|
||||
.. literalinclude:: ../../../examples/controller_get_templatesbaseconfigs.txt
|
||||
@ -0,0 +1,90 @@
|
||||
/v2/templates/{template_id}/base-config/{filename}
|
||||
------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
.. contents::
|
||||
|
||||
GET /v2/templates/**{template_id}**/base-configs/**{filename}**
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
List all available base configuration files
|
||||
|
||||
Response status codes
|
||||
**********************
|
||||
- **200**: List of base configuration files returned
|
||||
|
||||
Output
|
||||
*******
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>filename</td>
|
||||
<td>string</td>
|
||||
<td>Name of the base configuration file</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
Sample session
|
||||
***************
|
||||
|
||||
.. literalinclude:: ../../../examples/controller_get_templateidbaseconfig.txt
|
||||
|
||||
|
||||
PUT /v2/templates/**{template_id}**/base-config/**{filename}**
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Update base configuration file content
|
||||
|
||||
Response status codes
|
||||
**********************
|
||||
- **200**: File updated
|
||||
- **404**: File not found
|
||||
|
||||
Input
|
||||
*******
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Mandatory</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
|
||||
<tr><td>content</td><td>✔</td><td>string</td><td>New file content</td></tr>
|
||||
|
||||
</table>
|
||||
|
||||
Output
|
||||
*******
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Mandatory</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
|
||||
<tr><td>template_id</td><td>✔</td><td>string</td><td>Template UUID</td></tr>
|
||||
<tr><td>filename</td><td>✔</td><td>string</td><td>Base configuration filename</td></tr>
|
||||
<tr><td>content</td><td>✔</td><td>string</td><td>Updated file content</td></tr>
|
||||
|
||||
</table>
|
||||
|
||||
Sample session
|
||||
***************
|
||||
|
||||
.. literalinclude:: ../../../examples/controller_put_templateidbaseconfig.txt
|
||||
@ -22,6 +22,7 @@ from gns3server.schemas.template import TEMPLATE_USAGE_SCHEMA
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
|
||||
from gns3server.schemas.template import (
|
||||
TEMPLATE_OBJECT_SCHEMA,
|
||||
@ -173,3 +174,131 @@ class TemplateHandler:
|
||||
compute_id=request.json.get("compute_id"))
|
||||
response.set_status(201)
|
||||
response.json(node)
|
||||
|
||||
@Route.get(
|
||||
r"/templates/{template_id}/base-config/{filename}",
|
||||
description="Get base configuration file content",
|
||||
)
|
||||
def get_base_config(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
|
||||
template_id = request.match_info["template_id"]
|
||||
|
||||
template = controller.template_manager.get_template(template_id)
|
||||
|
||||
filename = os.path.basename(request.match_info["filename"])
|
||||
|
||||
path = os.path.join(controller.configs_path(), filename)
|
||||
|
||||
try:
|
||||
if not os.path.exists(path):
|
||||
response.set_status(404)
|
||||
response.json({"message": "File not found"})
|
||||
return
|
||||
|
||||
with open(path, encoding="utf-8", errors="ignore") as f:
|
||||
content = f.read()
|
||||
|
||||
except Exception as e:
|
||||
response.set_status(500)
|
||||
response.json({"message": str(e)})
|
||||
return
|
||||
|
||||
response.set_status(200)
|
||||
response.json({
|
||||
"template_id": template.id,
|
||||
"filename": filename,
|
||||
"content": content
|
||||
})
|
||||
|
||||
@Route.put(
|
||||
r"/templates/{template_id}/base-config/{filename}",
|
||||
description="Update base configuration file content",
|
||||
parameters={
|
||||
"template_id": "Template UUID",
|
||||
"filename": "Base config filename"
|
||||
},
|
||||
status_codes={
|
||||
200: "File updated",
|
||||
400: "Invalid request",
|
||||
404: "Template or file not found"
|
||||
},
|
||||
)
|
||||
def update_base_config(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
|
||||
template_id = request.match_info["template_id"]
|
||||
filename = os.path.basename(request.match_info["filename"])
|
||||
|
||||
try:
|
||||
template = controller.template_manager.get_template(template_id)
|
||||
except Exception:
|
||||
response.set_status(404)
|
||||
response.json({"message": "Template not found"})
|
||||
return
|
||||
|
||||
path = os.path.join(controller.configs_path(), filename)
|
||||
|
||||
body = request.json
|
||||
|
||||
if not body or "content" not in body:
|
||||
response.set_status(400)
|
||||
response.json({"message": "Missing 'content' field"})
|
||||
return
|
||||
|
||||
content = body["content"]
|
||||
|
||||
try:
|
||||
os.makedirs(controller.configs_path(), exist_ok=True)
|
||||
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
|
||||
except OSError as e:
|
||||
response.set_status(500)
|
||||
response.json({"message": str(e)})
|
||||
return
|
||||
|
||||
response.set_status(200)
|
||||
response.json({
|
||||
"template_id": template.id,
|
||||
"filename": filename,
|
||||
"content": content
|
||||
})
|
||||
|
||||
@Route.get(
|
||||
r"/templates/base-configs",
|
||||
description="List all available base configuration files",
|
||||
status_codes={
|
||||
200: "List of base configuration files returned"
|
||||
}
|
||||
)
|
||||
def list_base_configs(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
|
||||
configs_path = controller.configs_path()
|
||||
|
||||
try:
|
||||
files = []
|
||||
|
||||
if os.path.exists(configs_path):
|
||||
for filename in os.listdir(configs_path):
|
||||
|
||||
path = os.path.join(configs_path, filename)
|
||||
|
||||
if os.path.isfile(path):
|
||||
files.append({"filename": filename})
|
||||
|
||||
except OSError as e:
|
||||
response.set_status(500)
|
||||
response.json({
|
||||
"message": str(e)
|
||||
})
|
||||
return
|
||||
|
||||
files = sorted(files, key=lambda x: x["filename"])
|
||||
response.set_status(200)
|
||||
response.json(files)
|
||||
|
||||
@ -14,8 +14,7 @@
|
||||
#
|
||||
# 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 uuid
|
||||
|
||||
from tests.utils import asyncio_patch
|
||||
@ -955,3 +954,81 @@ async def test_create_node_from_template(controller_api, controller, project):
|
||||
mock.assert_called_with(id, x=42, y=12, name=None, compute_id=None)
|
||||
assert response.route == "/projects/{project_id}/templates/{template_id}"
|
||||
assert response.status == 201
|
||||
|
||||
|
||||
async def test_get_base_config(controller_api, controller):
|
||||
|
||||
template_id = str(uuid.uuid4())
|
||||
|
||||
controller.template_manager._templates[template_id] = Template(template_id, {
|
||||
"template_type": "vpcs",
|
||||
"category": 0,
|
||||
"name": "test",
|
||||
"symbol": "guest.svg",
|
||||
"default_name_format": "{name}-{0}",
|
||||
"compute_id": "local"
|
||||
})
|
||||
|
||||
config_path = os.path.join(controller.configs_path(), "test_config.txt")
|
||||
|
||||
with open(config_path, "w") as f:
|
||||
f.write("hello config")
|
||||
|
||||
response = await controller_api.get(
|
||||
f"/templates/{template_id}/base-config/test_config.txt"
|
||||
)
|
||||
|
||||
assert response.status == 200
|
||||
assert response.json["filename"] == "test_config.txt"
|
||||
assert response.json["content"] == "hello config"
|
||||
|
||||
async def test_update_base_config(controller_api, controller):
|
||||
|
||||
template_id = str(uuid.uuid4())
|
||||
|
||||
controller.template_manager._templates[template_id] = Template(template_id, {
|
||||
"template_type": "vpcs",
|
||||
"category": 0,
|
||||
"name": "test",
|
||||
"symbol": "guest.svg",
|
||||
"default_name_format": "{name}-{0}",
|
||||
"compute_id": "local"
|
||||
})
|
||||
|
||||
config_path = os.path.join(controller.configs_path(), "update_test.txt")
|
||||
|
||||
with open(config_path, "w") as f:
|
||||
f.write("old content")
|
||||
|
||||
response = await controller_api.put(
|
||||
f"/templates/{template_id}/base-config/update_test.txt",
|
||||
{
|
||||
"content": "new updated content"
|
||||
}
|
||||
)
|
||||
|
||||
assert response.status == 200
|
||||
assert response.json["content"] == "new updated content"
|
||||
|
||||
with open(config_path) as f:
|
||||
assert f.read() == "new updated content"
|
||||
|
||||
async def test_list_base_configs(controller_api, controller):
|
||||
|
||||
config1 = os.path.join(controller.configs_path(), "config1.txt")
|
||||
config2 = os.path.join(controller.configs_path(), "config2.txt")
|
||||
|
||||
with open(config1, "w") as f:
|
||||
f.write("test1")
|
||||
|
||||
with open(config2, "w") as f:
|
||||
f.write("test2")
|
||||
|
||||
response = await controller_api.get("/templates/base-configs")
|
||||
|
||||
assert response.status == 200
|
||||
|
||||
filenames = [item["filename"] for item in response.json]
|
||||
|
||||
assert "config1.txt" in filenames
|
||||
assert "config2.txt" in filenames
|
||||
Loading…
x
Reference in New Issue
Block a user