Skip to content

Commit 3f2dc47

Browse files
committed
Improve onboarding experience for new users
- Auto-redirect `keep` (no args) to `init` when uninitialized - Add isAvailable() checks on vault drivers for missing AWS SDK - Show actionable KeepException when no vaults configured - Pre-check AWS credentials via STS before running permission matrix
1 parent 004ef28 commit 3f2dc47

7 files changed

Lines changed: 91 additions & 11 deletions

File tree

src/Commands/Concerns/ConfiguresVaults.php

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,27 @@ trait ConfiguresVaults
1818
{
1919
protected function configureNewVault(): ?array
2020
{
21-
// Get available vault classes and build options
2221
$availableVaults = Keep::getAvailableVaults();
2322
$driverOptions = [];
2423
$vaultClassMap = [];
2524

2625
foreach ($availableVaults as $vaultClass) {
26+
if (!$vaultClass::isAvailable()) {
27+
continue;
28+
}
2729
$driver = $vaultClass::DRIVER;
2830
$name = $vaultClass::NAME;
2931
$driverOptions[$driver] = $name;
3032
$vaultClassMap[$driver] = $vaultClass;
3133
}
3234

35+
if (empty($driverOptions)) {
36+
error('No vault drivers are available. The AWS SDK is required.');
37+
info('Install it with: composer require aws/aws-sdk-php');
38+
39+
return null;
40+
}
41+
3342
$selectedDriver = select(
3443
label: 'Which vault driver would you like to use?',
3544
options: $driverOptions
@@ -79,7 +88,6 @@ protected function configureNewVault(): ?array
7988
info("{$friendlyName} vault '{$slug}' configured successfully");
8089

8190
// Reload vault configurations to include the newly saved vault
82-
// This ensures the permission tester can find and update the vault
8391
$container = \STS\Keep\KeepContainer::getInstance();
8492
$container->instance(
8593
\STS\Keep\KeepManager::class,
@@ -88,12 +96,24 @@ protected function configureNewVault(): ?array
8896
\STS\Keep\Data\Collections\VaultConfigCollection::load()
8997
)
9098
);
91-
92-
// Run verify to check and cache permissions for all environments
99+
100+
$region = $vaultConfig['region'] ?? 'us-east-1';
101+
if (!$this->checkAwsCredentials($region)) {
102+
info('');
103+
error('AWS credentials not found or invalid.');
104+
info('Configure credentials via:');
105+
info(' - AWS CLI: aws configure');
106+
info(' - Environment variables: AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY');
107+
info(' - Instance profile (on EC2/ECS/Lambda)');
108+
info('');
109+
info('Skipping permission verification. Run "keep verify" once credentials are configured.');
110+
111+
return ['slug' => $slug, 'config' => $vaultConfig];
112+
}
113+
93114
info("\nVerifying vault permissions...");
94115
$collection = $this->testVaultAcrossEnvs($slug);
95-
96-
// Display summary of permissions
116+
97117
foreach ($collection->groupByEnv() as $env => $permissions) {
98118
$permission = $permissions->first();
99119
$permString = empty($permission->permissions()) ? 'no permissions' : implode(', ', $permission->permissions());
@@ -129,6 +149,26 @@ protected function testVaultAcrossEnvs(string $vaultName): PermissionsCollection
129149
return $collection;
130150
}
131151

152+
protected function checkAwsCredentials(string $region): bool
153+
{
154+
if (!class_exists(\Aws\Sts\StsClient::class)) {
155+
return false;
156+
}
157+
158+
try {
159+
$sts = new \Aws\Sts\StsClient([
160+
'version' => 'latest',
161+
'region' => $region,
162+
'use_aws_shared_config_files' => true,
163+
]);
164+
$sts->getCallerIdentity();
165+
166+
return true;
167+
} catch (\Exception) {
168+
return false;
169+
}
170+
}
171+
132172
private function generateUniqueSlug(string $driver): string
133173
{
134174
$existingVaults = Keep::getConfiguredVaults();

src/Commands/Concerns/GathersInput.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ protected function vaultName($prompt = 'Vault', $cacheName = 'vaultName'): strin
6666
{
6767
return $this->{$cacheName} ??= match (true) {
6868
$this->hasOption('vault') && (bool) $this->option('vault') => $this->option('vault'),
69-
Keep::getConfiguredVaults()->count() === 0 => throw new \RuntimeException('No vaults are configured. Please add a vault first with: keep vault:add.'),
69+
Keep::getConfiguredVaults()->count() === 0 => throw (new KeepException('No vaults are configured.'))->withContext(['suggestion' => 'Add your first vault with: keep vault:add']),
7070
Keep::getConfiguredVaults()->count() === 1 => Keep::getConfiguredVaults()->first()->slug(),
7171
default => select($prompt, Keep::getConfiguredVaults()->keys()->toArray(), Keep::getDefaultVault()),
7272
};

src/Commands/ShellCommand.php

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,39 @@
22

33
namespace STS\Keep\Commands;
44

5+
use STS\Keep\Facades\Keep;
56
use STS\Keep\Shell\SimpleShell;
67
use STS\Keep\Shell\ShellContext;
78

89
class ShellCommand extends BaseCommand
910
{
10-
protected $signature = 'shell
11+
protected $signature = 'shell
1112
{--env= : Initial environment to use}
1213
{--vault= : Initial vault to use}';
13-
14+
1415
protected $description = 'Start an interactive shell for Keep commands';
15-
16+
17+
protected function requiresInitialization(): bool
18+
{
19+
return false;
20+
}
21+
1622
public function process()
1723
{
18-
// Check if readline is available
24+
if (!Keep::isInitialized()) {
25+
$this->line('');
26+
$this->line('<info>Welcome to Keep!</info> Secret management for your team.');
27+
$this->line('');
28+
29+
$this->call('init');
30+
31+
if (!Keep::isInitialized()) {
32+
return self::SUCCESS;
33+
}
34+
35+
$this->line('');
36+
}
37+
1938
if (!function_exists('readline')) {
2039
$this->error('The readline extension is required to run the Keep shell.');
2140
$this->line('Please install the readline PHP extension.');

src/KeepManager.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,12 @@ public function vault(string $name, string $env): AbstractVault
110110
throw new \InvalidArgumentException("Vault driver '{$driver}' for '{$name}' is not available.");
111111
}
112112

113+
if (!$driverClass::isAvailable()) {
114+
throw new \RuntimeException(
115+
"Vault driver '{$driver}' requires the AWS SDK. Install it with: composer require aws/aws-sdk-php"
116+
);
117+
}
118+
113119
$vault = new $driverClass($name, $config->toArray(), $env);
114120
$this->loadedVaults[$cacheKey] = $vault;
115121

src/Vaults/AbstractVault.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ abstract class AbstractVault
1616

1717
public const string NAME = '';
1818

19+
public static function isAvailable(): bool
20+
{
21+
return true;
22+
}
23+
1924
public function __construct(protected string $name, protected array $config, protected string $env) {}
2025

2126
public function name(): string

src/Vaults/AwsSecretsManagerVault.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ class AwsSecretsManagerVault extends AbstractVault
2626

2727
protected SecretsManagerClient $client;
2828

29+
public static function isAvailable(): bool
30+
{
31+
return class_exists(SecretsManagerClient::class);
32+
}
33+
2934
public static function configure(array $existingSettings = []): array
3035
{
3136
return [

src/Vaults/AwsSsmVault.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ class AwsSsmVault extends AbstractVault
2828

2929
protected SsmClient $client;
3030

31+
public static function isAvailable(): bool
32+
{
33+
return class_exists(SsmClient::class);
34+
}
35+
3136
public static function configure(array $existingSettings = []): array
3237
{
3338
return [

0 commit comments

Comments
 (0)