-
Notifications
You must be signed in to change notification settings - Fork 61
Expand file tree
/
Copy pathConfigPushCommand.php
More file actions
190 lines (167 loc) · 6.84 KB
/
Copy pathConfigPushCommand.php
File metadata and controls
190 lines (167 loc) · 6.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
<?php
declare(strict_types=1);
namespace Acquia\Cli\Command\Source;
use Acquia\Cli\ApiCredentialsInterface;
use Acquia\Cli\Attribute\RequireAuth;
use Acquia\Cli\CloudApi\ClientService;
use Acquia\Cli\Command\CommandBase;
use Acquia\Cli\DataStore\AcquiaCliDatastore;
use Acquia\Cli\DataStore\CloudDataStore;
use Acquia\Cli\Exception\AcquiaCliException;
use Acquia\Cli\Helpers\LocalMachineHelper;
use Acquia\Cli\Helpers\LoopHelper;
use Acquia\Cli\Helpers\SshHelper;
use Acquia\Cli\Helpers\TelemetryHelper;
use Acquia\Cli\SasApi\SasClientService;
use Acquia\Cli\SasApi\SourceConfig;
use Psr\Log\LoggerInterface;
use SelfUpdate\SelfUpdateManager;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Yaml\Yaml;
/**
* Push local Source configuration to a site via the Sites Aggregation Service.
*
* Reads every .yml file under .acquia/config/ in the current project and
* assembles them into a single YAML document keyed by config collection (the
* root directory is the default collection) and then by config name. This
* mirrors the structure produced by `drush source:config:dump --single-yaml`,
* which is what a future source:config:pull command writes out.
*/
#[RequireAuth]
#[AsCommand(name: 'source:config:push', description: 'Push Source configuration from .acquia/config to a site')]
final class ConfigPushCommand extends CommandBase
{
/**
* The directory (relative to the project root) holding config files.
*/
private const CONFIG_DIR = '.acquia/config';
public function __construct(
LocalMachineHelper $localMachineHelper,
CloudDataStore $datastoreCloud,
AcquiaCliDatastore $datastoreAcli,
ApiCredentialsInterface $cloudCredentials,
TelemetryHelper $telemetryHelper,
string $projectDir,
ClientService $cloudApiClientService,
SshHelper $sshHelper,
string $sshDir,
LoggerInterface $logger,
SelfUpdateManager $selfUpdateManager,
private readonly SasClientService $sasClient,
) {
parent::__construct(
$localMachineHelper,
$datastoreCloud,
$datastoreAcli,
$cloudCredentials,
$telemetryHelper,
$projectDir,
$cloudApiClientService,
$sshHelper,
$sshDir,
$logger,
$selfUpdateManager,
);
}
protected function configure(): void
{
$this
->acceptEnvironmentId()
->acceptSiteInstanceId()
->addOption('force', 'f', InputOption::VALUE_NONE, 'Do not ask for confirmation before pushing');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->setDirAndRequireProjectCwd($input);
$siteInstance = $this->determineSiteInstance($input);
if ($siteInstance === null) {
throw new AcquiaCliException(
'Could not determine a Source site instance. Run this command from a repository linked to an Acquia Cloud application, or pass --siteInstanceId.'
);
}
$environment = $siteInstance->environment;
$payload = $this->assemblePayload();
if ($payload === []) {
throw new AcquiaCliException(sprintf('No configuration files found in %s.', self::CONFIG_DIR));
}
$yaml = Yaml::dump($payload, 10, 2);
if (!$input->getOption('force')) {
$answer = $this->io->confirm(
sprintf('Push configuration from %s to the %s environment?', self::CONFIG_DIR, $environment->name),
false,
);
if (!$answer) {
return Command::SUCCESS;
}
}
$sourceConfig = new SourceConfig($this->sasClient->getClient());
$response = $sourceConfig->push($environment->uuid, $yaml);
// @todo DXBE-20: Confirm the operation ID field name with the SAS team.
$operationId = $response->id ?? null;
if (!is_string($operationId)) {
throw new AcquiaCliException('The SAS API response did not include an operation ID.');
}
$this->io->writeln(sprintf('Config push submitted (operation %s). Waiting for it to complete...', $operationId));
return $this->waitForPush($sourceConfig, $operationId) ? Command::SUCCESS : Command::FAILURE;
}
/**
* Assemble the payload from the config files on disk.
*
* Returns a structure keyed by collection name (the default collection is
* the empty string; subdirectories become dotted collection names like
* "language.es"), then by config name (the file name minus .yml).
* Collections with no config files are omitted.
*
* @return array<string, array<string, mixed>>
*/
private function assemblePayload(): array
{
$configDir = $this->dir . '/' . self::CONFIG_DIR;
if (!is_dir($configDir)) {
return [];
}
$finder = new Finder();
$finder->files()->in($configDir)->name('*.yml');
$payload = [];
foreach ($finder as $file) {
$relativeDir = $file->getRelativePath();
// The root directory maps to the default collection ("").
// Subdirectories map to dotted collection names: language/es
// becomes language.es.
$collection = $relativeDir === '' ? '' : str_replace('/', '.', $relativeDir);
$name = $file->getBasename('.yml');
$payload[$collection][$name] = Yaml::parseFile($file->getPathname());
}
return $payload;
}
/**
* Poll the operation until it leaves the in-progress states.
*
* @todo DXBE-20: Confirm the status field name and its values with the
* SAS team. Assumes a `status` field mirroring the task gateway's
* phases (pending/running/succeeded/failed).
*/
private function waitForPush(SourceConfig $sourceConfig, string $operationId): bool
{
$status = null;
$checkStatus = static function () use ($sourceConfig, $operationId, &$status): bool {
$response = $sourceConfig->getPushStatus($operationId);
$status = $response->status ?? 'unknown';
return !in_array($status, ['pending', 'running'], true);
};
$onDone = static function (): void {
};
LoopHelper::getLoopy($this->output, $this->io, 'Pushing configuration...', $checkStatus, $onDone);
if ($status === 'succeeded') {
$this->io->success('Configuration pushed successfully.');
return true;
}
$this->io->error(sprintf('Config push ended with status: %s', $status));
return false;
}
}