mirror of
https://github.com/coollabsio/coolify.git
synced 2026-08-24 10:05:47 -05:00
feat(var): add environment variable copy functionality
This commit is contained in:
@@ -161,6 +161,22 @@ class Show extends Component
|
||||
$this->valuesLoaded = true;
|
||||
}
|
||||
|
||||
public function copyValue(): ?string
|
||||
{
|
||||
if ($this->env->is_shown_once || (auth()->user()?->isMember() ?? true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $this->env instanceof ModelsEnvironmentVariable) {
|
||||
return $this->env->value;
|
||||
}
|
||||
|
||||
return $this->env->get_real_environment_variables_with_server(
|
||||
$this->env->resolveReferencedValue(),
|
||||
$this->env->resourceable,
|
||||
);
|
||||
}
|
||||
|
||||
public function syncData(bool $toModel = false)
|
||||
{
|
||||
if ($toModel) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Livewire\Project\Shared\EnvironmentVariable;
|
||||
|
||||
use App\Models\EnvironmentVariable;
|
||||
use Livewire\Component;
|
||||
|
||||
class ShowHardcoded extends Component
|
||||
@@ -20,6 +21,10 @@ class ShowHardcoded extends Component
|
||||
|
||||
public bool $isPreview = false;
|
||||
|
||||
public ?string $resourceableType = null;
|
||||
|
||||
public ?int $resourceableId = null;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->key = $this->env['key'];
|
||||
@@ -28,6 +33,20 @@ class ShowHardcoded extends Component
|
||||
$this->serviceName = $this->env['service_name'] ?? null;
|
||||
}
|
||||
|
||||
public function copyValue(): ?string
|
||||
{
|
||||
if (auth()->user()?->isMember() ?? true) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return EnvironmentVariable::make([
|
||||
'value' => $this->value,
|
||||
'is_preview' => $this->isPreview,
|
||||
'resourceable_type' => $this->resourceableType,
|
||||
'resourceable_id' => $this->resourceableId,
|
||||
])->resolveReferencedValue();
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.shared.environment-variable.show-hardcoded');
|
||||
|
||||
@@ -302,6 +302,23 @@ class EnvironmentVariable extends BaseModel
|
||||
return $real_value;
|
||||
}
|
||||
|
||||
public function resolveReferencedValue(): ?string
|
||||
{
|
||||
$value = $this->value;
|
||||
|
||||
if ($this->is_literal || blank($value) || ! str($value)->startsWith('$')) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$referencedKey = str($value)->after('$')->trim('{}')->value();
|
||||
|
||||
return static::where('resourceable_type', $this->resourceable_type)
|
||||
->where('resourceable_id', $this->resourceable_id)
|
||||
->where('is_preview', (bool) $this->is_preview)
|
||||
->where('key', $referencedKey)
|
||||
->first()?->value ?? $value;
|
||||
}
|
||||
|
||||
private function get_real_environment_variables(?string $environment_variable = null, $resource = null)
|
||||
{
|
||||
return $this->get_real_environment_variables_internal($environment_variable, $resource);
|
||||
|
||||
@@ -219,7 +219,8 @@
|
||||
@else
|
||||
<livewire:project.shared.environment-variable.show-hardcoded
|
||||
wire:key="{{ $row['id'] }}" :env="$row['environmentVariable']"
|
||||
:isPreview="$row['scope'] === 'preview'" :showEnvironmentType="$showEnvironmentType" />
|
||||
:isPreview="$row['scope'] === 'preview'" :showEnvironmentType="$showEnvironmentType"
|
||||
:resourceableType="get_class($resource)" :resourceableId="$resource->id" />
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
use App\Livewire\Project\Shared\EnvironmentVariable\Show;
|
||||
use App\Livewire\Project\Shared\EnvironmentVariable\ShowHardcoded;
|
||||
use App\Models\Application;
|
||||
use App\Models\Environment;
|
||||
use App\Models\EnvironmentVariable;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Project;
|
||||
use App\Models\SharedEnvironmentVariable;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Livewire\Livewire;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
InstanceSettings::forceCreate(['id' => 0]);
|
||||
|
||||
$this->user = User::factory()->create();
|
||||
$this->team = Team::factory()->create();
|
||||
$this->team->members()->attach($this->user, ['role' => 'owner']);
|
||||
$this->project = Project::factory()->create(['team_id' => $this->team->id]);
|
||||
$this->environment = Environment::factory()->create(['project_id' => $this->project->id]);
|
||||
$this->application = Application::factory()->create(['environment_id' => $this->environment->id]);
|
||||
|
||||
$this->actingAs($this->user);
|
||||
session(['currentTeam' => $this->team]);
|
||||
});
|
||||
|
||||
function createEnvironmentVariable(array $attributes = []): EnvironmentVariable
|
||||
{
|
||||
return EnvironmentVariable::create(array_merge([
|
||||
'key' => 'API_KEY',
|
||||
'value' => 'secret-value',
|
||||
'resourceable_type' => Application::class,
|
||||
'resourceable_id' => test()->application->id,
|
||||
], $attributes));
|
||||
}
|
||||
|
||||
function assertCopiedValue(EnvironmentVariable|SharedEnvironmentVariable $env, ?string $expected): void
|
||||
{
|
||||
Livewire::test(Show::class, ['env' => $env, 'type' => 'application'])
|
||||
->call('copyValue')
|
||||
->assertReturned($expected);
|
||||
}
|
||||
|
||||
function assertCopiedComposeValue(string $value, ?string $expected): void
|
||||
{
|
||||
Livewire::test(ShowHardcoded::class, [
|
||||
'env' => ['key' => 'MYSQL_USER', 'value' => $value],
|
||||
'resourceableType' => Application::class,
|
||||
'resourceableId' => test()->application->id,
|
||||
])
|
||||
->call('copyValue')
|
||||
->assertReturned($expected);
|
||||
}
|
||||
|
||||
test('copies the plain value', function () {
|
||||
assertCopiedValue(createEnvironmentVariable(), 'secret-value');
|
||||
});
|
||||
|
||||
test('copies the referenced variable value instead of the reference', function (string $reference) {
|
||||
createEnvironmentVariable(['key' => 'SERVICE_USER_CLASSICPRESS', 'value' => 'classicpress-user']);
|
||||
|
||||
assertCopiedValue(createEnvironmentVariable(['key' => 'MYSQL_USER', 'value' => $reference]), 'classicpress-user');
|
||||
})->with(['bare' => '$SERVICE_USER_CLASSICPRESS', 'braced' => '${SERVICE_USER_CLASSICPRESS}']);
|
||||
|
||||
test('copies the resolved shared variable value', function () {
|
||||
SharedEnvironmentVariable::create([
|
||||
'key' => 'MY_SECRET',
|
||||
'value' => 'resolved-secret',
|
||||
'type' => 'team',
|
||||
'team_id' => $this->team->id,
|
||||
]);
|
||||
|
||||
assertCopiedValue(createEnvironmentVariable(['value' => '{{team.MY_SECRET}}']), 'resolved-secret');
|
||||
});
|
||||
|
||||
test('copies embedded, literal and unknown references as stored', function () {
|
||||
createEnvironmentVariable(['key' => 'SERVICE_PASSWORD_MYSQL', 'value' => 'generated-password']);
|
||||
|
||||
assertCopiedValue(
|
||||
createEnvironmentVariable(['key' => 'DATABASE_URL', 'value' => 'mysql://root:$SERVICE_PASSWORD_MYSQL@db:3306']),
|
||||
'mysql://root:$SERVICE_PASSWORD_MYSQL@db:3306',
|
||||
);
|
||||
assertCopiedValue(
|
||||
createEnvironmentVariable(['key' => 'LITERAL', 'value' => '$SERVICE_PASSWORD_MYSQL', 'is_literal' => true]),
|
||||
'$SERVICE_PASSWORD_MYSQL',
|
||||
);
|
||||
assertCopiedValue(createEnvironmentVariable(['key' => 'UNKNOWN', 'value' => '$DOES_NOT_EXIST']), '$DOES_NOT_EXIST');
|
||||
});
|
||||
|
||||
test('copies literal values without .env-style quoting', function () {
|
||||
$env = createEnvironmentVariable(['value' => 'pa$$word', 'is_literal' => true]);
|
||||
|
||||
expect($env->real_value)->toBe("'pa\$\$word'");
|
||||
assertCopiedValue($env, 'pa$$word');
|
||||
});
|
||||
|
||||
test('copies the value of a shared environment variable row', function () {
|
||||
$shared = SharedEnvironmentVariable::create([
|
||||
'key' => 'TEAM_WIDE',
|
||||
'value' => 'team-wide-value',
|
||||
'type' => 'team',
|
||||
'team_id' => $this->team->id,
|
||||
]);
|
||||
|
||||
assertCopiedValue($shared, 'team-wide-value');
|
||||
});
|
||||
|
||||
test('members get no copy button and no value', function () {
|
||||
$member = User::factory()->create();
|
||||
$this->team->members()->attach($member, ['role' => 'member']);
|
||||
$this->actingAs($member);
|
||||
|
||||
Livewire::test(Show::class, ['env' => createEnvironmentVariable(), 'type' => 'application'])
|
||||
->assertDontSeeHtml('Copy value')
|
||||
->call('copyValue')
|
||||
->assertReturned(null);
|
||||
});
|
||||
|
||||
test('locked variables get no copy button and no value', function () {
|
||||
Livewire::test(Show::class, ['env' => createEnvironmentVariable(['is_shown_once' => true]), 'type' => 'application'])
|
||||
->assertDontSeeHtml('Copy value')
|
||||
->call('copyValue')
|
||||
->assertReturned(null);
|
||||
});
|
||||
|
||||
test('compose-managed rows copy the referenced variable value', function () {
|
||||
createEnvironmentVariable(['key' => 'SERVICE_USER_CLASSICPRESS', 'value' => 'classicpress-user']);
|
||||
|
||||
assertCopiedComposeValue('$SERVICE_USER_CLASSICPRESS', 'classicpress-user');
|
||||
assertCopiedComposeValue('production', 'production');
|
||||
});
|
||||
|
||||
test('compose-managed rows hide copying from members', function () {
|
||||
$member = User::factory()->create();
|
||||
$this->team->members()->attach($member, ['role' => 'member']);
|
||||
$this->actingAs($member);
|
||||
|
||||
Livewire::test(ShowHardcoded::class, [
|
||||
'env' => ['key' => 'MYSQL_USER', 'value' => '$SERVICE_USER_CLASSICPRESS'],
|
||||
'resourceableType' => Application::class,
|
||||
'resourceableId' => $this->application->id,
|
||||
])
|
||||
->assertDontSeeHtml('Copy value')
|
||||
->call('copyValue')
|
||||
->assertReturned(null);
|
||||
});
|
||||
Reference in New Issue
Block a user