|
| 1 | +<?php |
| 2 | + |
| 3 | +namespace Platformsh\Cli\Command\Resources; |
| 4 | + |
| 5 | +use Doctrine\Common\Cache\CacheProvider; |
| 6 | +use Platformsh\Cli\Command\CommandBase; |
| 7 | +use Platformsh\Cli\Console\ArrayArgument; |
| 8 | +use Platformsh\Cli\Console\ProgressMessage; |
| 9 | +use Platformsh\Cli\Util\Wildcard; |
| 10 | +use Platformsh\Client\Exception\EnvironmentStateException; |
| 11 | +use Platformsh\Client\Model\Deployment\EnvironmentDeployment; |
| 12 | +use Platformsh\Client\Model\Deployment\Service; |
| 13 | +use Platformsh\Client\Model\Deployment\WebApp; |
| 14 | +use Platformsh\Client\Model\Deployment\Worker; |
| 15 | +use Platformsh\Client\Model\Environment; |
| 16 | +use Platformsh\Client\Model\Project; |
| 17 | +use Symfony\Component\Console\Input\InputInterface; |
| 18 | + |
| 19 | +class ResourcesCommandBase extends CommandBase |
| 20 | +{ |
| 21 | + private static $cachedNextDeployment = []; |
| 22 | + |
| 23 | + public function isHidden() |
| 24 | + { |
| 25 | + return !$this->config()->get('api.sizing') || parent::isHidden(); |
| 26 | + } |
| 27 | + |
| 28 | + /** |
| 29 | + * Lists services in a deployment. |
| 30 | + * |
| 31 | + * @param EnvironmentDeployment $deployment |
| 32 | + * |
| 33 | + * @return array<string, WebApp||Worker|Service> |
| 34 | + * An array of services keyed by the service name. |
| 35 | + */ |
| 36 | + protected function allServices(EnvironmentDeployment $deployment) |
| 37 | + { |
| 38 | + $webapps = $deployment->webapps; |
| 39 | + $workers = $deployment->workers; |
| 40 | + $services = $deployment->services; |
| 41 | + ksort($webapps, SORT_STRING|SORT_FLAG_CASE); |
| 42 | + ksort($workers, SORT_STRING|SORT_FLAG_CASE); |
| 43 | + ksort($services, SORT_STRING|SORT_FLAG_CASE); |
| 44 | + return array_merge($webapps, $workers, $services); |
| 45 | + } |
| 46 | + |
| 47 | + /** |
| 48 | + * Checks whether a service needs a persistent disk. |
| 49 | + * |
| 50 | + * @todo replace this when the API has support for finding which services need a disk property |
| 51 | + * |
| 52 | + * @param WebApp|Service|Worker $service |
| 53 | + * @return bool |
| 54 | + */ |
| 55 | + protected function needsDisk($service) |
| 56 | + { |
| 57 | + if ($service instanceof Worker) { |
| 58 | + // Workers use the disk of their parent app. |
| 59 | + return false; |
| 60 | + } |
| 61 | + $diskless = ['chrome_headless', 'memcached', 'redis']; |
| 62 | + if ($service instanceof Service && in_array($service->type, $diskless)) { |
| 63 | + return false; |
| 64 | + } |
| 65 | + return !empty($service->disk) || ($service instanceof Service || !empty($service->mounts)); |
| 66 | + } |
| 67 | + |
| 68 | + /** |
| 69 | + * Loads the next environment deployment and caches it statically. |
| 70 | + * |
| 71 | + * The static cache means it can be reused while running a sub-command. |
| 72 | + * |
| 73 | + * @param Environment $environment |
| 74 | + * @param bool $reset |
| 75 | + * @return EnvironmentDeployment |
| 76 | + */ |
| 77 | + protected function loadNextDeployment(Environment $environment, $reset = false) |
| 78 | + { |
| 79 | + $cacheKey = $environment->project . ':' . $environment->id; |
| 80 | + if (isset(self::$cachedNextDeployment[$cacheKey]) && !$reset) { |
| 81 | + return self::$cachedNextDeployment[$cacheKey]; |
| 82 | + } |
| 83 | + $progress = new ProgressMessage($this->stdErr); |
| 84 | + try { |
| 85 | + $progress->show('Loading deployment information...'); |
| 86 | + $next = $environment->getNextDeployment(); |
| 87 | + if (!$next) { |
| 88 | + throw new EnvironmentStateException('No next deployment found', $environment); |
| 89 | + } |
| 90 | + } finally { |
| 91 | + $progress->done(); |
| 92 | + } |
| 93 | + return self::$cachedNextDeployment[$cacheKey] = $next; |
| 94 | + } |
| 95 | + |
| 96 | + /** |
| 97 | + * Checks if a project supports the Flexible Resources API, AKA Sizing API. |
| 98 | + * |
| 99 | + * @param Project $project |
| 100 | + * @param EnvironmentDeployment|null $deployment |
| 101 | + * @return bool |
| 102 | + */ |
| 103 | + protected function supportsSizingApi(Project $project, EnvironmentDeployment $deployment = null) |
| 104 | + { |
| 105 | + if (isset($deployment->project_info['settings'])) { |
| 106 | + return !empty($deployment->project_info['settings']['sizing_api_enabled']); |
| 107 | + } |
| 108 | + /** @var CacheProvider $cacheService */ |
| 109 | + $cacheService = $this->getService('cache'); |
| 110 | + $cacheKey = 'project-settings:' . $project->id; |
| 111 | + $cachedSettings = $cacheService->fetch($cacheKey); |
| 112 | + if (!empty($cachedSettings['sizing_api_enabled'])) { |
| 113 | + return true; |
| 114 | + } |
| 115 | + $httpClient = $this->api()->getHttpClient(); |
| 116 | + $settings = $httpClient->get($project->getUri() . '/settings')->json(); |
| 117 | + $cacheService->save($cacheKey, $settings, $this->config()->get('api.projects_ttl')); |
| 118 | + return !empty($settings['sizing_api_enabled']); |
| 119 | + } |
| 120 | + |
| 121 | + /** |
| 122 | + * Filters a list of services according to the --service or --type options. |
| 123 | + * |
| 124 | + * @param array<string, WebApp|Service|Worker> $services |
| 125 | + * @param InputInterface $input |
| 126 | + * |
| 127 | + * @return WebApp[]|Service[]|Worker[]|false |
| 128 | + * False on error, or an array of services. |
| 129 | + */ |
| 130 | + protected function filterServices($services, InputInterface $input) |
| 131 | + { |
| 132 | + $selectedNames = []; |
| 133 | + |
| 134 | + $requestedServices = ArrayArgument::getOption($input, 'service'); |
| 135 | + if (!empty($requestedServices)) { |
| 136 | + $selectedNames = Wildcard::select(array_keys($services), $requestedServices); |
| 137 | + if (!$selectedNames) { |
| 138 | + $this->stdErr->writeln('No services were found matching the name(s): <error>' . implode('</error>, <error>', $requestedServices) . '</error>'); |
| 139 | + return false; |
| 140 | + } |
| 141 | + $services = array_intersect_key($services, array_flip($selectedNames)); |
| 142 | + } |
| 143 | + $requestedApps = ArrayArgument::getOption($input, 'app'); |
| 144 | + if (!empty($requestedApps)) { |
| 145 | + $selectedNames = Wildcard::select(array_keys(array_filter($services, function ($s) { return $s instanceof WebApp; })), $requestedApps); |
| 146 | + if (!$selectedNames) { |
| 147 | + $this->stdErr->writeln('No applications were found matching the name(s): <error>' . implode('</error>, <error>', $requestedApps) . '</error>'); |
| 148 | + return false; |
| 149 | + } |
| 150 | + $services = array_intersect_key($services, array_flip($selectedNames)); |
| 151 | + } |
| 152 | + $requestedWorkers = ArrayArgument::getOption($input, 'worker'); |
| 153 | + if (!empty($requestedWorkers)) { |
| 154 | + $selectedNames = Wildcard::select(array_keys(array_filter($services, function ($s) { return $s instanceof Worker; })), $requestedWorkers); |
| 155 | + if (!$selectedNames) { |
| 156 | + $this->stdErr->writeln('No workers were found matching the name(s): <error>' . implode('</error>, <error>', $requestedWorkers) . '</error>'); |
| 157 | + return false; |
| 158 | + } |
| 159 | + $services = array_intersect_key($services, array_flip($selectedNames)); |
| 160 | + } |
| 161 | + |
| 162 | + if ($input->hasOption('type') && ($requestedTypes = ArrayArgument::getOption($input, 'type'))) { |
| 163 | + $byType = []; |
| 164 | + foreach ($services as $name => $service) { |
| 165 | + $type = $service->type; |
| 166 | + list($prefix) = explode(':', $service->type, 2); |
| 167 | + $byType[$type][] = $name; |
| 168 | + $byType[$prefix][] = $name; |
| 169 | + } |
| 170 | + $selectedTypes = Wildcard::select(array_keys($byType), $requestedTypes); |
| 171 | + if (!$selectedTypes) { |
| 172 | + $this->stdErr->writeln('No services were found matching the type(s): <error>' . implode('</error>, <error>', $requestedTypes) . '</error>'); |
| 173 | + return false; |
| 174 | + } |
| 175 | + foreach ($selectedTypes as $selectedType) { |
| 176 | + $selectedNames = array_merge($selectedNames, $byType[$selectedType]); |
| 177 | + } |
| 178 | + $services = array_intersect_key($services, array_flip($selectedNames)); |
| 179 | + } |
| 180 | + |
| 181 | + return $services; |
| 182 | + } |
| 183 | + |
| 184 | + /** |
| 185 | + * Returns container profile size info, given service properties. |
| 186 | + * |
| 187 | + * @param array $properties |
| 188 | + * The service properties (e.g. from $service->getProperties()). |
| 189 | + * @param array $containerProfiles |
| 190 | + * The list of container profiles (e.g. from |
| 191 | + * $deployment->container_profiles). |
| 192 | + * |
| 193 | + * @return array{'cpu': string, 'memory': string}|null |
| 194 | + */ |
| 195 | + protected function sizeInfo(array $properties, array $containerProfiles) |
| 196 | + { |
| 197 | + if (isset($properties['resources']['profile_size'])) { |
| 198 | + $size = $properties['resources']['profile_size']; |
| 199 | + $profile = $properties['container_profile']; |
| 200 | + if (isset($containerProfiles[$profile][$size])) { |
| 201 | + return $containerProfiles[$profile][$size]; |
| 202 | + } |
| 203 | + } |
| 204 | + return null; |
| 205 | + } |
| 206 | +} |
0 commit comments