From fc0060b8def5a74ed86279bde40039bc748cce70 Mon Sep 17 00:00:00 2001 From: Pablo Zmdl Date: Thu, 28 May 2026 12:39:20 +0200 Subject: [PATCH] Ensure all HTTP requests are proxied, even with streaming When requesting a streamed response, Guzzle apparently doesn't use curl, and thus we have to specify the unix socket proxy differently. We can't specify it when creating the client, though (Guzzle complains). Signed-off-by: Pablo Zmdl --- php/src/Docker/DockerActionManager.php | 56 ++++++++++++++------------ 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/php/src/Docker/DockerActionManager.php b/php/src/Docker/DockerActionManager.php index 90d2e924..61c7d9e9 100644 --- a/php/src/Docker/DockerActionManager.php +++ b/php/src/Docker/DockerActionManager.php @@ -48,7 +48,7 @@ readonly class DockerActionManager { public function GetContainerRunningState(Container $container): ContainerState { $url = $this->BuildApiUrl(sprintf('containers/%s/json', urlencode($container->identifier))); try { - $response = $this->guzzleClient->get($url); + $response = $this->sendHttpRequest('GET', $url); } catch (RequestException $e) { if ($e->getCode() === 404) { return ContainerState::ImageDoesNotExist; @@ -68,7 +68,7 @@ readonly class DockerActionManager { public function GetContainerRestartingState(Container $container): ContainerState { $url = $this->BuildApiUrl(sprintf('containers/%s/json', urlencode($container->identifier))); try { - $response = $this->guzzleClient->get($url); + $response = $this->sendHttpRequest('GET', $url); } catch (RequestException $e) { if ($e->getCode() === 404) { return ContainerState::ImageDoesNotExist; @@ -138,7 +138,7 @@ readonly class DockerActionManager { public function DeleteContainer(Container $container): void { $url = $this->BuildApiUrl(sprintf('containers/%s?v=true', urlencode($container->identifier))); try { - $this->guzzleClient->delete($url); + $this->sendHttpRequest('DELETE', $url); } catch (RequestException $e) { if ($e->getCode() !== 404) { throw $e; @@ -155,7 +155,7 @@ readonly class DockerActionManager { // Delete the borg cache volume $url = $this->BuildApiUrl('volumes/nextcloud_aio_backup_cache'); try { - $this->guzzleClient->delete($url); + $this->sendHttpRequest('DELETE', $url); error_log('nextcloud_aio_backup_cache volume deleted successfully.'); } catch (RequestException $e) { if ($e->getCode() !== 404) { @@ -174,7 +174,7 @@ readonly class DockerActionManager { urlencode($id), $since )); - $responseBody = (string)$this->guzzleClient->get($url)->getBody(); + $responseBody = (string)$this->sendHttpRequest('GET', $url)->getBody(); $response = ""; $separator = "\r\n"; @@ -196,7 +196,7 @@ readonly class DockerActionManager { if ($addToStreamingResponseBody !== null) { $addToStreamingResponseBody("Starting container", $container); } - $this->guzzleClient->post($url); + $this->sendHttpRequest('POST', $url); } catch (RequestException $e) { throw new \Exception("Could not start container " . $container->identifier . ": " . $e->getResponse()?->getBody()->getContents()); } @@ -215,7 +215,7 @@ readonly class DockerActionManager { $firstChar = substr($volume->name, 0, 1); if (!in_array($firstChar, $forbiddenChars)) { - $this->guzzleClient->request( + $this->sendHttpRequest( 'POST', $url, [ @@ -494,7 +494,7 @@ readonly class DockerActionManager { $url = $this->BuildApiUrl('containers/create?name=' . $container->identifier); try { - $this->guzzleClient->request( + $this->sendHttpRequest( 'POST', $url, [ @@ -554,7 +554,7 @@ readonly class DockerActionManager { $addToStreamingResponseBody("Pulling image", $container); } $imageUrl = $this->BuildApiUrl(sprintf('images/%s/json', $encodedImageName)); - $this->guzzleClient->get($imageUrl)->getBody()->getContents(); + $this->sendHttpRequest('GET', $imageUrl)->getBody()->getContents(); } catch (\Throwable $e) { $imageIsThere = false; } @@ -562,7 +562,7 @@ readonly class DockerActionManager { $maxRetries = 3; for ($attempt = 1; $attempt <= $maxRetries; $attempt++) { try { - $this->guzzleClient->post($url); + $this->sendHttpRequest('POST', $url); break; } catch (RequestException $e) { $message = "Could not pull image " . $imageName . " (attempt $attempt/$maxRetries): " . $e->getResponse()?->getBody()->getContents(); @@ -647,11 +647,11 @@ readonly class DockerActionManager { private function GetRepoDigestsOfContainer(string $containerName): ?array { try { $containerUrl = $this->BuildApiUrl(sprintf('containers/%s/json', $containerName)); - $containerOutput = json_decode($this->guzzleClient->get($containerUrl)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); + $containerOutput = json_decode($this->sendHttpRequest('GET', $containerUrl)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); $imageName = $containerOutput['Image']; $imageUrl = $this->BuildApiUrl(sprintf('images/%s/json', $imageName)); - $imageOutput = json_decode($this->guzzleClient->get($imageUrl)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); + $imageOutput = json_decode($this->sendHttpRequest('GET', $imageUrl)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); if (!isset($imageOutput['RepoDigests'])) { error_log('RepoDigests is not set of container ' . $containerName); @@ -695,7 +695,7 @@ readonly class DockerActionManager { $containerName = 'nextcloud-aio-mastercontainer'; $url = $this->BuildApiUrl(sprintf('containers/%s/json', $containerName)); try { - $output = json_decode($this->guzzleClient->get($url)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); + $output = json_decode($this->sendHttpRequest('GET', $url)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); $imageNameArray = explode(':', $output['Config']['Image']); if (count($imageNameArray) === 2) { $imageName = $imageNameArray[0]; @@ -722,7 +722,7 @@ readonly class DockerActionManager { $containerName = 'nextcloud-aio-mastercontainer'; $url = $this->BuildApiUrl(sprintf('containers/%s/json', $containerName)); try { - $output = json_decode($this->guzzleClient->get($url)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); + $output = json_decode($this->sendHttpRequest('GET', $url)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); $tagArray = explode(':', $output['Config']['Image']); if (count($tagArray) === 2) { $tag = $tagArray[1]; @@ -808,7 +808,7 @@ readonly class DockerActionManager { // Create exec instance $url = $this->BuildApiUrl(sprintf('containers/%s/exec', urlencode($containerName))); $response = json_decode( - $this->guzzleClient->request( + $this->sendHttpRequest( 'POST', $url, [ @@ -839,7 +839,7 @@ readonly class DockerActionManager { $requestOptions['stream'] = true; } - $startResponse = $this->guzzleClient->request('POST', $url, $requestOptions); + $startResponse = $this->sendHttpRequest('POST', $url, $requestOptions); if ($outputCallback !== null) { $body = $startResponse->getBody(); @@ -859,7 +859,7 @@ readonly class DockerActionManager { ); try { - $this->guzzleClient->request( + $this->sendHttpRequest( 'POST', $url, [ @@ -880,7 +880,7 @@ readonly class DockerActionManager { if ($createNetwork) { $url = $this->BuildApiUrl('networks/create'); try { - $this->guzzleClient->request( + $this->sendHttpRequest( 'POST', $url, [ @@ -909,7 +909,7 @@ readonly class DockerActionManager { } try { - $this->guzzleClient->request( + $this->sendHttpRequest( 'POST', $url, [ @@ -954,7 +954,7 @@ readonly class DockerActionManager { } $url = $this->BuildApiUrl(sprintf('containers/%s/stop?t=%s', urlencode($container->identifier), $maxShutDownTime)); try { - $this->guzzleClient->post($url); + $this->sendHttpRequest('POST', $url); } catch (RequestException $e) { if ($e->getCode() !== 404 && $e->getCode() !== 304) { throw $e; @@ -966,7 +966,7 @@ readonly class DockerActionManager { $containerName = 'nextcloud-aio-borgbackup'; $url = $this->BuildApiUrl(sprintf('containers/%s/json', urlencode($containerName))); try { - $response = $this->guzzleClient->get($url); + $response = $this->sendHttpRequest('GET', $url); } catch (RequestException $e) { if ($e->getCode() === 404) { return -1; @@ -988,7 +988,7 @@ readonly class DockerActionManager { $containerName = 'nextcloud-aio-database'; $url = $this->BuildApiUrl(sprintf('containers/%s/json', urlencode($containerName))); try { - $response = $this->guzzleClient->get($url); + $response = $this->sendHttpRequest('GET', $url); } catch (RequestException $e) { if ($e->getCode() === 404) { return -1; @@ -1028,7 +1028,7 @@ readonly class DockerActionManager { $imageName = $imageName . ':' . $this->GetCurrentChannel(); try { $imageUrl = $this->BuildApiUrl(sprintf('images/%s/json', $imageName)); - $imageOutput = json_decode($this->guzzleClient->get($imageUrl)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); + $imageOutput = json_decode($this->sendHttpRequest('GET', $imageUrl)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); if (!isset($imageOutput['Created'])) { error_log('Created is not set of image ' . $imageName); @@ -1106,7 +1106,7 @@ readonly class DockerActionManager { } try { - $response = $this->guzzleClient->post($url); + $response = $this->sendHttpRequest('POST', $url); if ($addToStreamingResponseBody !== null) { $data = json_decode((string)$response->getBody(), true); $deleted = 0; @@ -1144,4 +1144,10 @@ readonly class DockerActionManager { sleep(10); } } -} + + protected function sendHttpRequest(string $httpMethod, string $url, array $requestOptions = []): Guzzle\Http\Message\Response { + if ($requestOptions['stream'] === true) { + $requestOptions['proxy'] = 'unix:///var/run/docker.sock'; + } + return $this->guzzleClient->request($httpMethod, $url, $requestOptions); + }