Skip to content

Commit b012917

Browse files
committed
feat: add cache:tunnel command
1 parent 17a11ca commit b012917

File tree

3 files changed

+112
-6
lines changed

3 files changed

+112
-6
lines changed

src/Command/Cache/AbstractCacheCommand.php

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,8 @@ abstract class AbstractCacheCommand extends AbstractCommand
2323
/**
2424
* Determine the cache that the command is interacting with.
2525
*/
26-
protected function determineCache(string $question, InputInterface $input, ConsoleOutput $output): int
26+
protected function determineCache(string $question, InputInterface $input, ConsoleOutput $output): array
2727
{
28-
$cache = null;
2928
$caches = $this->apiClient->getCaches($this->cliConfiguration->getActiveTeamId());
3029
$cacheIdOrName = $this->getStringArgument($input, 'cache');
3130

@@ -39,10 +38,10 @@ protected function determineCache(string $question, InputInterface $input, Conso
3938

4039
if (1 < $caches->where('name', $cacheIdOrName)->count()) {
4140
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'])) {
41+
} elseif (!is_array($cache) || empty($cache['id'])) {
4342
throw new RuntimeException(sprintf('Unable to find a cache cluster with "%s" as the ID or name', $cacheIdOrName));
4443
}
4544

46-
return (int) $cache['id'];
45+
return $cache;
4746
}
4847
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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\InputArgument;
18+
use Symfony\Component\Console\Input\InputInterface;
19+
use Symfony\Component\Filesystem\Filesystem;
20+
use Tightenco\Collect\Support\Arr;
21+
use Ymir\Cli\ApiClient;
22+
use Ymir\Cli\CliConfiguration;
23+
use Ymir\Cli\Command\Network\AddBastionHostCommand;
24+
use Ymir\Cli\Console\ConsoleOutput;
25+
use Ymir\Cli\ProjectConfiguration;
26+
27+
class CacheTunnelCommand extends AbstractCacheCommand
28+
{
29+
/**
30+
* The name of the command.
31+
*
32+
* @var string
33+
*/
34+
public const NAME = 'cache:tunnel';
35+
36+
/**
37+
* The file system.
38+
*
39+
* @var Filesystem
40+
*/
41+
private $filesystem;
42+
43+
/**
44+
* The path to the user's home directory.
45+
*
46+
* @var string
47+
*/
48+
private $homeDirectory;
49+
50+
/**
51+
* Constructor.
52+
*/
53+
public function __construct(ApiClient $apiClient, CliConfiguration $cliConfiguration, Filesystem $filesystem, string $homeDirectory, ProjectConfiguration $projectConfiguration)
54+
{
55+
parent::__construct($apiClient, $cliConfiguration, $projectConfiguration);
56+
57+
$this->filesystem = $filesystem;
58+
$this->homeDirectory = rtrim($homeDirectory, '/');
59+
}
60+
61+
/**
62+
* {@inheritdoc}
63+
*/
64+
protected function configure()
65+
{
66+
$this
67+
->setName(self::NAME)
68+
->setDescription('Create a SSH tunnel to a cache cluster')
69+
->addArgument('cache', InputArgument::OPTIONAL, 'The ID or name of the cache cluster to create a SSH tunnel to')
70+
->addArgument('port', InputArgument::OPTIONAL, 'The local port to use to connect to the cache cluster', '6378');
71+
}
72+
73+
/**
74+
* {@inheritdoc}
75+
*/
76+
protected function perform(InputInterface $input, ConsoleOutput $output)
77+
{
78+
$cache = $this->determineCache('Which cache cluster would you like to connect to', $input, $output);
79+
80+
if ('available' !== $cache['status']) {
81+
throw new RuntimeException(sprintf('The "%s" cache isn\'t available', $cache['name']));
82+
}
83+
84+
$network = $this->apiClient->getNetwork($cache['network']['id']);
85+
86+
if (!$network->has('bastion_host')) {
87+
throw new RuntimeException(sprintf('The cache network does\'t have a bastion host to connect to. You can add one to the network with the "%s" command.', AddBastionHostCommand::NAME));
88+
} elseif (!is_dir($this->homeDirectory.'/.ssh')) {
89+
$this->filesystem->mkdir($this->homeDirectory.'/.ssh', 0700);
90+
}
91+
92+
$localPort = $this->getNumericArgument($input, 'port');
93+
94+
if (6379 === $localPort) {
95+
throw new RuntimeException('Cannot use port 6379 as the local port for the SSH tunnel to the cache cluster');
96+
}
97+
98+
$privateKeyFilename = $this->homeDirectory.'/.ssh/ymir-cache-tunnel';
99+
100+
$this->filesystem->dumpFile($privateKeyFilename, Arr::get($network, 'bastion_host.private_key'));
101+
$this->filesystem->chmod($privateKeyFilename, 0600);
102+
103+
$output->writeln(sprintf('<info>Creating SSH tunnel to the </info> "<comment>%s</comment>" <info>database server. You can connect using: <comment>localhost:%s</comment>', $cache['name'], $localPort));
104+
105+
passthru(sprintf('ssh ec2-user@%s -i %s -o LogLevel=error -L %s:%s:6379 -N', Arr::get($network, 'bastion_host.endpoint'), $privateKeyFilename, $localPort, $cache['endpoint']));
106+
}
107+
}

src/Command/Cache/DeleteCacheCommand.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,13 @@ protected function configure()
4343
*/
4444
protected function perform(InputInterface $input, ConsoleOutput $output)
4545
{
46-
$cacheId = $this->determineCache('Which cache cluster would you like to delete', $input, $output);
46+
$cache = $this->determineCache('Which cache cluster would you like to delete', $input, $output);
4747

4848
if (!$output->confirm('Are you sure you want to delete this cache cluster?', false)) {
4949
return;
5050
}
5151

52-
$this->apiClient->deleteCache($cacheId);
52+
$this->apiClient->deleteCache($cache['id']);
5353

5454
$output->infoWithDelayWarning('Cache cluster deleted');
5555
$output->newLine();

0 commit comments

Comments
 (0)