Skip to content

Commit f272900

Browse files
jrushlowweaverryan
authored andcommitted
Introducing make:docker:database + fixing a YamlSourceManipulator bug.
Fixing bug with YamlSourceManipulator related to comments matching values When we are walking an existing YAML structure, we try to advance to beyond a value, but YSM got confused because a comment containing that exact string value appeared before the value. This caused YSM to incorrectly put its pointer in the middle of that comment, instead of advancing to the end of the real value.
1 parent 4f08d17 commit f272900

File tree

10 files changed

+853
-11
lines changed

10 files changed

+853
-11
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony MakerBundle package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\MakerBundle\Docker;
13+
14+
use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException;
15+
16+
/**
17+
* @author Jesse Rushlow <[email protected]>
18+
*
19+
* @internal
20+
*/
21+
final class DockerDatabaseServices
22+
{
23+
/**
24+
* @throws RuntimeCommandException
25+
*/
26+
public static function getDatabaseSkeleton(string $name, string $version): array
27+
{
28+
switch ($name) {
29+
case 'mariadb':
30+
return [
31+
'image' => sprintf('mariadb:%s', $version),
32+
'environment' => [
33+
'MYSQL_ROOT_PASSWORD' => 'password',
34+
],
35+
];
36+
case 'mysql':
37+
return [
38+
'image' => sprintf('mysql:%s', $version),
39+
'environment' => [
40+
'MYSQL_ROOT_PASSWORD' => 'password',
41+
],
42+
];
43+
case 'postgres':
44+
return [
45+
'image' => sprintf('postgres:%s', $version),
46+
'environment' => [
47+
'POSTGRES_PASSWORD' => 'main',
48+
'POSTGRES_USER' => 'main',
49+
'POSTGRES_DB' => 'main',
50+
],
51+
];
52+
}
53+
54+
self::throwInvalidDatabase($name);
55+
}
56+
57+
/**
58+
* @throws RuntimeCommandException
59+
*/
60+
public static function getDefaultPorts(string $name): array
61+
{
62+
switch ($name) {
63+
case 'mariadb':
64+
case 'mysql':
65+
return ['3306'];
66+
case 'postgres':
67+
return ['5432'];
68+
}
69+
70+
self::throwInvalidDatabase($name);
71+
}
72+
73+
public static function getSuggestedServiceVersion(string $name): string
74+
{
75+
if ('postgres' === $name) {
76+
return 'alpine';
77+
}
78+
79+
return 'latest';
80+
}
81+
82+
public static function getMissingExtensionName(string $name): ?string
83+
{
84+
switch ($name) {
85+
case 'mariadb':
86+
case 'mysql':
87+
$driver = 'mysql';
88+
break;
89+
case 'postgres':
90+
$driver = 'pdsql';
91+
break;
92+
default:
93+
self::throwInvalidDatabase($name);
94+
}
95+
96+
if (!\in_array($driver, \PDO::getAvailableDrivers(), true)) {
97+
return $driver;
98+
}
99+
100+
return null;
101+
}
102+
103+
/**
104+
* @throws RuntimeCommandException
105+
*/
106+
private static function throwInvalidDatabase(string $name): void
107+
{
108+
throw new RuntimeCommandException(sprintf('%s is not a valid / supported docker database type.', $name));
109+
}
110+
}

src/Maker/MakeDockerDatabase.php

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony MakerBundle package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\MakerBundle\Maker;
13+
14+
use Symfony\Bundle\MakerBundle\ConsoleStyle;
15+
use Symfony\Bundle\MakerBundle\DependencyBuilder;
16+
use Symfony\Bundle\MakerBundle\Docker\DockerDatabaseServices;
17+
use Symfony\Bundle\MakerBundle\FileManager;
18+
use Symfony\Bundle\MakerBundle\Generator;
19+
use Symfony\Bundle\MakerBundle\InputConfiguration;
20+
use Symfony\Bundle\MakerBundle\Util\ComposeFileManipulator;
21+
use Symfony\Bundle\MakerBundle\Validator;
22+
use Symfony\Component\Console\Command\Command;
23+
use Symfony\Component\Console\Input\InputInterface;
24+
use Symfony\Component\Yaml\Yaml;
25+
26+
/**
27+
* @author Jesse Rushlow <[email protected]>
28+
*
29+
* @internal
30+
*/
31+
final class MakeDockerDatabase extends AbstractMaker
32+
{
33+
private $fileManager;
34+
private $composeFilePath;
35+
36+
/**
37+
* @var ComposeFileManipulator
38+
*/
39+
private $composeFileManipulator;
40+
41+
/**
42+
* @var string type of database selected by the user
43+
*/
44+
private $databaseChoice;
45+
46+
/**
47+
* @var string Service identifier to be set in docker-compose.yaml
48+
*/
49+
private $serviceName = 'database';
50+
51+
/**
52+
* @var string Version set in docker-compose.yaml for the service. e.g. latest
53+
*/
54+
private $serviceVersion = 'latest';
55+
56+
public function __construct(FileManager $fileManager)
57+
{
58+
$this->fileManager = $fileManager;
59+
$this->composeFilePath = sprintf('%s/docker-compose.yaml', $this->fileManager->getRootDirectory());
60+
}
61+
62+
public static function getCommandName(): string
63+
{
64+
return 'make:docker:database';
65+
}
66+
67+
public function configureCommand(Command $command, InputConfiguration $inputConfig): void
68+
{
69+
$command
70+
->setDescription('Adds a database container to your docker-compose.yaml file.')
71+
;
72+
}
73+
74+
public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void
75+
{
76+
$io->section('- Docker Compose Setup-');
77+
78+
$composeFileContents = '';
79+
$statusMessage = 'Existing docker-compose.yaml not found: a new one will be generated!';
80+
81+
if ($this->fileManager->fileExists($this->composeFilePath)) {
82+
$composeFileContents = $this->fileManager->getFileContents($this->composeFilePath);
83+
84+
$statusMessage = 'We found your existing docker-compose.yaml: Let\'s update it!';
85+
}
86+
87+
$io->text($statusMessage);
88+
89+
$this->composeFileManipulator = new ComposeFileManipulator($composeFileContents);
90+
91+
$io->newLine();
92+
93+
$this->databaseChoice = strtolower($io->choice(
94+
'Which database service will you be creating?',
95+
['MySQL', 'MariaDB', 'Postgres']
96+
));
97+
98+
$io->text([sprintf(
99+
'For a list of supported versions, check out https://hub.docker.com/_/%s',
100+
$this->databaseChoice
101+
)]);
102+
103+
$this->serviceVersion = $io->ask('What version would you like to use?', DockerDatabaseServices::getSuggestedServiceVersion($this->databaseChoice));
104+
105+
if ($this->composeFileManipulator->serviceExists($this->serviceName)) {
106+
$io->comment(sprintf('A <fg=yellow>"%s"</> service is already defined.', $this->serviceName));
107+
$io->newLine();
108+
109+
$serviceNameMsg[] = 'If you are using the Symfony Binary, it will expose the connection config for';
110+
$serviceNameMsg[] = 'this service as environment variables. The name of the service determines the';
111+
$serviceNameMsg[] = 'name of those environment variables.';
112+
$serviceNameMsg[] = '';
113+
$serviceNameMsg[] = 'For example, if you name the service <fg=yellow>database_alt</>, the binary will expose a';
114+
$serviceNameMsg[] = '<fg=yellow>DATABASE_ALT_URL</> environment variable.';
115+
116+
$io->text($serviceNameMsg);
117+
118+
$this->serviceName = $io->ask(sprintf('What name should we call the new %s service? e.g. database', $this->serviceName), null, [Validator::class, 'notBlank']);
119+
}
120+
121+
$this->checkForPDOSupport($this->databaseChoice, $io);
122+
}
123+
124+
public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
125+
{
126+
$io->newLine();
127+
128+
$service = DockerDatabaseServices::getDatabaseSkeleton($this->databaseChoice, $this->serviceVersion);
129+
130+
$this->composeFileManipulator->addDockerService($this->serviceName, $service);
131+
$this->composeFileManipulator->exposePorts($this->serviceName, DockerDatabaseServices::getDefaultPorts($this->databaseChoice));
132+
133+
$generator->dumpFile($this->composeFilePath, $this->composeFileManipulator->getDataString());
134+
$generator->writeChanges();
135+
136+
$this->writeSuccessMessage($io);
137+
138+
$io->text(sprintf('The new <fg=yellow>"%s"</> service is now ready!', $this->serviceName));
139+
$io->newLine();
140+
141+
$ports = DockerDatabaseServices::getDefaultPorts($this->databaseChoice);
142+
$closing[] = 'Next:';
143+
$closing[] = sprintf(' A) Run <fg=yellow>docker-compose up -d %s</> to start your database container', $this->serviceName);
144+
$closing[] = sprintf(' or <fg=yellow>docker-compose up -d</> to start all of them.');
145+
$closing[] = '';
146+
$closing[] = ' B) If you are using the Symfony Binary, it will detect the new service automatically.';
147+
$closing[] = ' Run <fg=yellow>symfony var:export --multiline</> to see the environment variables the binary is exposing.';
148+
$closing[] = ' These will override any values you have in your .env files.';
149+
$closing[] = '';
150+
$closing[] = ' C) Run <fg=yellow>docker-compose stop</> will stop all the containers in docker-compose.yaml.';
151+
$closing[] = ' <fg=yellow>docker-compose down</> will stop and destroy the containers.';
152+
$closing[] = '';
153+
$closing[] = sprintf(
154+
'Port%s %s will be exposed to %s random port%s on your host machine.',
155+
1 === \count($ports) ? '' : 's',
156+
implode(' ', $ports),
157+
1 === \count($ports) ? 'a' : '',
158+
1 === \count($ports) ? '' : 's'
159+
);
160+
161+
$io->text($closing);
162+
$io->newLine();
163+
}
164+
165+
public function configureDependencies(DependencyBuilder $dependencies): void
166+
{
167+
$dependencies->addClassDependency(
168+
Yaml::class,
169+
'yaml'
170+
);
171+
}
172+
173+
private function checkForPDOSupport(string $databaseType, ConsoleStyle $io): void
174+
{
175+
$extension = DockerDatabaseServices::getMissingExtensionName($databaseType);
176+
177+
if (null !== $extension) {
178+
$io->note(
179+
sprintf('Cannot find PHP\'s pdo_%s extension. Be sure it\'s installed & enabled to talk to the database.', $extension)
180+
);
181+
}
182+
}
183+
}

src/Resources/config/makers.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@
3030
<tag name="maker.command" />
3131
</service>
3232

33+
<service id="maker.maker.make_docker_database" class="Symfony\Bundle\MakerBundle\Maker\MakeDockerDatabase">
34+
<argument type="service" id="maker.file_manager" />
35+
<tag name="maker.command" />
36+
</service>
37+
3338
<service id="maker.maker.make_entity" class="Symfony\Bundle\MakerBundle\Maker\MakeEntity">
3439
<argument type="service" id="maker.file_manager" />
3540
<argument type="service" id="maker.doctrine_helper" />

0 commit comments

Comments
 (0)