Passed
Push — master ( 1eb667...735243 )
by Nils
02:30
created

InitCommand::getPreparedEndpoint()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
nc 1
nop 1
dl 0
loc 3
c 0
b 0
f 0
cc 1
rs 10
1
<?php
2
3
namespace Startwind\Inventorio\Command;
4
5
use GuzzleHttp\Client;
6
use GuzzleHttp\Exception\ClientException;
7
use GuzzleHttp\RequestOptions;
8
use Symfony\Component\Console\Command\Command;
9
use Symfony\Component\Console\Input\ArrayInput;
10
use Symfony\Component\Console\Input\InputArgument;
11
use Symfony\Component\Console\Input\InputInterface;
12
use Symfony\Component\Console\Input\InputOption;
13
use Symfony\Component\Console\Output\OutputInterface;
14
use Symfony\Component\Console\Style\SymfonyStyle;
15
16
class InitCommand extends InventorioCommand
17
{
18
    protected static $defaultName = 'init';
19
    protected static $defaultDescription = 'Initialize Inventorio';
20
21
    private const ENDPOINT_INIT = '/inventory/server/{serverId}';
22
23
    private const SERVER_ID_PREFIX = 'inv-srv-';
24
25
    /**
26
     * @inheritDoc
27
     */
28
    protected function configure(): void
29
    {
30
        parent::configure();
31
32
        $this->addOption('remote', null, InputOption::VALUE_REQUIRED, 'Start remote command mode');
33
        $this->addOption('logfile', null, InputOption::VALUE_REQUIRED, 'Start logfile mode');
34
        $this->addOption('metrics', null, InputOption::VALUE_REQUIRED, 'Start metrics collection mode');
35
36
        $this->addArgument('userId', InputArgument::REQUIRED, 'The inventorio user id.');
37
38
        $this->addOption('serverName', 's', InputOption::VALUE_OPTIONAL, 'The server name');
39
    }
40
41
    /**
42
     * @inheritDoc
43
     */
44
    protected function execute(InputInterface $input, OutputInterface $output): int
45
    {
46
        $this->initConfiguration($input->getOption('configFile'));
47
48
        if ($this->isInitialized()) {
49
            $output->writeln('<info>System is already initialized.</info>');
50
            return Command::SUCCESS;
51
        }
52
53
        $serverName = $this->getServerName($input, $output);
54
55
        $configFile = $this->getConfigFile();
56
        $serverId = $this->createServerId();
57
58
        $userId = $input->getArgument('userId');
59
60
        $client = new Client();
61
62
        $payload = [
63
            'userId' => $userId,
64
            'serverId' => $serverId,
65
            'serverName' => $serverName
66
        ];
67
68
        try {
69
            $response = $client->post($this->getPreparedEndpoint($serverId), [
70
                RequestOptions::JSON => $payload
71
            ]);
72
        } catch (ClientException $exception) {
73
            $result = json_decode((string)$exception->getResponse()->getBody(), true);
74
            $output->writeln('<error>Unable to initialize: ' . $result['message'] . '</error>');
75
            return Command::FAILURE;
76
        }
77
78
        $result = json_decode((string)$response->getBody(), true);
79
80
        $config = [
81
            'serverId' => $serverId,
82
            'userId' => $userId,
83
            'remote' => false,
84
            'commands' => $this->config->getCommands(false),
85
            'secret' => $result['data']['secret']
86
        ];
87
88
        if (!file_exists(dirname($configFile))) {
89
            mkdir(dirname($configFile), 0777, true);
90
        }
91
92
        file_put_contents($configFile, json_encode($config), JSON_PRETTY_PRINT);
93
94
        $output->writeln('<info>Server registered.</info>');
95
96
        $array = [
97
            'command' => 'config'
98
        ];
99
100
        if ($input->getOption('remote')) {
101
            $array['--remote'] = $input->getOption('remote');
102
        }
103
        if ($input->getOption('metrics')) {
104
            $array['--metrics'] = $input->getOption('metrics');
105
        }
106
107
        $newInput = new ArrayInput($array);
108
109
        $this->getApplication()->find('config')->run($newInput, $output);
110
111
        return Command::SUCCESS;
112
    }
113
114
    /**
115
     * Ask the user for the server name if not set a parameter.
116
     */
117
    private function getServerName(InputInterface $input, OutputInterface $output): string
118
    {
119
        if (!$input->getOption('serverName')) {
120
            $io = new SymfonyStyle($input, $output);
121
122
            $defaultName = gethostname();
123
124
            $serverName = $io->ask(
125
                'Please provide the name of the server (default: ' . $defaultName . ')',
126
                $defaultName,
127
                function (?string $value) {
128
                    if (strlen($value ?? '') < 3) {
129
                        throw new \RuntimeException('The server name has to be at least three characters long.');
130
                    }
131
                    return $value;
132
                }
133
            );
134
        } else {
135
            $serverName = $input->getOption('serverName');
136
        }
137
138
        return $serverName;
139
    }
140
141
    /**
142
     * Return the final endpoint where the collected data should be sent to.
143
     */
144
    private function getPreparedEndpoint($serverId): string
145
    {
146
        return str_replace('{serverId}', $serverId, $this->config->getInventorioServer() . self::ENDPOINT_INIT);
147
    }
148
149
    /**
150
     * Create an unique ID for the current server
151
     */
152
    private function createServerId(): string
153
    {
154
        $data = random_bytes(16);
155
156
        $data[6] = chr(ord($data[6]) & 0x0f | 0x40);
157
        $data[8] = chr(ord($data[8]) & 0x3f | 0x80);
158
159
        return self::SERVER_ID_PREFIX . vsprintf('%s%s-%s-%s-%s-%s%s%s', /** @scrutinizer ignore-type */ str_split(bin2hex($data), 4));
160
    }
161
}
162