mirror of
https://github.com/GNS3/gns3-server
synced 2024-11-24 17:28:08 +00:00
Install Docker resources in writable location
This commit is contained in:
parent
303cbf3642
commit
09ff807055
@ -25,7 +25,7 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import platformdirs
|
||||||
|
|
||||||
from gns3server.utils import parse_version
|
from gns3server.utils import parse_version
|
||||||
from gns3server.utils.asyncio import locking
|
from gns3server.utils.asyncio import locking
|
||||||
@ -59,11 +59,9 @@ class Docker(BaseManager):
|
|||||||
self._api_version = DOCKER_MINIMUM_API_VERSION
|
self._api_version = DOCKER_MINIMUM_API_VERSION
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def install_busybox():
|
async def install_busybox(dst_dir):
|
||||||
|
|
||||||
if not sys.platform.startswith("linux"):
|
dst_busybox = os.path.join(dst_dir, "bin", "busybox")
|
||||||
return
|
|
||||||
dst_busybox = os.path.join(os.path.dirname(os.path.abspath(__file__)), "resources", "bin", "busybox")
|
|
||||||
if os.path.isfile(dst_busybox):
|
if os.path.isfile(dst_busybox):
|
||||||
return
|
return
|
||||||
for busybox_exec in ("busybox-static", "busybox.static", "busybox"):
|
for busybox_exec in ("busybox-static", "busybox.static", "busybox"):
|
||||||
@ -91,6 +89,31 @@ class Docker(BaseManager):
|
|||||||
raise DockerError(f"Could not install busybox: {e}")
|
raise DockerError(f"Could not install busybox: {e}")
|
||||||
raise DockerError("No busybox executable could be found")
|
raise DockerError("No busybox executable could be found")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resources_path():
|
||||||
|
"""
|
||||||
|
Get the Docker resources storage directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
appname = vendor = "GNS3"
|
||||||
|
docker_resources_dir = os.path.join(platformdirs.user_data_dir(appname, vendor, roaming=True), "docker", "resources")
|
||||||
|
os.makedirs(docker_resources_dir, exist_ok=True)
|
||||||
|
return docker_resources_dir
|
||||||
|
|
||||||
|
async def install_resources(self):
|
||||||
|
"""
|
||||||
|
Copy the necessary resources to a writable location and install busybox
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
dst_path = self.resources_path()
|
||||||
|
log.info(f"Installing Docker resources in '{dst_path}'")
|
||||||
|
from gns3server.controller import Controller
|
||||||
|
Controller.instance().install_resource_files(dst_path, "compute/docker/resources")
|
||||||
|
await self.install_busybox(dst_path)
|
||||||
|
except OSError as e:
|
||||||
|
raise DockerError(f"Could not install Docker resources to {dst_path}: {e}")
|
||||||
|
|
||||||
async def _check_connection(self):
|
async def _check_connection(self):
|
||||||
|
|
||||||
if not self._connected:
|
if not self._connected:
|
||||||
|
@ -299,12 +299,15 @@ class DockerVM(BaseNode):
|
|||||||
:returns: Return the path that we need to map to local folders
|
:returns: Return the path that we need to map to local folders
|
||||||
"""
|
"""
|
||||||
|
|
||||||
resources = get_resource("compute/docker/resources")
|
try:
|
||||||
if not os.path.exists(resources):
|
resources_path = self.manager.resources_path()
|
||||||
raise DockerError(f"{resources} is missing, can't start Docker container")
|
except OSError as e:
|
||||||
|
raise DockerError(f"Cannot access resources: {e}")
|
||||||
|
|
||||||
|
log.info(f'Mount resources from "{resources_path}"')
|
||||||
binds = [{
|
binds = [{
|
||||||
"Type": "bind",
|
"Type": "bind",
|
||||||
"Source": resources,
|
"Source": resources_path,
|
||||||
"Target": "/gns3",
|
"Target": "/gns3",
|
||||||
"ReadOnly": True
|
"ReadOnly": True
|
||||||
}]
|
}]
|
||||||
@ -394,6 +397,8 @@ class DockerVM(BaseNode):
|
|||||||
if ":" in os.path.splitdrive(self.working_dir)[1]:
|
if ":" in os.path.splitdrive(self.working_dir)[1]:
|
||||||
raise DockerError("Cannot create a Docker container with a project directory containing a colon character (':')")
|
raise DockerError("Cannot create a Docker container with a project directory containing a colon character (':')")
|
||||||
|
|
||||||
|
#await self.manager.install_resources()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
image_infos = await self._get_image_information()
|
image_infos = await self._get_image_information()
|
||||||
except DockerHttp404Error:
|
except DockerHttp404Error:
|
||||||
@ -545,8 +550,7 @@ class DockerVM(BaseNode):
|
|||||||
Starts this Docker container.
|
Starts this Docker container.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# make sure busybox is installed
|
await self.manager.install_resources()
|
||||||
await self.manager.install_busybox()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
state = await self._get_container_state()
|
state = await self._get_container_state()
|
||||||
|
@ -317,9 +317,12 @@ class Controller:
|
|||||||
else:
|
else:
|
||||||
for entry in importlib_resources.files('gns3server').joinpath(resource_name).iterdir():
|
for entry in importlib_resources.files('gns3server').joinpath(resource_name).iterdir():
|
||||||
full_path = os.path.join(dst_path, entry.name)
|
full_path = os.path.join(dst_path, entry.name)
|
||||||
if entry.is_file() and not os.path.exists(full_path):
|
if not os.path.exists(full_path):
|
||||||
log.debug(f'Installing {resource_name} resource file "{entry.name}" to "{full_path}"')
|
if entry.is_file():
|
||||||
shutil.copy(str(entry), os.path.join(dst_path, entry.name))
|
log.debug(f'Installing {resource_name} resource file "{entry.name}" to "{full_path}"')
|
||||||
|
shutil.copy(str(entry), os.path.join(dst_path, entry.name))
|
||||||
|
elif entry.is_dir():
|
||||||
|
os.makedirs(full_path, exist_ok=True)
|
||||||
|
|
||||||
def _install_base_configs(self):
|
def _install_base_configs(self):
|
||||||
"""
|
"""
|
||||||
|
@ -223,7 +223,8 @@ async def test_install_busybox():
|
|||||||
with patch("gns3server.compute.docker.shutil.which", return_value="/usr/bin/busybox"):
|
with patch("gns3server.compute.docker.shutil.which", return_value="/usr/bin/busybox"):
|
||||||
with asyncio_patch("gns3server.compute.docker.asyncio.create_subprocess_exec", return_value=mock_process) as create_subprocess_mock:
|
with asyncio_patch("gns3server.compute.docker.asyncio.create_subprocess_exec", return_value=mock_process) as create_subprocess_mock:
|
||||||
with patch("gns3server.compute.docker.shutil.copy2") as copy2_mock:
|
with patch("gns3server.compute.docker.shutil.copy2") as copy2_mock:
|
||||||
await Docker.install_busybox()
|
dst_dir = Docker.resources_path()
|
||||||
|
await Docker.install_busybox(dst_dir)
|
||||||
create_subprocess_mock.assert_called_with(
|
create_subprocess_mock.assert_called_with(
|
||||||
"ldd",
|
"ldd",
|
||||||
"/usr/bin/busybox",
|
"/usr/bin/busybox",
|
||||||
@ -244,7 +245,8 @@ async def test_install_busybox_dynamic_linked():
|
|||||||
with patch("gns3server.compute.docker.shutil.which", return_value="/usr/bin/busybox"):
|
with patch("gns3server.compute.docker.shutil.which", return_value="/usr/bin/busybox"):
|
||||||
with asyncio_patch("gns3server.compute.docker.asyncio.create_subprocess_exec", return_value=mock_process):
|
with asyncio_patch("gns3server.compute.docker.asyncio.create_subprocess_exec", return_value=mock_process):
|
||||||
with pytest.raises(DockerError) as e:
|
with pytest.raises(DockerError) as e:
|
||||||
await Docker.install_busybox()
|
dst_dir = Docker.resources_path()
|
||||||
|
await Docker.install_busybox(dst_dir)
|
||||||
assert str(e.value) == "No busybox executable could be found"
|
assert str(e.value) == "No busybox executable could be found"
|
||||||
|
|
||||||
|
|
||||||
@ -254,5 +256,6 @@ async def test_install_busybox_no_executables():
|
|||||||
with patch("gns3server.compute.docker.os.path.isfile", return_value=False):
|
with patch("gns3server.compute.docker.os.path.isfile", return_value=False):
|
||||||
with patch("gns3server.compute.docker.shutil.which", return_value=None):
|
with patch("gns3server.compute.docker.shutil.which", return_value=None):
|
||||||
with pytest.raises(DockerError) as e:
|
with pytest.raises(DockerError) as e:
|
||||||
await Docker.install_busybox()
|
dst_dir = Docker.resources_path()
|
||||||
|
await Docker.install_busybox(dst_dir)
|
||||||
assert str(e.value) == "No busybox executable could be found"
|
assert str(e.value) == "No busybox executable could be found"
|
||||||
|
@ -29,7 +29,6 @@ from gns3server.compute.ubridge.ubridge_error import UbridgeNamespaceError
|
|||||||
from gns3server.compute.docker.docker_vm import DockerVM
|
from gns3server.compute.docker.docker_vm import DockerVM
|
||||||
from gns3server.compute.docker.docker_error import DockerError, DockerHttp404Error
|
from gns3server.compute.docker.docker_error import DockerError, DockerHttp404Error
|
||||||
from gns3server.compute.docker import Docker
|
from gns3server.compute.docker import Docker
|
||||||
from gns3server.utils.get_resource import get_resource
|
|
||||||
|
|
||||||
|
|
||||||
from unittest.mock import patch, MagicMock, call
|
from unittest.mock import patch, MagicMock, call
|
||||||
@ -108,7 +107,7 @@ async def test_create(compute_project, manager):
|
|||||||
"Mounts": [
|
"Mounts": [
|
||||||
{
|
{
|
||||||
"Type": "bind",
|
"Type": "bind",
|
||||||
"Source": get_resource("compute/docker/resources"),
|
"Source": Docker.resources_path(),
|
||||||
"Target": "/gns3",
|
"Target": "/gns3",
|
||||||
"ReadOnly": True
|
"ReadOnly": True
|
||||||
},
|
},
|
||||||
@ -158,7 +157,7 @@ async def test_create_with_tag(compute_project, manager):
|
|||||||
"Mounts": [
|
"Mounts": [
|
||||||
{
|
{
|
||||||
"Type": "bind",
|
"Type": "bind",
|
||||||
"Source": get_resource("compute/docker/resources"),
|
"Source": Docker.resources_path(),
|
||||||
"Target": "/gns3",
|
"Target": "/gns3",
|
||||||
"ReadOnly": True
|
"ReadOnly": True
|
||||||
},
|
},
|
||||||
@ -211,7 +210,7 @@ async def test_create_vnc(compute_project, manager):
|
|||||||
"Mounts": [
|
"Mounts": [
|
||||||
{
|
{
|
||||||
"Type": "bind",
|
"Type": "bind",
|
||||||
"Source": get_resource("compute/docker/resources"),
|
"Source": Docker.resources_path(),
|
||||||
"Target": "/gns3",
|
"Target": "/gns3",
|
||||||
"ReadOnly": True
|
"ReadOnly": True
|
||||||
},
|
},
|
||||||
@ -362,7 +361,7 @@ async def test_create_start_cmd(compute_project, manager):
|
|||||||
"Mounts": [
|
"Mounts": [
|
||||||
{
|
{
|
||||||
"Type": "bind",
|
"Type": "bind",
|
||||||
"Source": get_resource("compute/docker/resources"),
|
"Source": Docker.resources_path(),
|
||||||
"Target": "/gns3",
|
"Target": "/gns3",
|
||||||
"ReadOnly": True
|
"ReadOnly": True
|
||||||
},
|
},
|
||||||
@ -474,7 +473,7 @@ async def test_create_image_not_available(compute_project, manager):
|
|||||||
"Mounts": [
|
"Mounts": [
|
||||||
{
|
{
|
||||||
"Type": "bind",
|
"Type": "bind",
|
||||||
"Source": get_resource("compute/docker/resources"),
|
"Source": Docker.resources_path(),
|
||||||
"Target": "/gns3",
|
"Target": "/gns3",
|
||||||
"ReadOnly": True
|
"ReadOnly": True
|
||||||
},
|
},
|
||||||
@ -529,7 +528,7 @@ async def test_create_with_user(compute_project, manager):
|
|||||||
"Mounts": [
|
"Mounts": [
|
||||||
{
|
{
|
||||||
"Type": "bind",
|
"Type": "bind",
|
||||||
"Source": get_resource("compute/docker/resources"),
|
"Source": Docker.resources_path(),
|
||||||
"Target": "/gns3",
|
"Target": "/gns3",
|
||||||
"ReadOnly": True
|
"ReadOnly": True
|
||||||
},
|
},
|
||||||
@ -627,7 +626,7 @@ async def test_create_with_extra_volumes_duplicate_1_image(compute_project, mana
|
|||||||
"Mounts": [
|
"Mounts": [
|
||||||
{
|
{
|
||||||
"Type": "bind",
|
"Type": "bind",
|
||||||
"Source": get_resource("compute/docker/resources"),
|
"Source": Docker.resources_path(),
|
||||||
"Target": "/gns3",
|
"Target": "/gns3",
|
||||||
"ReadOnly": True
|
"ReadOnly": True
|
||||||
},
|
},
|
||||||
@ -682,7 +681,7 @@ async def test_create_with_extra_volumes_duplicate_2_user(compute_project, manag
|
|||||||
"Mounts": [
|
"Mounts": [
|
||||||
{
|
{
|
||||||
"Type": "bind",
|
"Type": "bind",
|
||||||
"Source": get_resource("compute/docker/resources"),
|
"Source": Docker.resources_path(),
|
||||||
"Target": "/gns3",
|
"Target": "/gns3",
|
||||||
"ReadOnly": True
|
"ReadOnly": True
|
||||||
},
|
},
|
||||||
@ -737,7 +736,7 @@ async def test_create_with_extra_volumes_duplicate_3_subdir(compute_project, man
|
|||||||
"Mounts": [
|
"Mounts": [
|
||||||
{
|
{
|
||||||
"Type": "bind",
|
"Type": "bind",
|
||||||
"Source": get_resource("compute/docker/resources"),
|
"Source": Docker.resources_path(),
|
||||||
"Target": "/gns3",
|
"Target": "/gns3",
|
||||||
"ReadOnly": True
|
"ReadOnly": True
|
||||||
},
|
},
|
||||||
@ -792,7 +791,7 @@ async def test_create_with_extra_volumes_duplicate_4_backslash(compute_project,
|
|||||||
"Mounts": [
|
"Mounts": [
|
||||||
{
|
{
|
||||||
"Type": "bind",
|
"Type": "bind",
|
||||||
"Source": get_resource("compute/docker/resources"),
|
"Source": Docker.resources_path(),
|
||||||
"Target": "/gns3",
|
"Target": "/gns3",
|
||||||
"ReadOnly": True
|
"ReadOnly": True
|
||||||
},
|
},
|
||||||
@ -847,7 +846,7 @@ async def test_create_with_extra_volumes_duplicate_5_subdir_issue_1595(compute_p
|
|||||||
"Mounts": [
|
"Mounts": [
|
||||||
{
|
{
|
||||||
"Type": "bind",
|
"Type": "bind",
|
||||||
"Source": get_resource("compute/docker/resources"),
|
"Source": Docker.resources_path(),
|
||||||
"Target": "/gns3",
|
"Target": "/gns3",
|
||||||
"ReadOnly": True
|
"ReadOnly": True
|
||||||
},
|
},
|
||||||
@ -897,7 +896,7 @@ async def test_create_with_extra_volumes_duplicate_6_subdir_issue_1595(compute_p
|
|||||||
"Mounts": [
|
"Mounts": [
|
||||||
{
|
{
|
||||||
"Type": "bind",
|
"Type": "bind",
|
||||||
"Source": get_resource("compute/docker/resources"),
|
"Source": Docker.resources_path(),
|
||||||
"Target": "/gns3",
|
"Target": "/gns3",
|
||||||
"ReadOnly": True
|
"ReadOnly": True
|
||||||
},
|
},
|
||||||
@ -953,7 +952,7 @@ async def test_create_with_extra_volumes(compute_project, manager):
|
|||||||
"Mounts": [
|
"Mounts": [
|
||||||
{
|
{
|
||||||
"Type": "bind",
|
"Type": "bind",
|
||||||
"Source": get_resource("compute/docker/resources"),
|
"Source": Docker.resources_path(),
|
||||||
"Target": "/gns3",
|
"Target": "/gns3",
|
||||||
"ReadOnly": True
|
"ReadOnly": True
|
||||||
},
|
},
|
||||||
@ -1213,7 +1212,7 @@ async def test_update(vm):
|
|||||||
"Mounts": [
|
"Mounts": [
|
||||||
{
|
{
|
||||||
"Type": "bind",
|
"Type": "bind",
|
||||||
"Source": get_resource("compute/docker/resources"),
|
"Source": Docker.resources_path(),
|
||||||
"Target": "/gns3",
|
"Target": "/gns3",
|
||||||
"ReadOnly": True
|
"ReadOnly": True
|
||||||
},
|
},
|
||||||
@ -1294,7 +1293,7 @@ async def test_update_running(vm):
|
|||||||
"Mounts": [
|
"Mounts": [
|
||||||
{
|
{
|
||||||
"Type": "bind",
|
"Type": "bind",
|
||||||
"Source": get_resource("compute/docker/resources"),
|
"Source": Docker.resources_path(),
|
||||||
"Target": "/gns3",
|
"Target": "/gns3",
|
||||||
"ReadOnly": True
|
"ReadOnly": True
|
||||||
},
|
},
|
||||||
@ -1583,7 +1582,7 @@ async def test_mount_binds(vm):
|
|||||||
assert vm._mount_binds(image_infos) == [
|
assert vm._mount_binds(image_infos) == [
|
||||||
{
|
{
|
||||||
"Type": "bind",
|
"Type": "bind",
|
||||||
"Source": get_resource("compute/docker/resources"),
|
"Source": Docker.resources_path(),
|
||||||
"Target": "/gns3",
|
"Target": "/gns3",
|
||||||
"ReadOnly": True
|
"ReadOnly": True
|
||||||
},
|
},
|
||||||
@ -1727,7 +1726,7 @@ async def test_cpus(compute_project, manager):
|
|||||||
"Mounts": [
|
"Mounts": [
|
||||||
{
|
{
|
||||||
"Type": "bind",
|
"Type": "bind",
|
||||||
"Source": get_resource("compute/docker/resources"),
|
"Source": Docker.resources_path(),
|
||||||
"Target": "/gns3",
|
"Target": "/gns3",
|
||||||
"ReadOnly": True
|
"ReadOnly": True
|
||||||
},
|
},
|
||||||
@ -1777,7 +1776,7 @@ async def test_memory(compute_project, manager):
|
|||||||
"Mounts": [
|
"Mounts": [
|
||||||
{
|
{
|
||||||
"Type": "bind",
|
"Type": "bind",
|
||||||
"Source": get_resource("compute/docker/resources"),
|
"Source": Docker.resources_path(),
|
||||||
"Target": "/gns3",
|
"Target": "/gns3",
|
||||||
"ReadOnly": True
|
"ReadOnly": True
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user