mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-19 00:15:01 +00:00
feat: add comprehensive environment variable parsing with nested resolution and hardcoded variable detection
This commit introduces advanced environment variable handling capabilities including: - Nested environment variable resolution with circular dependency detection - Extraction of hardcoded environment variables from docker-compose.yml - New ShowHardcoded Livewire component for displaying detected variables - Enhanced UI for better environment variable management The changes improve the user experience by automatically detecting and displaying environment variables that are hardcoded in docker-compose files, allowing users to override them if needed. The nested variable resolution ensures complex variable dependencies are properly handled. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
d67fcd1dff
commit
208f0eac99
10 files changed
+1145
-87
No files matched your search
+280
-73
@@ -998,53 +998,139 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int
|
||||
} else {
|
||||
if ($value->startsWith('$')) {
|
||||
$isRequired = false;
|
||||
if ($value->contains(':-')) {
|
||||
$value = replaceVariables($value);
|
||||
$key = $value->before(':');
|
||||
$value = $value->after(':-');
|
||||
} elseif ($value->contains('-')) {
|
||||
$value = replaceVariables($value);
|
||||
|
||||
$key = $value->before('-');
|
||||
$value = $value->after('-');
|
||||
} elseif ($value->contains(':?')) {
|
||||
$value = replaceVariables($value);
|
||||
// Extract variable content between ${...} using balanced brace matching
|
||||
$result = extractBalancedBraceContent($value->value(), 0);
|
||||
|
||||
$key = $value->before(':');
|
||||
$value = $value->after(':?');
|
||||
$isRequired = true;
|
||||
} elseif ($value->contains('?')) {
|
||||
$value = replaceVariables($value);
|
||||
if ($result !== null) {
|
||||
$content = $result['content'];
|
||||
$split = splitOnOperatorOutsideNested($content);
|
||||
|
||||
$key = $value->before('?');
|
||||
$value = $value->after('?');
|
||||
$isRequired = true;
|
||||
}
|
||||
if ($originalValue->value() === $value->value()) {
|
||||
// This means the variable does not have a default value, so it needs to be created in Coolify
|
||||
$parsedKeyValue = replaceVariables($value);
|
||||
if ($split !== null) {
|
||||
// Has default value syntax (:-, -, :?, or ?)
|
||||
$varName = $split['variable'];
|
||||
$operator = $split['operator'];
|
||||
$defaultValue = $split['default'];
|
||||
$isRequired = str_contains($operator, '?');
|
||||
|
||||
// Create the primary variable with its default (only if it doesn't exist)
|
||||
$envVar = $resource->environment_variables()->firstOrCreate([
|
||||
'key' => $varName,
|
||||
'resourceable_type' => get_class($resource),
|
||||
'resourceable_id' => $resource->id,
|
||||
], [
|
||||
'value' => $defaultValue,
|
||||
'is_preview' => false,
|
||||
'is_required' => $isRequired,
|
||||
]);
|
||||
|
||||
// Add the variable to the environment so it will be shown in the deployable compose file
|
||||
$environment[$varName] = $envVar->value;
|
||||
|
||||
// Recursively process nested variables in default value
|
||||
if (str_contains($defaultValue, '${')) {
|
||||
$searchPos = 0;
|
||||
$nestedResult = extractBalancedBraceContent($defaultValue, $searchPos);
|
||||
while ($nestedResult !== null) {
|
||||
$nestedContent = $nestedResult['content'];
|
||||
$nestedSplit = splitOnOperatorOutsideNested($nestedContent);
|
||||
|
||||
// Determine the nested variable name
|
||||
$nestedVarName = $nestedSplit !== null ? $nestedSplit['variable'] : $nestedContent;
|
||||
|
||||
// Skip SERVICE_URL_* and SERVICE_FQDN_* variables - they are handled by magic variable system
|
||||
$isMagicVariable = str_starts_with($nestedVarName, 'SERVICE_URL_') || str_starts_with($nestedVarName, 'SERVICE_FQDN_');
|
||||
|
||||
if (! $isMagicVariable) {
|
||||
if ($nestedSplit !== null) {
|
||||
$nestedEnvVar = $resource->environment_variables()->firstOrCreate([
|
||||
'key' => $nestedSplit['variable'],
|
||||
'resourceable_type' => get_class($resource),
|
||||
'resourceable_id' => $resource->id,
|
||||
], [
|
||||
'value' => $nestedSplit['default'],
|
||||
'is_preview' => false,
|
||||
]);
|
||||
$environment[$nestedSplit['variable']] = $nestedEnvVar->value;
|
||||
} else {
|
||||
$nestedEnvVar = $resource->environment_variables()->firstOrCreate([
|
||||
'key' => $nestedContent,
|
||||
'resourceable_type' => get_class($resource),
|
||||
'resourceable_id' => $resource->id,
|
||||
], [
|
||||
'is_preview' => false,
|
||||
]);
|
||||
$environment[$nestedContent] = $nestedEnvVar->value;
|
||||
}
|
||||
}
|
||||
|
||||
$searchPos = $nestedResult['end'] + 1;
|
||||
if ($searchPos >= strlen($defaultValue)) {
|
||||
break;
|
||||
}
|
||||
$nestedResult = extractBalancedBraceContent($defaultValue, $searchPos);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Simple variable reference without default
|
||||
$parsedKeyValue = replaceVariables($value);
|
||||
$resource->environment_variables()->firstOrCreate([
|
||||
'key' => $content,
|
||||
'resourceable_type' => get_class($resource),
|
||||
'resourceable_id' => $resource->id,
|
||||
], [
|
||||
'is_preview' => false,
|
||||
'is_required' => $isRequired,
|
||||
]);
|
||||
// Add the variable to the environment
|
||||
$environment[$content] = $value;
|
||||
}
|
||||
} else {
|
||||
// Fallback to old behavior for malformed input (backward compatibility)
|
||||
if ($value->contains(':-')) {
|
||||
$value = replaceVariables($value);
|
||||
$key = $value->before(':');
|
||||
$value = $value->after(':-');
|
||||
} elseif ($value->contains('-')) {
|
||||
$value = replaceVariables($value);
|
||||
$key = $value->before('-');
|
||||
$value = $value->after('-');
|
||||
} elseif ($value->contains(':?')) {
|
||||
$value = replaceVariables($value);
|
||||
$key = $value->before(':');
|
||||
$value = $value->after(':?');
|
||||
$isRequired = true;
|
||||
} elseif ($value->contains('?')) {
|
||||
$value = replaceVariables($value);
|
||||
$key = $value->before('?');
|
||||
$value = $value->after('?');
|
||||
$isRequired = true;
|
||||
}
|
||||
if ($originalValue->value() === $value->value()) {
|
||||
// This means the variable does not have a default value
|
||||
$parsedKeyValue = replaceVariables($value);
|
||||
$resource->environment_variables()->firstOrCreate([
|
||||
'key' => $parsedKeyValue,
|
||||
'resourceable_type' => get_class($resource),
|
||||
'resourceable_id' => $resource->id,
|
||||
], [
|
||||
'is_preview' => false,
|
||||
'is_required' => $isRequired,
|
||||
]);
|
||||
$environment[$parsedKeyValue->value()] = $value;
|
||||
|
||||
continue;
|
||||
}
|
||||
$resource->environment_variables()->firstOrCreate([
|
||||
'key' => $parsedKeyValue,
|
||||
'key' => $key,
|
||||
'resourceable_type' => get_class($resource),
|
||||
'resourceable_id' => $resource->id,
|
||||
], [
|
||||
'value' => $value,
|
||||
'is_preview' => false,
|
||||
'is_required' => $isRequired,
|
||||
]);
|
||||
// Add the variable to the environment so it will be shown in the deployable compose file
|
||||
$environment[$parsedKeyValue->value()] = $value;
|
||||
|
||||
continue;
|
||||
}
|
||||
$resource->environment_variables()->firstOrCreate([
|
||||
'key' => $key,
|
||||
'resourceable_type' => get_class($resource),
|
||||
'resourceable_id' => $resource->id,
|
||||
], [
|
||||
'value' => $value,
|
||||
'is_preview' => false,
|
||||
'is_required' => $isRequired,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1774,6 +1860,7 @@ function serviceParser(Service $resource): Collection
|
||||
$serviceExists->fqdn = $url;
|
||||
$serviceExists->save();
|
||||
}
|
||||
// Create FQDN variable
|
||||
$resource->environment_variables()->updateOrCreate([
|
||||
'key' => $key->value(),
|
||||
'resourceable_type' => get_class($resource),
|
||||
@@ -1784,9 +1871,22 @@ function serviceParser(Service $resource): Collection
|
||||
'comment' => $envComments[$originalMagicKey] ?? null,
|
||||
]);
|
||||
|
||||
// Also create the paired SERVICE_URL_* variable
|
||||
$urlKey = 'SERVICE_URL_'.strtoupper($fqdnFor);
|
||||
$resource->environment_variables()->updateOrCreate([
|
||||
'key' => $urlKey,
|
||||
'resourceable_type' => get_class($resource),
|
||||
'resourceable_id' => $resource->id,
|
||||
], [
|
||||
'value' => $url,
|
||||
'is_preview' => false,
|
||||
'comment' => $envComments[$urlKey] ?? null,
|
||||
]);
|
||||
|
||||
} elseif ($command->value() === 'URL') {
|
||||
$urlFor = $key->after('SERVICE_URL_')->lower()->value();
|
||||
$url = generateUrl(server: $server, random: str($urlFor)->replace('_', '-')->value()."-$uuid");
|
||||
$fqdn = generateFqdn(server: $server, random: str($urlFor)->replace('_', '-')->value()."-$uuid", parserVersion: $resource->compose_parsing_version);
|
||||
|
||||
$envExists = $resource->environment_variables()->where('key', $key->value())->first();
|
||||
// Also check if a port-suffixed version exists (e.g., SERVICE_URL_DASHBOARD_6791)
|
||||
@@ -1803,6 +1903,7 @@ function serviceParser(Service $resource): Collection
|
||||
$serviceExists->fqdn = $url;
|
||||
$serviceExists->save();
|
||||
}
|
||||
// Create URL variable
|
||||
$resource->environment_variables()->updateOrCreate([
|
||||
'key' => $key->value(),
|
||||
'resourceable_type' => get_class($resource),
|
||||
@@ -1813,6 +1914,18 @@ function serviceParser(Service $resource): Collection
|
||||
'comment' => $envComments[$originalMagicKey] ?? null,
|
||||
]);
|
||||
|
||||
// Also create the paired SERVICE_FQDN_* variable
|
||||
$fqdnKey = 'SERVICE_FQDN_'.strtoupper($urlFor);
|
||||
$resource->environment_variables()->updateOrCreate([
|
||||
'key' => $fqdnKey,
|
||||
'resourceable_type' => get_class($resource),
|
||||
'resourceable_id' => $resource->id,
|
||||
], [
|
||||
'value' => $fqdn,
|
||||
'is_preview' => false,
|
||||
'comment' => $envComments[$fqdnKey] ?? null,
|
||||
]);
|
||||
|
||||
} else {
|
||||
$value = generateEnvValue($command, $resource);
|
||||
$resource->environment_variables()->updateOrCreate([
|
||||
@@ -2213,55 +2326,149 @@ function serviceParser(Service $resource): Collection
|
||||
} else {
|
||||
if ($value->startsWith('$')) {
|
||||
$isRequired = false;
|
||||
if ($value->contains(':-')) {
|
||||
$value = replaceVariables($value);
|
||||
$key = $value->before(':');
|
||||
$value = $value->after(':-');
|
||||
} elseif ($value->contains('-')) {
|
||||
$value = replaceVariables($value);
|
||||
|
||||
$key = $value->before('-');
|
||||
$value = $value->after('-');
|
||||
} elseif ($value->contains(':?')) {
|
||||
$value = replaceVariables($value);
|
||||
// Extract variable content between ${...} using balanced brace matching
|
||||
$result = extractBalancedBraceContent($value->value(), 0);
|
||||
|
||||
$key = $value->before(':');
|
||||
$value = $value->after(':?');
|
||||
$isRequired = true;
|
||||
} elseif ($value->contains('?')) {
|
||||
$value = replaceVariables($value);
|
||||
if ($result !== null) {
|
||||
$content = $result['content'];
|
||||
$split = splitOnOperatorOutsideNested($content);
|
||||
|
||||
$key = $value->before('?');
|
||||
$value = $value->after('?');
|
||||
$isRequired = true;
|
||||
}
|
||||
if ($originalValue->value() === $value->value()) {
|
||||
// This means the variable does not have a default value, so it needs to be created in Coolify
|
||||
$parsedKeyValue = replaceVariables($value);
|
||||
if ($split !== null) {
|
||||
// Has default value syntax (:-, -, :?, or ?)
|
||||
$varName = $split['variable'];
|
||||
$operator = $split['operator'];
|
||||
$defaultValue = $split['default'];
|
||||
$isRequired = str_contains($operator, '?');
|
||||
|
||||
// Create the primary variable with its default (only if it doesn't exist)
|
||||
// Use firstOrCreate instead of updateOrCreate to avoid overwriting user edits
|
||||
$envVar = $resource->environment_variables()->firstOrCreate([
|
||||
'key' => $varName,
|
||||
'resourceable_type' => get_class($resource),
|
||||
'resourceable_id' => $resource->id,
|
||||
], [
|
||||
'value' => $defaultValue,
|
||||
'is_preview' => false,
|
||||
'is_required' => $isRequired,
|
||||
'comment' => $envComments[$originalKey] ?? null,
|
||||
]);
|
||||
|
||||
// Add the variable to the environment so it will be shown in the deployable compose file
|
||||
$environment[$varName] = $envVar->value;
|
||||
|
||||
// Recursively process nested variables in default value
|
||||
if (str_contains($defaultValue, '${')) {
|
||||
// Extract and create nested variables
|
||||
$searchPos = 0;
|
||||
$nestedResult = extractBalancedBraceContent($defaultValue, $searchPos);
|
||||
while ($nestedResult !== null) {
|
||||
$nestedContent = $nestedResult['content'];
|
||||
$nestedSplit = splitOnOperatorOutsideNested($nestedContent);
|
||||
|
||||
// Determine the nested variable name
|
||||
$nestedVarName = $nestedSplit !== null ? $nestedSplit['variable'] : $nestedContent;
|
||||
|
||||
// Skip SERVICE_URL_* and SERVICE_FQDN_* variables - they are handled by magic variable system
|
||||
$isMagicVariable = str_starts_with($nestedVarName, 'SERVICE_URL_') || str_starts_with($nestedVarName, 'SERVICE_FQDN_');
|
||||
|
||||
if (! $isMagicVariable) {
|
||||
if ($nestedSplit !== null) {
|
||||
// Create nested variable with its default (only if it doesn't exist)
|
||||
$nestedEnvVar = $resource->environment_variables()->firstOrCreate([
|
||||
'key' => $nestedSplit['variable'],
|
||||
'resourceable_type' => get_class($resource),
|
||||
'resourceable_id' => $resource->id,
|
||||
], [
|
||||
'value' => $nestedSplit['default'],
|
||||
'is_preview' => false,
|
||||
]);
|
||||
// Add nested variable to environment
|
||||
$environment[$nestedSplit['variable']] = $nestedEnvVar->value;
|
||||
} else {
|
||||
// Simple nested variable without default (only if it doesn't exist)
|
||||
$nestedEnvVar = $resource->environment_variables()->firstOrCreate([
|
||||
'key' => $nestedContent,
|
||||
'resourceable_type' => get_class($resource),
|
||||
'resourceable_id' => $resource->id,
|
||||
], [
|
||||
'is_preview' => false,
|
||||
]);
|
||||
// Add nested variable to environment
|
||||
$environment[$nestedContent] = $nestedEnvVar->value;
|
||||
}
|
||||
}
|
||||
|
||||
// Look for more nested variables
|
||||
$searchPos = $nestedResult['end'] + 1;
|
||||
if ($searchPos >= strlen($defaultValue)) {
|
||||
break;
|
||||
}
|
||||
$nestedResult = extractBalancedBraceContent($defaultValue, $searchPos);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Simple variable reference without default
|
||||
$resource->environment_variables()->updateOrCreate([
|
||||
'key' => $content,
|
||||
'resourceable_type' => get_class($resource),
|
||||
'resourceable_id' => $resource->id,
|
||||
], [
|
||||
'is_preview' => false,
|
||||
'is_required' => $isRequired,
|
||||
'comment' => $envComments[$originalKey] ?? null,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
// Fallback to old behavior for malformed input (backward compatibility)
|
||||
if ($value->contains(':-')) {
|
||||
$value = replaceVariables($value);
|
||||
$key = $value->before(':');
|
||||
$value = $value->after(':-');
|
||||
} elseif ($value->contains('-')) {
|
||||
$value = replaceVariables($value);
|
||||
$key = $value->before('-');
|
||||
$value = $value->after('-');
|
||||
} elseif ($value->contains(':?')) {
|
||||
$value = replaceVariables($value);
|
||||
$key = $value->before(':');
|
||||
$value = $value->after(':?');
|
||||
$isRequired = true;
|
||||
} elseif ($value->contains('?')) {
|
||||
$value = replaceVariables($value);
|
||||
$key = $value->before('?');
|
||||
$value = $value->after('?');
|
||||
$isRequired = true;
|
||||
}
|
||||
|
||||
if ($originalValue->value() === $value->value()) {
|
||||
// This means the variable does not have a default value
|
||||
$parsedKeyValue = replaceVariables($value);
|
||||
$resource->environment_variables()->updateOrCreate([
|
||||
'key' => $parsedKeyValue,
|
||||
'resourceable_type' => get_class($resource),
|
||||
'resourceable_id' => $resource->id,
|
||||
], [
|
||||
'is_preview' => false,
|
||||
'is_required' => $isRequired,
|
||||
'comment' => $envComments[$originalKey] ?? null,
|
||||
]);
|
||||
// Add the variable to the environment so it will be shown in the deployable compose file
|
||||
$environment[$parsedKeyValue->value()] = $value;
|
||||
|
||||
continue;
|
||||
}
|
||||
$resource->environment_variables()->updateOrCreate([
|
||||
'key' => $parsedKeyValue,
|
||||
'key' => $key,
|
||||
'resourceable_type' => get_class($resource),
|
||||
'resourceable_id' => $resource->id,
|
||||
], [
|
||||
'value' => $value,
|
||||
'is_preview' => false,
|
||||
'is_required' => $isRequired,
|
||||
'comment' => $envComments[$originalKey] ?? null,
|
||||
]);
|
||||
// Add the variable to the environment so it will be shown in the deployable compose file
|
||||
$environment[$parsedKeyValue->value()] = $value;
|
||||
|
||||
continue;
|
||||
}
|
||||
$resource->environment_variables()->updateOrCreate([
|
||||
'key' => $key,
|
||||
'resourceable_type' => get_class($resource),
|
||||
'resourceable_id' => $resource->id,
|
||||
], [
|
||||
'value' => $value,
|
||||
'is_preview' => false,
|
||||
'is_required' => $isRequired,
|
||||
'comment' => $envComments[$originalKey] ?? null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,115 @@ function collectRegex(string $name)
|
||||
{
|
||||
return "/{$name}\w+/";
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract content between balanced braces, handling nested braces properly.
|
||||
*
|
||||
* @param string $str The string to search
|
||||
* @param int $startPos Position to start searching from
|
||||
* @return array|null Array with 'content', 'start', and 'end' keys, or null if no balanced braces found
|
||||
*/
|
||||
function extractBalancedBraceContent(string $str, int $startPos = 0): ?array
|
||||
{
|
||||
// Find opening brace
|
||||
$openPos = strpos($str, '{', $startPos);
|
||||
if ($openPos === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Track depth to find matching closing brace
|
||||
$depth = 1;
|
||||
$pos = $openPos + 1;
|
||||
$len = strlen($str);
|
||||
|
||||
while ($pos < $len && $depth > 0) {
|
||||
if ($str[$pos] === '{') {
|
||||
$depth++;
|
||||
} elseif ($str[$pos] === '}') {
|
||||
$depth--;
|
||||
}
|
||||
$pos++;
|
||||
}
|
||||
|
||||
if ($depth !== 0) {
|
||||
// Unbalanced braces
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'content' => substr($str, $openPos + 1, $pos - $openPos - 2),
|
||||
'start' => $openPos,
|
||||
'end' => $pos - 1,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Split variable expression on operators (:-, -, :?, ?) while respecting nested braces.
|
||||
*
|
||||
* @param string $content The content to split (without outer ${...})
|
||||
* @return array|null Array with 'variable', 'operator', and 'default' keys, or null if no operator found
|
||||
*/
|
||||
function splitOnOperatorOutsideNested(string $content): ?array
|
||||
{
|
||||
$operators = [':-', '-', ':?', '?'];
|
||||
$depth = 0;
|
||||
$len = strlen($content);
|
||||
|
||||
for ($i = 0; $i < $len; $i++) {
|
||||
if ($content[$i] === '{') {
|
||||
$depth++;
|
||||
} elseif ($content[$i] === '}') {
|
||||
$depth--;
|
||||
} elseif ($depth === 0) {
|
||||
// Check for operators only at depth 0 (outside nested braces)
|
||||
foreach ($operators as $op) {
|
||||
if (substr($content, $i, strlen($op)) === $op) {
|
||||
return [
|
||||
'variable' => substr($content, 0, $i),
|
||||
'operator' => $op,
|
||||
'default' => substr($content, $i + strlen($op)),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function replaceVariables(string $variable): Stringable
|
||||
{
|
||||
return str($variable)->before('}')->replaceFirst('$', '')->replaceFirst('{', '');
|
||||
// Handle ${VAR} syntax with proper brace matching
|
||||
$str = str($variable);
|
||||
|
||||
// Handle ${VAR} format
|
||||
if ($str->startsWith('${')) {
|
||||
$result = extractBalancedBraceContent($variable, 0);
|
||||
if ($result !== null) {
|
||||
return str($result['content']);
|
||||
}
|
||||
|
||||
// Fallback to old behavior for malformed input
|
||||
return $str->before('}')->replaceFirst('$', '')->replaceFirst('{', '');
|
||||
}
|
||||
|
||||
// Handle {VAR} format (from regex capture group without $)
|
||||
if ($str->startsWith('{') && $str->endsWith('}')) {
|
||||
return str(substr($variable, 1, -1));
|
||||
}
|
||||
|
||||
// Handle {VAR format (from regex capture group, may be truncated)
|
||||
if ($str->startsWith('{')) {
|
||||
$result = extractBalancedBraceContent('$'.$variable, 0);
|
||||
if ($result !== null) {
|
||||
return str($result['content']);
|
||||
}
|
||||
|
||||
// Fallback: remove { and get content before }
|
||||
return $str->replaceFirst('{', '')->before('}');
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase|Application $oneService, bool $isInit = false)
|
||||
|
||||
@@ -3692,3 +3692,55 @@ function verifyPasswordConfirmation(mixed $password, ?Livewire\Component $compon
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract hard-coded environment variables from docker-compose YAML.
|
||||
*
|
||||
* @param string $dockerComposeRaw Raw YAML content
|
||||
* @return \Illuminate\Support\Collection Collection of arrays with: key, value, comment, service_name
|
||||
*/
|
||||
function extractHardcodedEnvironmentVariables(string $dockerComposeRaw): \Illuminate\Support\Collection
|
||||
{
|
||||
if (blank($dockerComposeRaw)) {
|
||||
return collect([]);
|
||||
}
|
||||
|
||||
try {
|
||||
$yaml = \Symfony\Component\Yaml\Yaml::parse($dockerComposeRaw);
|
||||
} catch (\Exception $e) {
|
||||
// Malformed YAML - return empty collection
|
||||
return collect([]);
|
||||
}
|
||||
|
||||
$services = data_get($yaml, 'services', []);
|
||||
if (empty($services)) {
|
||||
return collect([]);
|
||||
}
|
||||
|
||||
// Extract inline comments from raw YAML
|
||||
$envComments = extractYamlEnvironmentComments($dockerComposeRaw);
|
||||
|
||||
$hardcodedVars = collect([]);
|
||||
|
||||
foreach ($services as $serviceName => $service) {
|
||||
$environment = collect(data_get($service, 'environment', []));
|
||||
|
||||
if ($environment->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Convert environment variables to key-value format
|
||||
$environment = convertToKeyValueCollection($environment);
|
||||
|
||||
foreach ($environment as $key => $value) {
|
||||
$hardcodedVars->push([
|
||||
'key' => $key,
|
||||
'value' => $value,
|
||||
'comment' => $envComments[$key] ?? null,
|
||||
'service_name' => $serviceName,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return $hardcodedVars;
|
||||
}
|
||||
Reference in new issue
Block a user