Issues (224)

src/Console/Command/GenerateDockerFile.php (3 issues)

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the box project.
7
 *
8
 * (c) Kevin Herrera <[email protected]>
9
 *     Théo Fidry <[email protected]>
10
 *
11
 * This source file is subject to the MIT license that is bundled
12
 * with this source code in the file LICENSE.
13
 */
14
15
namespace KevinGH\Box\Console\Command;
16
17
use Fidry\Console\Command\CommandAware;
18
use Fidry\Console\Command\CommandAwareness;
19
use Fidry\Console\Command\Configuration;
20
use Fidry\Console\ExitCode;
21
use Fidry\Console\IO;
0 ignored issues
show
The type Fidry\Console\IO was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
22
use Fidry\FileSystem\FS;
23
use KevinGH\Box\DockerFileGenerator;
24
use Symfony\Component\Console\Input\InputArgument;
25
use Symfony\Component\Console\Input\InputInterface;
26
use Symfony\Component\Console\Input\StringInput;
27
use Symfony\Component\Console\Question\ConfirmationQuestion;
28
use Symfony\Component\Filesystem\Path;
29
use Webmozart\Assert\Assert;
30
use function file_exists;
31
use function getcwd;
32
use function realpath;
33
use function sprintf;
34
35
/**
36
 * @private
37
 */
38
final class GenerateDockerFile implements CommandAware
39
{
40
    use CommandAwareness;
41
42
    public const NAME = 'docker';
43
44
    private const PHAR_ARG = 'phar';
45
    private const DOCKER_FILE_NAME = 'Dockerfile';
46
47
    public function getConfiguration(): Configuration
48
    {
49
        return new Configuration(
50
            'docker',
51
            '🐳  Generates a Dockerfile for the given PHAR',
52
            '',
53
            [
54
                new InputArgument(
55
                    self::PHAR_ARG,
56
                    InputArgument::OPTIONAL,
57
                    'The PHAR file',
58
                ),
59
            ],
60
            [
61
                ConfigOption::getOptionInput(),
62
                ChangeWorkingDirOption::getOptionInput(),
63
            ],
64
        );
65
    }
66
67
    public function execute(IO $io): int
68
    {
69
        ChangeWorkingDirOption::changeWorkingDirectory($io);
70
71
        $pharFilePath = $this->getPharFilePath($io);
72
73
        if (null === $pharFilePath) {
74
            return ExitCode::FAILURE;
75
        }
76
77
        $io->newLine();
78
        $io->writeln(
79
            sprintf(
80
                '🐳  Generating a Dockerfile for the PHAR "<comment>%s</comment>"',
81
                $pharFilePath,
82
            ),
83
        );
84
        $io->newLine();
85
86
        $requirementsFilePhar = 'phar://'.$pharFilePath.'/.box/.requirements.php';
87
88
        return $this->generateFile(
89
            $pharFilePath,
90
            $requirementsFilePhar,
91
            $io,
92
        );
93
    }
94
95
    /**
96
     * @return null|non-empty-string
0 ignored issues
show
Documentation Bug introduced by
The doc comment null|non-empty-string at position 2 could not be parsed: Unknown type name 'non-empty-string' at position 2 in null|non-empty-string.
Loading history...
97
     */
98
    private function getPharFilePath(IO $io): ?string
99
    {
100
        $pharFilePath = $io->getTypedArgument(self::PHAR_ARG)->asNullableNonEmptyString();
101
102
        if (null === $pharFilePath) {
103
            $pharFilePath = $this->guessPharPath($io);
104
        }
105
106
        if (null === $pharFilePath) {
107
            return null;
108
        }
109
110
        $pharFilePath = Path::canonicalize($pharFilePath);
111
        Assert::file($pharFilePath);
112
113
        return false !== realpath($pharFilePath) ? realpath($pharFilePath) : $pharFilePath;
114
    }
115
116
    private function guessPharPath(IO $io): ?string
117
    {
118
        $config = ConfigOption::getConfig($io, true);
119
120
        if (file_exists($config->getOutputPath())) {
121
            return $config->getOutputPath();
122
        }
123
124
        $compile = $io->askQuestion(
125
            new ConfirmationQuestion(
126
                'The output PHAR could not be found, do you wish to generate it by running "<comment>box compile</comment>"?',
127
                true,
128
            ),
129
        );
130
131
        if (false === $compile) {
132
            $io->error('Could not find the PHAR to generate the docker file for');
133
134
            return null;
135
        }
136
137
        $this->getCompileCommand()->execute(
138
            new IO(
139
                self::createCompileInput($io),
140
                clone $io->getOutput(),
141
            ),
142
        );
143
144
        return $config->getOutputPath();
145
    }
146
147
    private function getCompileCommand(): Compile
148
    {
149
        /* @noinspection PhpIncompatibleReturnTypeInspection */
150
        return $this->getCommandRegistry()->findCommand(Compile::NAME);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getCommand...\Command\Compile::NAME) returns the type Fidry\Console\Bridge\Com...\ReversedSymfonyCommand which is incompatible with the type-hinted return KevinGH\Box\Console\Command\Compile.
Loading history...
151
    }
152
153
    private static function createCompileInput(IO $io): InputInterface
154
    {
155
        if ($io->isQuiet()) {
156
            $compileInput = '--quiet';
157
        } elseif ($io->isVerbose()) {
158
            $compileInput = '--verbose 1';
159
        } elseif ($io->isVeryVerbose()) {
160
            $compileInput = '--verbose 2';
161
        } elseif ($io->isDebug()) {
162
            $compileInput = '--verbose 3';
163
        } else {
164
            $compileInput = '';
165
        }
166
167
        $compileInput = new StringInput($compileInput);
168
        $compileInput->setInteractive(false);
169
170
        return $compileInput;
171
    }
172
173
    private function generateFile(string $pharPath, string $requirementsPhar, IO $io): int
174
    {
175
        if (false === file_exists($requirementsPhar)) {
176
            $io->error(
177
                'Cannot retrieve the requirements for the PHAR. Make sure the PHAR has been built with Box and the requirement checker enabled.',
178
            );
179
180
            return ExitCode::FAILURE;
181
        }
182
183
        $requirements = include $requirementsPhar;
184
185
        $dockerFileContents = DockerFileGenerator::createForRequirements(
186
            $requirements,
187
            Path::makeRelative($pharPath, getcwd()),
188
        )
189
            ->generateStub();
190
191
        if (file_exists(self::DOCKER_FILE_NAME)) {
192
            $remove = $io->askQuestion(
193
                new ConfirmationQuestion(
194
                    'A Docker file has already been found, are you sure you want to override it?',
195
                    true,
196
                ),
197
            );
198
199
            if (false === $remove) {
200
                $io->writeln('Skipped the docker file generation.');
201
202
                return ExitCode::SUCCESS;
203
            }
204
        }
205
206
        FS::dumpFile(self::DOCKER_FILE_NAME, $dockerFileContents);
207
208
        $io->success('Done');
209
210
        $io->writeln(
211
            [
212
                sprintf(
213
                    'You can now inspect your <comment>%s</comment> file or build your container with:',
214
                    self::DOCKER_FILE_NAME,
215
                ),
216
                '$ <comment>docker build .</comment>',
217
            ],
218
        );
219
220
        return ExitCode::SUCCESS;
221
    }
222
}
223