GeezerVison is showing github.com — feat(build): accept several tags in imageBuild() · docker-php/docker-php@0c271fa
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: docs/cookbook/build-image.mdx +4Lines changed: 4 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -79,6 +79,10 @@ $build->wait(); | 79 | 79 | Do not pre-URL-encode the JSON options. Declare `ARG APP_MODE` in the Dockerfile | 80 | 80 | if the build uses it. Build arguments are not a safe channel for secrets. | 81 | 81 |
| | 82 | +To apply several tags, pass a list to `t`, for example | | 83 | +`'t' => ['docker-php-example:latest', 'docker-php-example:1.0']`. Docker PHP | | 84 | +sends one `t` parameter per tag. Lists require client `3.3.0` or later. | | 85 | + | 82 | 86 | A streamed context is consumed by the first request. The original tar process | 83 | 87 | is not rewindable. Keep the context alive until the request has | 84 | 88 | finished, and do not assume a process-backed stream has a known byte length. | |
Collapse file Copy file name to clipboardExpand all lines: src/Docker.php +28Lines changed: 28 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -131,6 +131,34 @@ public function execStartInteractive(string $id, ?ExecIdStartPostBody $requestBo | 131 | 131 | /** | 132 | 132 | * {@inheritdoc} | 133 | 133 | * | | 134 | + * @param array{ | | 135 | + * "dockerfile"?: string, | | 136 | + * "t"?: string|list<string>, | | 137 | + * "extrahosts"?: string, | | 138 | + * "remote"?: string, | | 139 | + * "q"?: bool, | | 140 | + * "nocache"?: bool, | | 141 | + * "cachefrom"?: string, | | 142 | + * "pull"?: string, | | 143 | + * "rm"?: bool, | | 144 | + * "forcerm"?: bool, | | 145 | + * "memory"?: int, | | 146 | + * "memswap"?: int, | | 147 | + * "cpushares"?: int, | | 148 | + * "cpusetcpus"?: string, | | 149 | + * "cpuperiod"?: int, | | 150 | + * "cpuquota"?: int, | | 151 | + * "buildargs"?: string, | | 152 | + * "shmsize"?: int, | | 153 | + * "squash"?: bool, | | 154 | + * "labels"?: string, | | 155 | + * "networkmode"?: string, | | 156 | + * "platform"?: string, | | 157 | + * "target"?: string, | | 158 | + * "outputs"?: string, | | 159 | + * "version"?: string, | | 160 | + * } $queryParameters | | 161 | + * | 134 | 162 | * @return ($fetch is 'object' ? BuildStream|null : ResponseInterface) | 135 | 163 | */ | 136 | 164 | public function imageBuild($requestBody = null, array $queryParameters = [], array $headerParameters = [], string $fetch = self::FETCH_OBJECT) | |
Collapse file Copy file name to clipboardExpand all lines: src/Endpoint/ImageBuild.php +17Lines changed: 17 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 9 | 9 | use Docker\Stream\TarStream; | 10 | 10 | use Nyholm\Psr7\Stream; | 11 | 11 | use Psr\Http\Message\ResponseInterface; | | 12 | +use Symfony\Component\OptionsResolver\Options; | | 13 | +use Symfony\Component\OptionsResolver\OptionsResolver; | 12 | 14 | use Symfony\Component\Serializer\SerializerInterface; | 13 | 15 |
| 14 | 16 | class ImageBuild extends BaseEndpoint | @@ -24,6 +26,21 @@ public function getBody(SerializerInterface $serializer, $streamFactory = null): | 24 | 26 | return [['Content-Type' => ['application/octet-stream']], $body]; | 25 | 27 | } | 26 | 28 |
| | 29 | + protected function getQueryOptionsResolver(): OptionsResolver | | 30 | + { | | 31 | + $optionsResolver = parent::getQueryOptionsResolver(); | | 32 | + // Docker applies each repeated `t` parameter as a separate tag. | | 33 | + $optionsResolver->setAllowedTypes('t', ['string', 'string[]']); | | 34 | + $optionsResolver->setNormalizer('t', static fn (Options $options, $value) => \is_array($value) ? array_values($value) : $value); | | 35 | + | | 36 | + return $optionsResolver; | | 37 | + } | | 38 | + | | 39 | + protected function getQueryStyles(): array | | 40 | + { | | 41 | + return ['t' => ['style' => 'form', 'explode' => true]] + parent::getQueryStyles(); | | 42 | + } | | 43 | + | 27 | 44 | protected function transformResponseBody(ResponseInterface $response, SerializerInterface $serializer, ?string $contentType = null) | 28 | 45 | { | 29 | 46 | if (200 === $response->getStatusCode()) { | |
Collapse file Copy file name to clipboard +38Lines changed: 38 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +<?php | | 2 | + | | 3 | +declare(strict_types=1); | | 4 | + | | 5 | +namespace Docker\Tests\Endpoint; | | 6 | + | | 7 | +use Docker\Endpoint\ImageBuild; | | 8 | +use PHPUnit\Framework\Attributes\DataProvider; | | 9 | +use PHPUnit\Framework\TestCase; | | 10 | +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; | | 11 | + | | 12 | +class ImageBuildTest extends TestCase | | 13 | +{ | | 14 | + public static function tagProvider(): iterable | | 15 | + { | | 16 | + yield 'single tag' => ['app:latest', ['t=app%3Alatest']]; | | 17 | + yield 'several tags' => [['app:latest', 'app:1.0'], ['t=app%3Alatest', 't=app%3A1.0']]; | | 18 | + yield 'keyed tags' => [['a' => 'app:latest', 'b' => 'app:1.0'], ['t=app%3Alatest', 't=app%3A1.0']]; | | 19 | + yield 'no tags' => [[], []]; | | 20 | + } | | 21 | + | | 22 | + #[DataProvider('tagProvider')] | | 23 | + public function testRepeatsTagParameter(string|array $tags, array $expected): void | | 24 | + { | | 25 | + $query = (new ImageBuild(null, ['t' => $tags]))->getQueryString(); | | 26 | + $parameters = explode('&', $query); | | 27 | + | | 28 | + $this->assertSame($expected, array_values(array_filter($parameters, static fn (string $pair): bool => str_starts_with($pair, 't=')))); | | 29 | + $this->assertContains('dockerfile=Dockerfile', $parameters); | | 30 | + } | | 31 | + | | 32 | + public function testRejectsNonStringTags(): void | | 33 | + { | | 34 | + $this->expectException(InvalidOptionsException::class); | | 35 | + | | 36 | + (new ImageBuild(null, ['t' => ['app:latest', 1]]))->getQueryString(); | | 37 | + } | | 38 | +} |
Collapse file Copy file name to clipboardExpand all lines: tests/Resource/ImageResourceTest.php +22Lines changed: 22 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -40,6 +40,28 @@ public function testBuild(): void | 40 | 40 | $this->assertStringContainsString('Successfully', $lastMessage); | 41 | 41 | } | 42 | 42 |
| | 43 | + public function testBuildAppliesSeveralTags(): void | | 44 | + { | | 45 | + $contextBuilder = new ContextBuilder(); | | 46 | + $contextBuilder->from('busybox:latest'); | | 47 | + $contextBuilder->add('/test', 'test file content'); | | 48 | + $context = $contextBuilder->getContext(); | | 49 | + $tags = ['docker-php-test-tags:one', 'docker-php-test-tags:two']; | | 50 | + | | 51 | + $buildStream = $this->getManager()->imageBuild($context->read(), ['t' => $tags]); | | 52 | + $buildStream->wait(); | | 53 | + | | 54 | + try { | | 55 | + $repoTags = $this->getManager()->imageInspect($tags[0])->getRepoTags(); | | 56 | + sort($repoTags); | | 57 | + $this->assertSame($tags, $repoTags); | | 58 | + } finally { | | 59 | + foreach ($tags as $tag) { | | 60 | + $this->getManager()->imageDelete($tag); | | 61 | + } | | 62 | + } | | 63 | + } | | 64 | + | 43 | 65 | public function testCreate(): void | 44 | 66 | { | 45 | 67 | $createImageStream = $this->getManager()->imageCreate('', [ | |
Collapse file Copy file name to clipboardExpand all lines: tests/StaticAnalysis/DockerReturnTypes.php +1Lines changed: 1 addition & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -16,6 +16,7 @@ function dockerReturnTypes(Docker $docker): void | 16 | 16 | assertType('Docker\Stream\DockerRawStream|null', $docker->containerLogs('id')); | 17 | 17 | assertType('Docker\Stream\DockerRawStream|null', $docker->execStart('id')); | 18 | 18 | assertType('Docker\Stream\BuildStream|null', $docker->imageBuild()); | | 19 | + assertType('Docker\Stream\BuildStream|null', $docker->imageBuild(null, ['t' => ['app:latest', 'app:1.0']])); | 19 | 20 | assertType('Docker\Stream\CreateImageStream|null', $docker->imageCreate()); | 20 | 21 | assertType('Docker\Stream\PushStream|null', $docker->imagePush('name')); | 21 | 22 | assertType('Docker\Stream\EventStream|null', $docker->systemEvents()); | |
|