Skip to content

Commit 35d2c23

Browse files
committed
feat: add bastion host commands
1 parent 7543f75 commit 35d2c23

3 files changed

Lines changed: 193 additions & 0 deletions

File tree

src/ApiClient.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ public function __construct(string $baseUrl, ClientInterface $client, CliConfigu
5252
$this->cliConfiguration = $cliConfiguration;
5353
}
5454

55+
/**
56+
* Add a bastion host to the given network.
57+
*/
58+
public function addBastionHost(int $networkId): Collection
59+
{
60+
return $this->request('post', "/networks/{$networkId}/bastion-host");
61+
}
62+
5563
/**
5664
* Add a NAT gateway to the given network.
5765
*/
@@ -404,6 +412,14 @@ public function getArtifactUploadUrl(int $deploymentId): string
404412
return $uploadUrl;
405413
}
406414

415+
/**
416+
* Get the bastion host with the given ID.
417+
*/
418+
public function getBastionHost(int $bastionHostId): Collection
419+
{
420+
return $this->request('get', "/bastion-hosts/{$bastionHostId}");
421+
}
422+
407423
/**
408424
* Get the SSL certificates with the given ID.
409425
*/
@@ -748,6 +764,14 @@ public function isAuthenticated(): bool
748764
}
749765
}
750766

767+
/**
768+
* Remove the bastion host from the given network.
769+
*/
770+
public function removeBastionHost(int $networkId)
771+
{
772+
$this->request('delete', "/networks/{$networkId}/bastion-host");
773+
}
774+
751775
/**
752776
* Remove the NAT gateway from the given network.
753777
*/
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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 <support@ymirapp.com>
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\Network;
15+
16+
use Symfony\Component\Console\Input\InputArgument;
17+
use Symfony\Component\Console\Input\InputInterface;
18+
use Symfony\Component\Filesystem\Filesystem;
19+
use Ymir\Cli\ApiClient;
20+
use Ymir\Cli\CliConfiguration;
21+
use Ymir\Cli\Command\AbstractCommand;
22+
use Ymir\Cli\Console\ConsoleOutput;
23+
use Ymir\Cli\ProjectConfiguration;
24+
25+
class AddBastionHostCommand extends AbstractCommand
26+
{
27+
/**
28+
* The name of the command.
29+
*
30+
* @var string
31+
*/
32+
public const NAME = 'network:bastion:add';
33+
34+
/**
35+
* The file system.
36+
*
37+
* @var Filesystem
38+
*/
39+
private $filesystem;
40+
41+
/**
42+
* The path to the user's home directory.
43+
*
44+
* @var string
45+
*/
46+
private $homeDirectory;
47+
48+
/**
49+
* Constructor.
50+
*/
51+
public function __construct(ApiClient $apiClient, CliConfiguration $cliConfiguration, Filesystem $filesystem, string $homeDirectory, ProjectConfiguration $projectConfiguration)
52+
{
53+
parent::__construct($apiClient, $cliConfiguration, $projectConfiguration);
54+
55+
$this->filesystem = $filesystem;
56+
$this->homeDirectory = rtrim($homeDirectory, '/');
57+
}
58+
59+
/**
60+
* {@inheritdoc}
61+
*/
62+
protected function configure()
63+
{
64+
$this
65+
->setName(self::NAME)
66+
->setDescription('Add a bastion host to the network')
67+
->addArgument('network', InputArgument::OPTIONAL, 'The ID or name of the network to add a bastion host to');
68+
}
69+
70+
/**
71+
* {@inheritdoc}
72+
*/
73+
protected function perform(InputInterface $input, ConsoleOutput $output)
74+
{
75+
$network = $this->apiClient->getNetwork($this->determineNetwork('Which network would like to add a bastion host to', $input, $output));
76+
77+
$bastionHost = $this->apiClient->addBastionHost((int) $network->get('id'));
78+
79+
$output->infoWithDelayWarning('Bastion host added');
80+
$output->newLine();
81+
$output->warn('SSH private key:');
82+
$output->newLine();
83+
$output->writeln($bastionHost['private_key']);
84+
85+
if (!is_dir($this->homeDirectory.'/.ssh') || !$output->confirm('Would you like to create the SSH private key in your ~/.ssh directory?')) {
86+
return;
87+
}
88+
89+
$privateKeyFilename = $this->homeDirectory.'/.ssh/ymir-'.$network->get('name');
90+
91+
if ($this->filesystem->exists($privateKeyFilename) && !$output->confirm('A SSH key already exists for this network. Do you want to overwrite it?', false)) {
92+
return;
93+
}
94+
95+
$this->filesystem->dumpFile($privateKeyFilename, $bastionHost->get('private_key'));
96+
$this->filesystem->chmod($privateKeyFilename, 0600);
97+
98+
$output->infoWithValue('SSH private key created', $privateKeyFilename);
99+
100+
$sshConfigFilename = $this->homeDirectory.'/.ssh/config';
101+
102+
if (!$this->filesystem->exists($sshConfigFilename) || !$output->confirm('Would you like to configure SSH to connect to the bastion host?')) {
103+
return;
104+
}
105+
106+
$output->infoWithWarning('Waiting for bastion host to get assigned a public domain name', 'takes a few minutes');
107+
108+
$bastionHost = $this->wait(function () use ($bastionHost) {
109+
$bastionHost = $this->apiClient->getBastionHost((int) $bastionHost->get('id'));
110+
111+
return 'available' === $bastionHost->get('status') ? $bastionHost : [];
112+
}, 20, 15);
113+
114+
$this->filesystem->appendToFile($sshConfigFilename, sprintf("\nHost %s\n User ec2-user\n IdentitiesOnly yes\n IdentityFile %s\n", $bastionHost->get('endpoint'), $privateKeyFilename));
115+
116+
$output->newLine();
117+
$output->infoWithValue('SSH configured. Login using', sprintf('ssh %s', $bastionHost->get('endpoint')));
118+
}
119+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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 <support@ymirapp.com>
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\Network;
15+
16+
use Symfony\Component\Console\Input\InputArgument;
17+
use Symfony\Component\Console\Input\InputInterface;
18+
use Ymir\Cli\Command\AbstractCommand;
19+
use Ymir\Cli\Console\ConsoleOutput;
20+
21+
class RemoveBastionHostCommand extends AbstractCommand
22+
{
23+
/**
24+
* The name of the command.
25+
*
26+
* @var string
27+
*/
28+
public const NAME = 'network:bastion:remove';
29+
30+
/**
31+
* {@inheritdoc}
32+
*/
33+
protected function configure()
34+
{
35+
$this
36+
->setName(self::NAME)
37+
->setDescription('Remove bastion host from the network')
38+
->addArgument('network', InputArgument::OPTIONAL, 'The ID or name of the network to remove the bastion host from');
39+
}
40+
41+
/**
42+
* {@inheritdoc}
43+
*/
44+
protected function perform(InputInterface $input, ConsoleOutput $output)
45+
{
46+
$this->apiClient->removeBastionHost($this->determineNetwork('Which network would like to remove the bastion host from', $input, $output));
47+
48+
$output->infoWithDelayWarning('Bastion host removed');
49+
}
50+
}

0 commit comments

Comments
 (0)