Skip to content

Commit be3af4c

Browse files
committed
feat: add cache commands
1 parent 35d2c23 commit be3af4c

File tree

7 files changed

+331
-26
lines changed

7 files changed

+331
-26
lines changed

src/ApiClient.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,17 @@ public function changeSecret(int $projectId, string $environment, string $name,
112112
]);
113113
}
114114

115+
/**
116+
* Create a new cache on the given network.
117+
*/
118+
public function createCache(string $name, int $networkId, string $type): Collection
119+
{
120+
return $this->request('post', "/networks/{$networkId}/caches", [
121+
'name' => $name,
122+
'type' => $type,
123+
]);
124+
}
125+
115126
/**
116127
* Create a new SSL certificate.
117128
*/
@@ -271,6 +282,14 @@ public function createTeam(string $name): Collection
271282
]);
272283
}
273284

285+
/**
286+
* Delete the given cache.
287+
*/
288+
public function deleteCache(int $cacheId)
289+
{
290+
$this->request('delete', "/caches/{$cacheId}");
291+
}
292+
274293
/**
275294
* Delete the given SSL certificate.
276295
*/
@@ -420,6 +439,28 @@ public function getBastionHost(int $bastionHostId): Collection
420439
return $this->request('get', "/bastion-hosts/{$bastionHostId}");
421440
}
422441

442+
/**
443+
* Get the caches that belong to the given team.
444+
*/
445+
public function getCaches(int $teamId): Collection
446+
{
447+
return $this->request('get', "/teams/{$teamId}/caches");
448+
}
449+
450+
/**
451+
* Get the types of cache available on the given cloud provider.
452+
*/
453+
public function getCacheTypes(int $providerId): Collection
454+
{
455+
$types = $this->request('get', "/providers/{$providerId}/caches/types");
456+
457+
if ($types->isEmpty()) {
458+
throw new RuntimeException('The Ymir API failed to return information on the cache types');
459+
}
460+
461+
return $types;
462+
}
463+
423464
/**
424465
* Get the SSL certificates with the given ID.
425466
*/
@@ -694,6 +735,14 @@ public function getTeam($teamId): Collection
694735
return $this->request('get', '/teams/'.$teamId);
695736
}
696737

738+
/**
739+
* Get the caches that the team has access to.
740+
*/
741+
public function getTeamCaches($teamId): Collection
742+
{
743+
return $this->request('get', "/teams/{$teamId}/caches");
744+
}
745+
697746
/**
698747
* Get the database servers that the team has access to.
699748
*/

src/Command/AbstractCommand.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020
use Symfony\Component\Console\Input\ArrayInput;
2121
use Symfony\Component\Console\Input\InputInterface;
2222
use Symfony\Component\Console\Output\OutputInterface;
23+
use Tightenco\Collect\Support\Arr;
2324
use Ymir\Cli\ApiClient;
2425
use Ymir\Cli\CliConfiguration;
26+
use Ymir\Cli\Command\Network\CreateNetworkCommand;
2527
use Ymir\Cli\Command\Provider\ConnectProviderCommand;
2628
use Ymir\Cli\Console\ConsoleOutput;
2729
use Ymir\Cli\Exception\ApiClientException;
@@ -111,6 +113,28 @@ protected function determineNetwork(string $question, InputInterface $input, Con
111113
return (int) $network['id'];
112114
}
113115

116+
/**
117+
* Determine the network to use or create one otherwise.
118+
*/
119+
protected function determineOrCreateNetwork(string $question, InputInterface $input, ConsoleOutput $output)
120+
{
121+
$networks = $this->apiClient->getTeamNetworks($this->cliConfiguration->getActiveTeamId())->whereNotIn('status', ['deleting', 'failed']);
122+
123+
if ($networks->isEmpty() && !$output->confirm('Your team doesn\'t have any provisioned networks. Would you like to create one first? <fg=default>(Answering "<comment>no</comment>" will cancel the command.)</>')) {
124+
throw new CommandCancelledException();
125+
}
126+
127+
if ($networks->isEmpty()) {
128+
$this->retryApi(function () use ($output) {
129+
$this->invoke($output, CreateNetworkCommand::NAME);
130+
}, 'Do you want to try creating a network again?', $output);
131+
132+
return (int) Arr::get($this->apiClient->getTeamNetworks($this->cliConfiguration->getActiveTeamId())->last(), 'id');
133+
}
134+
135+
return $this->determineNetwork($question, $input, $output);
136+
}
137+
114138
/**
115139
* Determine the cloud provider region to use.
116140
*/
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of Ymir command-line tool.
7+
*
8+
* (c) Carl Alexander <[email protected]>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Ymir\Cli\Command\Cache;
15+
16+
use Symfony\Component\Console\Exception\RuntimeException;
17+
use Symfony\Component\Console\Input\InputInterface;
18+
use Ymir\Cli\Command\AbstractCommand;
19+
use Ymir\Cli\Console\ConsoleOutput;
20+
21+
abstract class AbstractCacheCommand extends AbstractCommand
22+
{
23+
/**
24+
* Determine the cache that the command is interacting with.
25+
*/
26+
protected function determineCache(string $question, InputInterface $input, ConsoleOutput $output): int
27+
{
28+
$cache = null;
29+
$caches = $this->apiClient->getCaches($this->cliConfiguration->getActiveTeamId());
30+
$cacheIdOrName = $this->getStringArgument($input, 'cache');
31+
32+
if ($caches->isEmpty()) {
33+
throw new RuntimeException(sprintf('The currently active team has no cache clusters. You can create one with the "%s" command.', CreateCacheCommand::NAME));
34+
} elseif (empty($cacheIdOrName)) {
35+
$cacheIdOrName = $output->choiceWithResourceDetails($question, $caches);
36+
}
37+
38+
$cache = $caches->firstWhere('id', $cacheIdOrName) ?? $caches->firstWhere('name', $cacheIdOrName);
39+
40+
if (1 < $caches->where('name', $cacheIdOrName)->count()) {
41+
throw new RuntimeException(sprintf('Unable to select a cache cluster because more than one cache cluster has the name "%s"', $cacheIdOrName));
42+
} elseif (empty($cache['id'])) {
43+
throw new RuntimeException(sprintf('Unable to find a cache cluster with "%s" as the ID or name', $cacheIdOrName));
44+
}
45+
46+
return (int) $cache['id'];
47+
}
48+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of Ymir command-line tool.
7+
*
8+
* (c) Carl Alexander <[email protected]>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Ymir\Cli\Command\Cache;
15+
16+
use Symfony\Component\Console\Exception\InvalidArgumentException;
17+
use Symfony\Component\Console\Exception\RuntimeException;
18+
use Symfony\Component\Console\Input\InputArgument;
19+
use Symfony\Component\Console\Input\InputInterface;
20+
use Symfony\Component\Console\Input\InputOption;
21+
use Tightenco\Collect\Support\Collection;
22+
use Ymir\Cli\Command\AbstractCommand;
23+
use Ymir\Cli\Console\ConsoleOutput;
24+
use Ymir\Cli\Exception\CommandCancelledException;
25+
26+
class CreateCacheCommand extends AbstractCommand
27+
{
28+
/**
29+
* The name of the command.
30+
*
31+
* @var string
32+
*/
33+
public const NAME = 'cache:create';
34+
35+
/**
36+
* {@inheritdoc}
37+
*/
38+
protected function configure()
39+
{
40+
$this
41+
->setName(self::NAME)
42+
->setDescription('Create a new cache cluster')
43+
->addArgument('name', InputArgument::OPTIONAL, 'The name of the cache cluster')
44+
->addOption('network', null, InputOption::VALUE_REQUIRED, 'The ID or name of the network on which the cache cluster will be created')
45+
->addOption('type', null, InputOption::VALUE_REQUIRED, 'The cache cluster type to create on the cloud provider');
46+
}
47+
48+
/**
49+
* {@inheritdoc}
50+
*/
51+
protected function perform(InputInterface $input, ConsoleOutput $output)
52+
{
53+
$name = $this->getStringArgument($input, 'name');
54+
55+
if (empty($name) && $input->isInteractive()) {
56+
$name = $output->askSlug('What is the name of the cache cluster');
57+
}
58+
59+
$network = $this->apiClient->getNetwork($this->determineOrCreateNetwork('On what network should the cache cluster be created?', $input, $output));
60+
61+
if (!$network->get('has_nat_gateway') && !$output->confirm('A cache cluster will require Ymir to add a NAT gateway to your network (~$32/month). Would you like to proceed?')) {
62+
throw new CommandCancelledException();
63+
}
64+
65+
$type = $this->determineType($network, $input, $output);
66+
67+
$this->apiClient->createCache($name, (int) $network->get('id'), $type);
68+
69+
$output->infoWithDelayWarning('Cache cluster created');
70+
}
71+
72+
/**
73+
* Determine the cache cluster type to create.
74+
*/
75+
private function determineType(Collection $network, InputInterface $input, ConsoleOutput $output): string
76+
{
77+
if (!isset($network['provider']['id'])) {
78+
throw new RuntimeException('The Ymir API failed to return information on the cloud provider');
79+
}
80+
81+
$type = $this->getStringOption($input, 'type');
82+
$types = $this->apiClient->getCacheTypes((int) $network['provider']['id']);
83+
84+
if (null !== $type && !$types->has($type)) {
85+
throw new InvalidArgumentException(sprintf('The type "%s" isn\'t a valid cache cluster type', $type));
86+
} elseif (null === $type) {
87+
$type = (string) $output->choice('What should the cache cluster type be?', $types->all());
88+
}
89+
90+
return $type;
91+
}
92+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of Ymir command-line tool.
7+
*
8+
* (c) Carl Alexander <[email protected]>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Ymir\Cli\Command\Cache;
15+
16+
use Symfony\Component\Console\Input\InputArgument;
17+
use Symfony\Component\Console\Input\InputInterface;
18+
use Ymir\Cli\Command\Network\RemoveNatGatewayCommand;
19+
use Ymir\Cli\Console\ConsoleOutput;
20+
21+
class DeleteCacheCommand extends AbstractCacheCommand
22+
{
23+
/**
24+
* The name of the command.
25+
*
26+
* @var string
27+
*/
28+
public const NAME = 'cache:delete';
29+
30+
/**
31+
* {@inheritdoc}
32+
*/
33+
protected function configure()
34+
{
35+
$this
36+
->setName(self::NAME)
37+
->setDescription('Delete a cache cluster')
38+
->addArgument('cache', InputArgument::OPTIONAL, 'The ID or name of the cache cluster to delete');
39+
}
40+
41+
/**
42+
* {@inheritdoc}
43+
*/
44+
protected function perform(InputInterface $input, ConsoleOutput $output)
45+
{
46+
$cacheId = $this->determineCache('Which cache cluster would you like to delete', $input, $output);
47+
48+
if (!$output->confirm('Are you sure you want to delete this cache cluster?', false)) {
49+
return;
50+
}
51+
52+
$this->apiClient->deleteCache($cacheId);
53+
54+
$output->infoWithDelayWarning('Cache cluster deleted');
55+
$output->newLine();
56+
$output->warn(sprintf('If you have no other resources using the private subnet, you should remove the network\'s NAT gateway using the "%s" command', RemoveNatGatewayCommand::NAME));
57+
}
58+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of Ymir command-line tool.
7+
*
8+
* (c) Carl Alexander <[email protected]>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Ymir\Cli\Command\Cache;
15+
16+
use Symfony\Component\Console\Input\InputInterface;
17+
use Ymir\Cli\Command\AbstractCommand;
18+
use Ymir\Cli\Console\ConsoleOutput;
19+
20+
class ListCachesCommand extends AbstractCommand
21+
{
22+
/**
23+
* The name of the command.
24+
*
25+
* @var string
26+
*/
27+
public const NAME = 'cache:list';
28+
29+
/**
30+
* {@inheritdoc}
31+
*/
32+
protected function configure()
33+
{
34+
$this
35+
->setName(self::NAME)
36+
->setDescription('List all the cache clusters that the current team has access to');
37+
}
38+
39+
/**
40+
* {@inheritdoc}
41+
*/
42+
protected function perform(InputInterface $input, ConsoleOutput $output)
43+
{
44+
$output->table(
45+
['Id', 'Name', 'Provider', 'Network', 'Region', 'Status', 'Type'],
46+
$this->apiClient->getTeamCaches($this->cliConfiguration->getActiveTeamId())->map(function (array $cache) use ($output) {
47+
return [
48+
$cache['id'],
49+
$cache['name'],
50+
$cache['network']['provider']['name'],
51+
$cache['network']['name'],
52+
$cache['region'],
53+
$output->formatStatus($cache['status']),
54+
$cache['type'],
55+
];
56+
})->all()
57+
);
58+
}
59+
}

0 commit comments

Comments
 (0)