feat: add magic variable detection and update UI behavior accordingly

This commit is contained in:
Andras Bacsai 2025-11-25 11:23:18 +01:00
parent 89192c9862
commit d67fcd1dff
3 changed files with 227 additions and 50 deletions

View file

@ -24,6 +24,8 @@ class Show extends Component
public bool $isLocked = false;
public bool $isMagicVariable = false;
public bool $isSharedVariable = false;
public string $type;
@ -146,9 +148,13 @@ public function syncData(bool $toModel = false)
public function checkEnvs()
{
$this->isDisabled = false;
$this->isMagicVariable = false;
if (str($this->env->key)->startsWith('SERVICE_FQDN') || str($this->env->key)->startsWith('SERVICE_URL') || str($this->env->key)->startsWith('SERVICE_NAME')) {
$this->isDisabled = true;
$this->isMagicVariable = true;
}
if ($this->env->is_shown_once) {
$this->isLocked = true;
}

View file

@ -40,10 +40,12 @@
<x-forms.checkbox instantSave id="is_runtime"
helper="Make this variable available in the running container at runtime."
label="Available at Runtime" />
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
<x-forms.checkbox instantSave id="is_literal"
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
label="Is Literal?" />
@if (!$isMagicVariable)
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
<x-forms.checkbox instantSave id="is_literal"
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
label="Is Literal?" />
@endif
@else
@if ($is_shared)
<x-forms.checkbox instantSave id="is_literal"
@ -51,7 +53,9 @@
label="Is Literal?" />
@else
@if ($isSharedVariable)
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
@if (!$isMagicVariable)
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
@endif
@else
@if (!$env->is_nixpacks)
<x-forms.checkbox instantSave id="is_buildtime"
@ -61,12 +65,14 @@
<x-forms.checkbox instantSave id="is_runtime"
helper="Make this variable available in the running container at runtime."
label="Available at Runtime" />
@if (!$env->is_nixpacks)
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
@if ($is_multiline === false)
<x-forms.checkbox instantSave id="is_literal"
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
label="Is Literal?" />
@if (!$isMagicVariable)
@if (!$env->is_nixpacks)
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
@if ($is_multiline === false)
<x-forms.checkbox instantSave id="is_literal"
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
label="Is Literal?" />
@endif
@endif
@endif
@endif
@ -86,10 +92,12 @@
<x-forms.checkbox disabled id="is_runtime"
helper="Make this variable available in the running container at runtime."
label="Available at Runtime" />
<x-forms.checkbox disabled id="is_multiline" label="Is Multiline?" />
<x-forms.checkbox disabled id="is_literal"
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
label="Is Literal?" />
@if (!$isMagicVariable)
<x-forms.checkbox disabled id="is_multiline" label="Is Multiline?" />
<x-forms.checkbox disabled id="is_literal"
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
label="Is Literal?" />
@endif
@else
@if ($is_shared)
<x-forms.checkbox disabled id="is_literal"
@ -97,7 +105,9 @@
label="Is Literal?" />
@else
@if ($isSharedVariable)
<x-forms.checkbox disabled id="is_multiline" label="Is Multiline?" />
@if (!$isMagicVariable)
<x-forms.checkbox disabled id="is_multiline" label="Is Multiline?" />
@endif
@else
<x-forms.checkbox disabled id="is_buildtime"
helper="Make this variable available during Docker build process. Useful for build secrets and dependencies."
@ -105,11 +115,13 @@
<x-forms.checkbox disabled id="is_runtime"
helper="Make this variable available in the running container at runtime."
label="Available at Runtime" />
<x-forms.checkbox disabled id="is_multiline" label="Is Multiline?" />
@if ($is_multiline === false)
<x-forms.checkbox disabled id="is_literal"
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
label="Is Literal?" />
@if (!$isMagicVariable)
<x-forms.checkbox disabled id="is_multiline" label="Is Multiline?" />
@if ($is_multiline === false)
<x-forms.checkbox disabled id="is_literal"
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
label="Is Literal?" />
@endif
@endif
@endif
@endif
@ -133,8 +145,10 @@
<x-forms.input disabled type="password" id="real_value" />
@endif
</div>
<x-forms.input disabled id="comment" label="Comment"
helper="Add a note to document what this environment variable is used for." maxlength="256" />
@if (!$isMagicVariable)
<x-forms.input disabled id="comment" label="Comment"
helper="Add a note to document what this environment variable is used for." maxlength="256" />
@endif
</div>
@else
<div class="flex flex-col w-full gap-2">
@ -164,8 +178,10 @@
<x-forms.input disabled type="password" id="real_value" />
@endif
</div>
<x-forms.input disabled id="comment" label="Comment"
helper="Add a note to document what this environment variable is used for." maxlength="256" />
@if (!$isMagicVariable)
<x-forms.input disabled id="comment" label="Comment"
helper="Add a note to document what this environment variable is used for." maxlength="256" />
@endif
</div>
@endcan
@can('update', $this->env)
@ -179,10 +195,12 @@
<x-forms.checkbox instantSave id="is_runtime"
helper="Make this variable available in the running container at runtime."
label="Available at Runtime" />
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
<x-forms.checkbox instantSave id="is_literal"
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
label="Is Literal?" />
@if (!$isMagicVariable)
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
<x-forms.checkbox instantSave id="is_literal"
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
label="Is Literal?" />
@endif
@else
@if ($is_shared)
<x-forms.checkbox instantSave id="is_literal"
@ -190,7 +208,9 @@
label="Is Literal?" />
@else
@if ($isSharedVariable)
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
@if (!$isMagicVariable)
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
@endif
@else
@if (!$env->is_nixpacks)
<x-forms.checkbox instantSave id="is_buildtime"
@ -200,12 +220,14 @@
<x-forms.checkbox instantSave id="is_runtime"
helper="Make this variable available in the running container at runtime."
label="Available at Runtime" />
@if (!$env->is_nixpacks)
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
@if ($is_multiline === false)
<x-forms.checkbox instantSave id="is_literal"
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
label="Is Literal?" />
@if (!$isMagicVariable)
@if (!$env->is_nixpacks)
<x-forms.checkbox instantSave id="is_multiline" label="Is Multiline?" />
@if ($is_multiline === false)
<x-forms.checkbox instantSave id="is_literal"
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
label="Is Literal?" />
@endif
@endif
@endif
@endif
@ -214,8 +236,9 @@
@endif
</div>
<x-environment-variable-warning :problematic-variables="$problematicVariables" />
<div class="flex w-full justify-end gap-2">
@if ($isDisabled)
@if (!$isMagicVariable)
<div class="flex w-full justify-end gap-2">
@if ($isDisabled)
<x-forms.button disabled type="submit">Update</x-forms.button>
<x-forms.button wire:click='lock'>Lock</x-forms.button>
<x-modal-confirmation title="Confirm Environment Variable Deletion?" isErrorButton buttonTitle="Delete"
@ -233,8 +256,9 @@
confirmationLabel="Please confirm the execution of the actions by entering the Environment Variable Name below"
shortConfirmationLabel="Environment Variable Name" :confirmWithPassword="false"
step2ButtonText="Permanently Delete" />
@endif
</div>
@endif
</div>
@endif
</div>
@else
<div class="flex flex-col w-full gap-3">
@ -247,10 +271,12 @@
<x-forms.checkbox disabled id="is_runtime"
helper="Make this variable available in the running container at runtime."
label="Available at Runtime" />
<x-forms.checkbox disabled id="is_multiline" label="Is Multiline?" />
<x-forms.checkbox disabled id="is_literal"
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
label="Is Literal?" />
@if (!$isMagicVariable)
<x-forms.checkbox disabled id="is_multiline" label="Is Multiline?" />
<x-forms.checkbox disabled id="is_literal"
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
label="Is Literal?" />
@endif
@else
@if ($is_shared)
<x-forms.checkbox disabled id="is_literal"
@ -258,7 +284,9 @@
label="Is Literal?" />
@else
@if ($isSharedVariable)
<x-forms.checkbox disabled id="is_multiline" label="Is Multiline?" />
@if (!$isMagicVariable)
<x-forms.checkbox disabled id="is_multiline" label="Is Multiline?" />
@endif
@else
<x-forms.checkbox disabled id="is_buildtime"
helper="Make this variable available during Docker build process. Useful for build secrets and dependencies."
@ -266,11 +294,13 @@
<x-forms.checkbox disabled id="is_runtime"
helper="Make this variable available in the running container at runtime."
label="Available at Runtime" />
<x-forms.checkbox disabled id="is_multiline" label="Is Multiline?" />
@if ($is_multiline === false)
<x-forms.checkbox disabled id="is_literal"
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
label="Is Literal?" />
@if (!$isMagicVariable)
<x-forms.checkbox disabled id="is_multiline" label="Is Multiline?" />
@if ($is_multiline === false)
<x-forms.checkbox disabled id="is_literal"
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
label="Is Literal?" />
@endif
@endif
@endif
@endif

View file

@ -0,0 +1,141 @@
<?php
use App\Livewire\Project\Shared\EnvironmentVariable\Show;
use App\Models\EnvironmentVariable;
afterEach(function () {
Mockery::close();
});
test('SERVICE_FQDN variables are identified as magic variables', function () {
$mock = Mockery::mock(EnvironmentVariable::class);
$mock->shouldReceive('getAttribute')
->with('key')
->andReturn('SERVICE_FQDN_DB');
$mock->shouldReceive('getAttribute')
->with('is_shown_once')
->andReturn(false);
$mock->shouldReceive('getMorphClass')
->andReturn(EnvironmentVariable::class);
$component = new Show;
$component->env = $mock;
$component->checkEnvs();
expect($component->isMagicVariable)->toBeTrue();
expect($component->isDisabled)->toBeTrue();
});
test('SERVICE_URL variables are identified as magic variables', function () {
$mock = Mockery::mock(EnvironmentVariable::class);
$mock->shouldReceive('getAttribute')
->with('key')
->andReturn('SERVICE_URL_API');
$mock->shouldReceive('getAttribute')
->with('is_shown_once')
->andReturn(false);
$mock->shouldReceive('getMorphClass')
->andReturn(EnvironmentVariable::class);
$component = new Show;
$component->env = $mock;
$component->checkEnvs();
expect($component->isMagicVariable)->toBeTrue();
expect($component->isDisabled)->toBeTrue();
});
test('SERVICE_NAME variables are identified as magic variables', function () {
$mock = Mockery::mock(EnvironmentVariable::class);
$mock->shouldReceive('getAttribute')
->with('key')
->andReturn('SERVICE_NAME');
$mock->shouldReceive('getAttribute')
->with('is_shown_once')
->andReturn(false);
$mock->shouldReceive('getMorphClass')
->andReturn(EnvironmentVariable::class);
$component = new Show;
$component->env = $mock;
$component->checkEnvs();
expect($component->isMagicVariable)->toBeTrue();
expect($component->isDisabled)->toBeTrue();
});
test('regular variables are not magic variables', function () {
$mock = Mockery::mock(EnvironmentVariable::class);
$mock->shouldReceive('getAttribute')
->with('key')
->andReturn('DATABASE_URL');
$mock->shouldReceive('getAttribute')
->with('is_shown_once')
->andReturn(false);
$mock->shouldReceive('getMorphClass')
->andReturn(EnvironmentVariable::class);
$component = new Show;
$component->env = $mock;
$component->checkEnvs();
expect($component->isMagicVariable)->toBeFalse();
expect($component->isDisabled)->toBeFalse();
});
test('locked variables are not magic variables unless they start with SERVICE_', function () {
$mock = Mockery::mock(EnvironmentVariable::class);
$mock->shouldReceive('getAttribute')
->with('key')
->andReturn('SECRET_KEY');
$mock->shouldReceive('getAttribute')
->with('is_shown_once')
->andReturn(true);
$mock->shouldReceive('getMorphClass')
->andReturn(EnvironmentVariable::class);
$component = new Show;
$component->env = $mock;
$component->checkEnvs();
expect($component->isMagicVariable)->toBeFalse();
expect($component->isLocked)->toBeTrue();
});
test('SERVICE_FQDN with port suffix is identified as magic variable', function () {
$mock = Mockery::mock(EnvironmentVariable::class);
$mock->shouldReceive('getAttribute')
->with('key')
->andReturn('SERVICE_FQDN_DB_5432');
$mock->shouldReceive('getAttribute')
->with('is_shown_once')
->andReturn(false);
$mock->shouldReceive('getMorphClass')
->andReturn(EnvironmentVariable::class);
$component = new Show;
$component->env = $mock;
$component->checkEnvs();
expect($component->isMagicVariable)->toBeTrue();
expect($component->isDisabled)->toBeTrue();
});
test('SERVICE_URL with port suffix is identified as magic variable', function () {
$mock = Mockery::mock(EnvironmentVariable::class);
$mock->shouldReceive('getAttribute')
->with('key')
->andReturn('SERVICE_URL_API_8080');
$mock->shouldReceive('getAttribute')
->with('is_shown_once')
->andReturn(false);
$mock->shouldReceive('getMorphClass')
->andReturn(EnvironmentVariable::class);
$component = new Show;
$component->env = $mock;
$component->checkEnvs();
expect($component->isMagicVariable)->toBeTrue();
expect($component->isDisabled)->toBeTrue();
});