feat(EnvironmentVariable): implement protection for critical environment variables and enhance deletion logic
This commit is contained in:
@@ -3,10 +3,13 @@
|
||||
namespace App\Livewire\Project\Shared\EnvironmentVariable;
|
||||
|
||||
use App\Models\EnvironmentVariable;
|
||||
use App\Traits\EnvironmentVariableProtection;
|
||||
use Livewire\Component;
|
||||
|
||||
class All extends Component
|
||||
{
|
||||
use EnvironmentVariableProtection;
|
||||
|
||||
public $resource;
|
||||
|
||||
public string $resourceClass;
|
||||
@@ -138,17 +141,57 @@ class All extends Component
|
||||
private function handleBulkSubmit()
|
||||
{
|
||||
$variables = parseEnvFormatToArray($this->variables);
|
||||
$changesMade = false;
|
||||
$errorOccurred = false;
|
||||
|
||||
$this->deleteRemovedVariables(false, $variables);
|
||||
$this->updateOrCreateVariables(false, $variables);
|
||||
// Try to delete removed variables
|
||||
$deletedCount = $this->deleteRemovedVariables(false, $variables);
|
||||
if ($deletedCount > 0) {
|
||||
$changesMade = true;
|
||||
} elseif ($deletedCount === 0 && $this->resource->environment_variables()->whereNotIn('key', array_keys($variables))->exists()) {
|
||||
// If we tried to delete but couldn't (due to Docker Compose), mark as error
|
||||
$errorOccurred = true;
|
||||
}
|
||||
|
||||
// Update or create variables
|
||||
$updatedCount = $this->updateOrCreateVariables(false, $variables);
|
||||
if ($updatedCount > 0) {
|
||||
$changesMade = true;
|
||||
}
|
||||
|
||||
if ($this->showPreview) {
|
||||
$previewVariables = parseEnvFormatToArray($this->variablesPreview);
|
||||
$this->deleteRemovedVariables(true, $previewVariables);
|
||||
$this->updateOrCreateVariables(true, $previewVariables);
|
||||
|
||||
// Try to delete removed preview variables
|
||||
$deletedPreviewCount = $this->deleteRemovedVariables(true, $previewVariables);
|
||||
if ($deletedPreviewCount > 0) {
|
||||
$changesMade = true;
|
||||
} elseif ($deletedPreviewCount === 0 && $this->resource->environment_variables_preview()->whereNotIn('key', array_keys($previewVariables))->exists()) {
|
||||
// If we tried to delete but couldn't (due to Docker Compose), mark as error
|
||||
$errorOccurred = true;
|
||||
}
|
||||
|
||||
// Update or create preview variables
|
||||
$updatedPreviewCount = $this->updateOrCreateVariables(true, $previewVariables);
|
||||
if ($updatedPreviewCount > 0) {
|
||||
$changesMade = true;
|
||||
}
|
||||
}
|
||||
|
||||
$this->dispatch('success', 'Environment variables updated.');
|
||||
// Debug information
|
||||
\Log::info('Environment variables update status', [
|
||||
'deletedCount' => $deletedCount,
|
||||
'updatedCount' => $updatedCount,
|
||||
'deletedPreviewCount' => $deletedPreviewCount ?? 0,
|
||||
'updatedPreviewCount' => $updatedPreviewCount ?? 0,
|
||||
'changesMade' => $changesMade,
|
||||
'errorOccurred' => $errorOccurred,
|
||||
]);
|
||||
|
||||
// Only show success message if changes were actually made and no errors occurred
|
||||
if ($changesMade && ! $errorOccurred) {
|
||||
$this->dispatch('success', 'Environment variables updated.');
|
||||
}
|
||||
}
|
||||
|
||||
private function handleSingleSubmit($data)
|
||||
@@ -184,11 +227,46 @@ class All extends Component
|
||||
private function deleteRemovedVariables($isPreview, $variables)
|
||||
{
|
||||
$method = $isPreview ? 'environment_variables_preview' : 'environment_variables';
|
||||
|
||||
// Get all environment variables that will be deleted
|
||||
$variablesToDelete = $this->resource->$method()->whereNotIn('key', array_keys($variables))->get();
|
||||
|
||||
// If there are no variables to delete, return 0
|
||||
if ($variablesToDelete->isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check for system variables that shouldn't be deleted
|
||||
foreach ($variablesToDelete as $envVar) {
|
||||
if ($this->isProtectedEnvironmentVariable($envVar->key)) {
|
||||
$this->dispatch('error', "Cannot delete system environment variable '{$envVar->key}'.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if any of these variables are used in Docker Compose
|
||||
if ($this->resource->type() === 'service' || $this->resource->build_pack === 'dockercompose') {
|
||||
foreach ($variablesToDelete as $envVar) {
|
||||
[$isUsed, $reason] = $this->isEnvironmentVariableUsedInDockerCompose($envVar->key, $this->resource->docker_compose);
|
||||
|
||||
if ($isUsed) {
|
||||
$this->dispatch('error', "Cannot delete environment variable '{$envVar->key}' <br><br>Please remove it from the Docker Compose file first.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, no variables are used in Docker Compose, so we can delete them
|
||||
$this->resource->$method()->whereNotIn('key', array_keys($variables))->delete();
|
||||
|
||||
return $variablesToDelete->count();
|
||||
}
|
||||
|
||||
private function updateOrCreateVariables($isPreview, $variables)
|
||||
{
|
||||
$count = 0;
|
||||
foreach ($variables as $key => $value) {
|
||||
if (str($key)->startsWith('SERVICE_FQDN') || str($key)->startsWith('SERVICE_URL')) {
|
||||
continue;
|
||||
@@ -198,8 +276,12 @@ class All extends Component
|
||||
|
||||
if ($found) {
|
||||
if (! $found->is_shown_once && ! $found->is_multiline) {
|
||||
$found->value = $value;
|
||||
$found->save();
|
||||
// Only count as a change if the value actually changed
|
||||
if ($found->value !== $value) {
|
||||
$found->value = $value;
|
||||
$found->save();
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$environment = new EnvironmentVariable;
|
||||
@@ -212,8 +294,11 @@ class All extends Component
|
||||
$environment->resourceable_type = $this->resource->getMorphClass();
|
||||
|
||||
$environment->save();
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
public function refreshEnvs()
|
||||
|
||||
@@ -4,10 +4,13 @@ namespace App\Livewire\Project\Shared\EnvironmentVariable;
|
||||
|
||||
use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
|
||||
use App\Models\SharedEnvironmentVariable;
|
||||
use App\Traits\EnvironmentVariableProtection;
|
||||
use Livewire\Component;
|
||||
|
||||
class Show extends Component
|
||||
{
|
||||
use EnvironmentVariableProtection;
|
||||
|
||||
public $parameters;
|
||||
|
||||
public ModelsEnvironmentVariable|SharedEnvironmentVariable $env;
|
||||
@@ -175,11 +178,24 @@ class Show extends Component
|
||||
public function delete()
|
||||
{
|
||||
try {
|
||||
if ($this->is_redis_credential) {
|
||||
$this->dispatch('error', 'Cannot delete Redis credentials.');
|
||||
// Check if the variable is protected
|
||||
if ($this->isProtectedEnvironmentVariable($this->env->key)) {
|
||||
$this->dispatch('error', "Cannot delete system environment variable '{$this->env->key}'.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the variable is used in Docker Compose
|
||||
if ($this->type === 'service' || $this->type === 'application' && $this->env->resource()?->docker_compose) {
|
||||
[$isUsed, $reason] = $this->isEnvironmentVariableUsedInDockerCompose($this->env->key, $this->env->resource()?->docker_compose);
|
||||
|
||||
if ($isUsed) {
|
||||
$this->dispatch('error', "Cannot delete environment variable '{$this->env->key}' <br><br>Please remove it from the Docker Compose file first.");
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->env->delete();
|
||||
$this->dispatch('environmentVariableDeleted');
|
||||
$this->dispatch('success', 'Environment variable deleted successfully.');
|
||||
|
||||
63
app/Traits/EnvironmentVariableProtection.php
Normal file
63
app/Traits/EnvironmentVariableProtection.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
trait EnvironmentVariableProtection
|
||||
{
|
||||
/**
|
||||
* Check if an environment variable is protected from deletion
|
||||
*
|
||||
* @param string $key The environment variable key to check
|
||||
* @return bool True if the variable is protected, false otherwise
|
||||
*/
|
||||
protected function isProtectedEnvironmentVariable(string $key): bool
|
||||
{
|
||||
return str($key)->startsWith('SERVICE_FQDN') || str($key)->startsWith('SERVICE_URL');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an environment variable is used in Docker Compose
|
||||
*
|
||||
* @param string $key The environment variable key to check
|
||||
* @param string|null $dockerCompose The Docker Compose YAML content
|
||||
* @return array [bool $isUsed, string $reason] Whether the variable is used and the reason if it is
|
||||
*/
|
||||
protected function isEnvironmentVariableUsedInDockerCompose(string $key, ?string $dockerCompose): array
|
||||
{
|
||||
if (empty($dockerCompose)) {
|
||||
return [false, ''];
|
||||
}
|
||||
|
||||
try {
|
||||
$dockerComposeData = Yaml::parse($dockerCompose);
|
||||
$dockerEnvVars = data_get($dockerComposeData, 'services.*.environment');
|
||||
|
||||
foreach ($dockerEnvVars as $serviceEnvs) {
|
||||
if (! is_array($serviceEnvs)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for direct variable usage
|
||||
foreach ($serviceEnvs as $env => $value) {
|
||||
if ($env === $key) {
|
||||
return [true, "Environment variable '{$key}' is used directly in the Docker Compose file."];
|
||||
}
|
||||
}
|
||||
|
||||
// Check for variable references in values
|
||||
foreach ($serviceEnvs as $env => $value) {
|
||||
if (is_string($value) && str_contains($value, '$'.$key)) {
|
||||
return [true, "Environment variable '{$key}' is referenced in the Docker Compose file."];
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// If there's an error parsing the Docker Compose file, we'll assume it's not used
|
||||
return [false, ''];
|
||||
}
|
||||
|
||||
return [false, ''];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user