diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..21e1bf803 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,40 @@ +name: Bug report +description: Report a bug so we can fix it. +title: "[Bug]: " +labels: ["bug"] +body: + - type: textarea + id: what-happened + attributes: + label: What happened? + description: A clear description of the bug. + validations: + required: true + - type: textarea + id: reproduce + attributes: + label: Steps to reproduce + description: How can we reproduce this? Numbered steps if possible. + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected behavior + description: What did you expect to happen instead? + - type: input + id: version + attributes: + label: Version / commit + description: Which version or commit hash are you on? + - type: textarea + id: environment + attributes: + label: Environment + description: OS, runtime version, anything else that might be relevant. + - type: textarea + id: logs + attributes: + label: Relevant logs + description: Paste any relevant log output. This is automatically rendered as code. + render: shell diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 834bad959..3978699ee 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -28,6 +28,13 @@ jobs: echo "stable=false" >> $GITHUB_OUTPUT fi + - name: Set lowercase image name vars + if: steps.ver.outputs.stable == 'true' + id: names + run: | + echo "owner=$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" + echo "repo=$(echo '${{ github.event.repository.name }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" + - name: Set up Docker Buildx if: steps.ver.outputs.stable == 'true' uses: docker/setup-buildx-action@v3 @@ -57,5 +64,5 @@ jobs: tags: | ${{ env.DOCKERHUB_ORG }}/${{ github.event.repository.name }}:${{ steps.ver.outputs.tag }} ${{ env.DOCKERHUB_ORG }}/${{ github.event.repository.name }}:latest - ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}:${{ steps.ver.outputs.tag }} - ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}:latest + ghcr.io/${{ steps.names.outputs.owner }}/${{ steps.names.outputs.repo }}:${{ steps.ver.outputs.tag }} + ghcr.io/${{ steps.names.outputs.owner }}/${{ steps.names.outputs.repo }}:latest diff --git a/gns3server/compute/docker/docker_vm.py b/gns3server/compute/docker/docker_vm.py index a5822d998..f71ec3776 100644 --- a/gns3server/compute/docker/docker_vm.py +++ b/gns3server/compute/docker/docker_vm.py @@ -869,7 +869,7 @@ class DockerVM(BaseNode): await self._fix_permissions() state = await self._get_container_state() - if state != "stopped" or state != "exited": + if state != "stopped" and state != "exited": # t=5 number of seconds to wait before killing the container try: await self.manager.query("POST", "containers/{}/stop".format(self._cid), params={"t": 5}) diff --git a/gns3server/controller/project.py b/gns3server/controller/project.py index f26b1ff5f..e35e89038 100644 --- a/gns3server/controller/project.py +++ b/gns3server/controller/project.py @@ -917,7 +917,7 @@ class Project: if self._status != "opened": try: - await self.open() + await self.open(auto_start=False) except aiohttp.web.HTTPConflict as e: # ignore missing images or other conflicts when deleting a project log.warning(f"Conflict while deleting project: {e}") @@ -998,9 +998,12 @@ class Project: return os.path.join(self.path, self._filename) @locking - async def open(self): + async def open(self, auto_start=True): """ Load topology elements + + :param auto_start: whether the nodes may be started when the project + has auto start enabled """ if self._closing: @@ -1114,7 +1117,7 @@ class Project: self._loading = False self.emit_controller_notification("project.opened", self.__json__()) # Should we start the nodes when project is open - if self._auto_start: + if self._auto_start and auto_start: # Start all in the background without waiting for completion # we ignore errors because we want to let the user open # their project and fix it diff --git a/tests/compute/docker/test_docker_vm.py b/tests/compute/docker/test_docker_vm.py index 332a52c17..f43fb7460 100644 --- a/tests/compute/docker/test_docker_vm.py +++ b/tests/compute/docker/test_docker_vm.py @@ -1503,3 +1503,19 @@ async def test_read_console_output_with_binary_mode(vm): with asyncio_patch('gns3server.compute.docker.docker_vm.DockerVM.stop'): await vm._read_console_output(input_stream, output_stream) output_stream.feed_data.assert_called_once_with(b"test") + + +async def test_stop_exited_container_no_stop_query(vm): + + vm._ubridge_hypervisor = None + vm._fix_permissions = MagicMock() + + with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="exited"): + with asyncio_patch("gns3server.compute.docker.Docker.query") as mock_query: + vm._permissions_fixed = False + await vm.stop() + assert not any( + call.args[:2] == ("POST", "containers/e90e34656842/stop") + for call in mock_query.mock_calls + ) + assert vm.status == "stopped" diff --git a/tests/controller/test_project.py b/tests/controller/test_project.py index 234bf1b57..5d7d8a52a 100644 --- a/tests/controller/test_project.py +++ b/tests/controller/test_project.py @@ -655,6 +655,19 @@ async def test_delete(project): assert not os.path.exists(project.path) +async def test_delete_does_not_start_nodes(project): + """ + Deleting a project must not start its nodes, even when auto_start is enabled. + """ + + project.auto_start = True + project.dump() + await project.close() + project.start_all = AsyncioMagicMock() + await project.delete() + assert not project.start_all.called + + async def test_dump(projects_dir): directory = projects_dir