feat: add function to extract inline comments from docker-compose YAML environment variables
This commit is contained in:
parent
e4cc5c1178
commit
89192c9862
3 changed files with 586 additions and 14 deletions
|
|
@ -1411,6 +1411,9 @@ function serviceParser(Service $resource): Collection
|
|||
return collect([]);
|
||||
}
|
||||
|
||||
// Extract inline comments from raw YAML before Symfony parser discards them
|
||||
$envComments = extractYamlEnvironmentComments($compose);
|
||||
|
||||
$server = data_get($resource, 'server');
|
||||
$allServices = get_service_templates();
|
||||
|
||||
|
|
@ -1694,51 +1697,60 @@ function serviceParser(Service $resource): Collection
|
|||
}
|
||||
|
||||
// ALWAYS create BOTH base SERVICE_URL and SERVICE_FQDN pairs (without port)
|
||||
$fqdnKey = "SERVICE_FQDN_{$serviceName}";
|
||||
$resource->environment_variables()->updateOrCreate([
|
||||
'key' => "SERVICE_FQDN_{$serviceName}",
|
||||
'key' => $fqdnKey,
|
||||
'resourceable_type' => get_class($resource),
|
||||
'resourceable_id' => $resource->id,
|
||||
], [
|
||||
'value' => $fqdnValueForEnv,
|
||||
'is_preview' => false,
|
||||
'comment' => $envComments[$fqdnKey] ?? null,
|
||||
]);
|
||||
|
||||
$urlKey = "SERVICE_URL_{$serviceName}";
|
||||
$resource->environment_variables()->updateOrCreate([
|
||||
'key' => "SERVICE_URL_{$serviceName}",
|
||||
'key' => $urlKey,
|
||||
'resourceable_type' => get_class($resource),
|
||||
'resourceable_id' => $resource->id,
|
||||
], [
|
||||
'value' => $url,
|
||||
'is_preview' => false,
|
||||
'comment' => $envComments[$urlKey] ?? null,
|
||||
]);
|
||||
|
||||
// For port-specific variables, ALSO create port-specific pairs
|
||||
// If template variable has port, create both URL and FQDN with port suffix
|
||||
if ($parsed['has_port'] && $port) {
|
||||
$fqdnPortKey = "SERVICE_FQDN_{$serviceName}_{$port}";
|
||||
$resource->environment_variables()->updateOrCreate([
|
||||
'key' => "SERVICE_FQDN_{$serviceName}_{$port}",
|
||||
'key' => $fqdnPortKey,
|
||||
'resourceable_type' => get_class($resource),
|
||||
'resourceable_id' => $resource->id,
|
||||
], [
|
||||
'value' => $fqdnValueForEnvWithPort,
|
||||
'is_preview' => false,
|
||||
'comment' => $envComments[$fqdnPortKey] ?? null,
|
||||
]);
|
||||
|
||||
$urlPortKey = "SERVICE_URL_{$serviceName}_{$port}";
|
||||
$resource->environment_variables()->updateOrCreate([
|
||||
'key' => "SERVICE_URL_{$serviceName}_{$port}",
|
||||
'key' => $urlPortKey,
|
||||
'resourceable_type' => get_class($resource),
|
||||
'resourceable_id' => $resource->id,
|
||||
], [
|
||||
'value' => $urlWithPort,
|
||||
'is_preview' => false,
|
||||
'comment' => $envComments[$urlPortKey] ?? null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
$allMagicEnvironments = $allMagicEnvironments->merge($magicEnvironments);
|
||||
if ($magicEnvironments->count() > 0) {
|
||||
foreach ($magicEnvironments as $key => $value) {
|
||||
$key = str($key);
|
||||
foreach ($magicEnvironments as $magicKey => $value) {
|
||||
$originalMagicKey = $magicKey; // Preserve original key for comment lookup
|
||||
$key = str($magicKey);
|
||||
$value = replaceVariables($value);
|
||||
$command = parseCommandFromMagicEnvVariable($key);
|
||||
if ($command->value() === 'FQDN') {
|
||||
|
|
@ -1762,13 +1774,14 @@ function serviceParser(Service $resource): Collection
|
|||
$serviceExists->fqdn = $url;
|
||||
$serviceExists->save();
|
||||
}
|
||||
$resource->environment_variables()->firstOrCreate([
|
||||
$resource->environment_variables()->updateOrCreate([
|
||||
'key' => $key->value(),
|
||||
'resourceable_type' => get_class($resource),
|
||||
'resourceable_id' => $resource->id,
|
||||
], [
|
||||
'value' => $fqdn,
|
||||
'is_preview' => false,
|
||||
'comment' => $envComments[$originalMagicKey] ?? null,
|
||||
]);
|
||||
|
||||
} elseif ($command->value() === 'URL') {
|
||||
|
|
@ -1790,24 +1803,26 @@ function serviceParser(Service $resource): Collection
|
|||
$serviceExists->fqdn = $url;
|
||||
$serviceExists->save();
|
||||
}
|
||||
$resource->environment_variables()->firstOrCreate([
|
||||
$resource->environment_variables()->updateOrCreate([
|
||||
'key' => $key->value(),
|
||||
'resourceable_type' => get_class($resource),
|
||||
'resourceable_id' => $resource->id,
|
||||
], [
|
||||
'value' => $url,
|
||||
'is_preview' => false,
|
||||
'comment' => $envComments[$originalMagicKey] ?? null,
|
||||
]);
|
||||
|
||||
} else {
|
||||
$value = generateEnvValue($command, $resource);
|
||||
$resource->environment_variables()->firstOrCreate([
|
||||
$resource->environment_variables()->updateOrCreate([
|
||||
'key' => $key->value(),
|
||||
'resourceable_type' => get_class($resource),
|
||||
'resourceable_id' => $resource->id,
|
||||
], [
|
||||
'value' => $value,
|
||||
'is_preview' => false,
|
||||
'comment' => $envComments[$originalMagicKey] ?? null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -2163,18 +2178,20 @@ function serviceParser(Service $resource): Collection
|
|||
return ! str($value)->startsWith('SERVICE_');
|
||||
});
|
||||
foreach ($normalEnvironments as $key => $value) {
|
||||
$originalKey = $key; // Preserve original key for comment lookup
|
||||
$key = str($key);
|
||||
$value = str($value);
|
||||
$originalValue = $value;
|
||||
$parsedValue = replaceVariables($value);
|
||||
if ($parsedValue->startsWith('SERVICE_')) {
|
||||
$resource->environment_variables()->firstOrCreate([
|
||||
$resource->environment_variables()->updateOrCreate([
|
||||
'key' => $key,
|
||||
'resourceable_type' => get_class($resource),
|
||||
'resourceable_id' => $resource->id,
|
||||
], [
|
||||
'value' => $value,
|
||||
'is_preview' => false,
|
||||
'comment' => $envComments[$originalKey] ?? null,
|
||||
]);
|
||||
|
||||
continue;
|
||||
|
|
@ -2184,13 +2201,14 @@ function serviceParser(Service $resource): Collection
|
|||
}
|
||||
if ($key->value() === $parsedValue->value()) {
|
||||
$value = null;
|
||||
$resource->environment_variables()->firstOrCreate([
|
||||
$resource->environment_variables()->updateOrCreate([
|
||||
'key' => $key,
|
||||
'resourceable_type' => get_class($resource),
|
||||
'resourceable_id' => $resource->id,
|
||||
], [
|
||||
'value' => $value,
|
||||
'is_preview' => false,
|
||||
'comment' => $envComments[$originalKey] ?? null,
|
||||
]);
|
||||
} else {
|
||||
if ($value->startsWith('$')) {
|
||||
|
|
@ -2220,20 +2238,21 @@ function serviceParser(Service $resource): Collection
|
|||
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);
|
||||
$resource->environment_variables()->firstOrCreate([
|
||||
$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()->firstOrCreate([
|
||||
$resource->environment_variables()->updateOrCreate([
|
||||
'key' => $key,
|
||||
'resourceable_type' => get_class($resource),
|
||||
'resourceable_id' => $resource->id,
|
||||
|
|
@ -2241,6 +2260,7 @@ function serviceParser(Service $resource): Collection
|
|||
'value' => $value,
|
||||
'is_preview' => false,
|
||||
'is_required' => $isRequired,
|
||||
'comment' => $envComments[$originalKey] ?? null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -512,6 +512,215 @@ function parseEnvFormatToArray($env_file_contents)
|
|||
return $env_array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract inline comments from environment variables in raw docker-compose YAML.
|
||||
*
|
||||
* Parses raw docker-compose YAML to extract inline comments from environment sections.
|
||||
* Standard YAML parsers discard comments, so this pre-processes the raw text.
|
||||
*
|
||||
* Handles both formats:
|
||||
* - Map format: `KEY: "value" # comment` or `KEY: value # comment`
|
||||
* - Array format: `- KEY=value # comment`
|
||||
*
|
||||
* @param string $rawYaml The raw docker-compose.yml content
|
||||
* @return array Map of environment variable keys to their inline comments
|
||||
*/
|
||||
function extractYamlEnvironmentComments(string $rawYaml): array
|
||||
{
|
||||
$comments = [];
|
||||
$lines = explode("\n", $rawYaml);
|
||||
$inEnvironmentBlock = false;
|
||||
$environmentIndent = 0;
|
||||
|
||||
foreach ($lines as $line) {
|
||||
// Skip empty lines
|
||||
if (trim($line) === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate current line's indentation (number of leading spaces)
|
||||
$currentIndent = strlen($line) - strlen(ltrim($line));
|
||||
|
||||
// Check if this line starts an environment block
|
||||
if (preg_match('/^(\s*)environment\s*:\s*$/', $line, $matches)) {
|
||||
$inEnvironmentBlock = true;
|
||||
$environmentIndent = strlen($matches[1]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this line starts an environment block with inline content (rare but possible)
|
||||
if (preg_match('/^(\s*)environment\s*:\s*\{/', $line)) {
|
||||
// Inline object format - not supported for comment extraction
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we're in an environment block, check if we've exited it
|
||||
if ($inEnvironmentBlock) {
|
||||
// If we hit a line with same or less indentation that's not empty, we've left the block
|
||||
// Unless it's a continuation of the environment block
|
||||
$trimmedLine = ltrim($line);
|
||||
|
||||
// Check if this is a new top-level key (same indent as 'environment:' or less)
|
||||
if ($currentIndent <= $environmentIndent && ! str_starts_with($trimmedLine, '-') && ! str_starts_with($trimmedLine, '#')) {
|
||||
// Check if it looks like a YAML key (contains : not inside quotes)
|
||||
if (preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*\s*:/', $trimmedLine)) {
|
||||
$inEnvironmentBlock = false;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip comment-only lines
|
||||
if (str_starts_with($trimmedLine, '#')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to extract environment variable and comment from this line
|
||||
$extracted = extractEnvVarCommentFromYamlLine($trimmedLine);
|
||||
if ($extracted !== null && $extracted['comment'] !== null) {
|
||||
$comments[$extracted['key']] = $extracted['comment'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $comments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract environment variable key and inline comment from a single YAML line.
|
||||
*
|
||||
* @param string $line A trimmed line from the environment section
|
||||
* @return array|null Array with 'key' and 'comment', or null if not an env var line
|
||||
*/
|
||||
function extractEnvVarCommentFromYamlLine(string $line): ?array
|
||||
{
|
||||
$key = null;
|
||||
$comment = null;
|
||||
|
||||
// Handle array format: `- KEY=value # comment` or `- KEY # comment`
|
||||
if (str_starts_with($line, '-')) {
|
||||
$content = ltrim(substr($line, 1));
|
||||
|
||||
// Check for KEY=value format
|
||||
if (preg_match('/^([A-Za-z_][A-Za-z0-9_]*)/', $content, $keyMatch)) {
|
||||
$key = $keyMatch[1];
|
||||
// Find comment - need to handle quoted values
|
||||
$comment = extractCommentAfterValue($content);
|
||||
}
|
||||
}
|
||||
// Handle map format: `KEY: "value" # comment` or `KEY: value # comment`
|
||||
elseif (preg_match('/^([A-Za-z_][A-Za-z0-9_]*)\s*:/', $line, $keyMatch)) {
|
||||
$key = $keyMatch[1];
|
||||
// Get everything after the key and colon
|
||||
$afterKey = substr($line, strlen($keyMatch[0]));
|
||||
$comment = extractCommentAfterValue($afterKey);
|
||||
}
|
||||
|
||||
if ($key === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'key' => $key,
|
||||
'comment' => $comment,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract inline comment from a value portion of a YAML line.
|
||||
*
|
||||
* Handles quoted values (where # inside quotes is not a comment).
|
||||
*
|
||||
* @param string $valueAndComment The value portion (may include comment)
|
||||
* @return string|null The comment text, or null if no comment
|
||||
*/
|
||||
function extractCommentAfterValue(string $valueAndComment): ?string
|
||||
{
|
||||
$valueAndComment = ltrim($valueAndComment);
|
||||
|
||||
if ($valueAndComment === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$firstChar = $valueAndComment[0] ?? '';
|
||||
|
||||
// Handle case where value is empty and line starts directly with comment
|
||||
// e.g., `KEY: # comment` becomes `# comment` after ltrim
|
||||
if ($firstChar === '#') {
|
||||
$comment = trim(substr($valueAndComment, 1));
|
||||
|
||||
return $comment !== '' ? $comment : null;
|
||||
}
|
||||
|
||||
// Handle double-quoted value
|
||||
if ($firstChar === '"') {
|
||||
// Find closing quote (handle escaped quotes)
|
||||
$pos = 1;
|
||||
$len = strlen($valueAndComment);
|
||||
while ($pos < $len) {
|
||||
if ($valueAndComment[$pos] === '\\' && $pos + 1 < $len) {
|
||||
$pos += 2; // Skip escaped character
|
||||
|
||||
continue;
|
||||
}
|
||||
if ($valueAndComment[$pos] === '"') {
|
||||
// Found closing quote
|
||||
$remainder = substr($valueAndComment, $pos + 1);
|
||||
|
||||
return extractCommentFromRemainder($remainder);
|
||||
}
|
||||
$pos++;
|
||||
}
|
||||
|
||||
// No closing quote found
|
||||
return null;
|
||||
}
|
||||
|
||||
// Handle single-quoted value
|
||||
if ($firstChar === "'") {
|
||||
// Find closing quote (single quotes don't have escapes in YAML)
|
||||
$closingPos = strpos($valueAndComment, "'", 1);
|
||||
if ($closingPos !== false) {
|
||||
$remainder = substr($valueAndComment, $closingPos + 1);
|
||||
|
||||
return extractCommentFromRemainder($remainder);
|
||||
}
|
||||
|
||||
// No closing quote found
|
||||
return null;
|
||||
}
|
||||
|
||||
// Unquoted value - find # that's preceded by whitespace
|
||||
// Be careful not to match # at the start of a value like color codes
|
||||
if (preg_match('/\s+#\s*(.*)$/', $valueAndComment, $matches)) {
|
||||
$comment = trim($matches[1]);
|
||||
|
||||
return $comment !== '' ? $comment : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract comment from the remainder of a line after a quoted value.
|
||||
*
|
||||
* @param string $remainder Text after the closing quote
|
||||
* @return string|null The comment text, or null if no comment
|
||||
*/
|
||||
function extractCommentFromRemainder(string $remainder): ?string
|
||||
{
|
||||
// Look for # in remainder
|
||||
$hashPos = strpos($remainder, '#');
|
||||
if ($hashPos !== false) {
|
||||
$comment = trim(substr($remainder, $hashPos + 1));
|
||||
|
||||
return $comment !== '' ? $comment : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function data_get_str($data, $key, $default = null): Stringable
|
||||
{
|
||||
$str = data_get($data, $key, $default) ?? $default;
|
||||
|
|
@ -1345,6 +1554,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||
{
|
||||
if ($resource->getMorphClass() === \App\Models\Service::class) {
|
||||
if ($resource->docker_compose_raw) {
|
||||
// Extract inline comments from raw YAML before Symfony parser discards them
|
||||
$envComments = extractYamlEnvironmentComments($resource->docker_compose_raw);
|
||||
|
||||
try {
|
||||
$yaml = Yaml::parse($resource->docker_compose_raw);
|
||||
} catch (\Exception $e) {
|
||||
|
|
@ -1376,7 +1588,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||
}
|
||||
$topLevelVolumes = collect($tempTopLevelVolumes);
|
||||
}
|
||||
$services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS, $resource, $allServices) {
|
||||
$services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS, $resource, $allServices, $envComments) {
|
||||
// Workarounds for beta users.
|
||||
if ($serviceName === 'registry') {
|
||||
$tempServiceName = 'docker-registry';
|
||||
|
|
@ -1722,6 +1934,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||
$key = str($variableName);
|
||||
$value = str($variable);
|
||||
}
|
||||
// Preserve original key for comment lookup before $key might be reassigned
|
||||
$originalKey = $key->value();
|
||||
if ($key->startsWith('SERVICE_FQDN')) {
|
||||
if ($isNew || $savedService->fqdn === null) {
|
||||
$name = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower();
|
||||
|
|
@ -1775,6 +1989,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||
'resourceable_type' => get_class($resource),
|
||||
'resourceable_id' => $resource->id,
|
||||
'is_preview' => false,
|
||||
'comment' => $envComments[$originalKey] ?? null,
|
||||
]);
|
||||
}
|
||||
// Caddy needs exact port in some cases.
|
||||
|
|
@ -1854,6 +2069,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||
'resourceable_type' => get_class($resource),
|
||||
'resourceable_id' => $resource->id,
|
||||
'is_preview' => false,
|
||||
'comment' => $envComments[$originalKey] ?? null,
|
||||
]);
|
||||
}
|
||||
if (! $isDatabase) {
|
||||
|
|
@ -1892,6 +2108,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||
'resourceable_type' => get_class($resource),
|
||||
'resourceable_id' => $resource->id,
|
||||
'is_preview' => false,
|
||||
'comment' => $envComments[$originalKey] ?? null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -1930,6 +2147,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||
'resourceable_type' => get_class($resource),
|
||||
'resourceable_id' => $resource->id,
|
||||
'is_preview' => false,
|
||||
'comment' => $envComments[$originalKey] ?? null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
334
tests/Unit/ExtractYamlEnvironmentCommentsTest.php
Normal file
334
tests/Unit/ExtractYamlEnvironmentCommentsTest.php
Normal file
|
|
@ -0,0 +1,334 @@
|
|||
<?php
|
||||
|
||||
test('extractYamlEnvironmentComments returns empty array for YAML without environment section', function () {
|
||||
$yaml = <<<'YAML'
|
||||
version: "3.8"
|
||||
services:
|
||||
web:
|
||||
image: nginx:latest
|
||||
ports:
|
||||
- "80:80"
|
||||
YAML;
|
||||
|
||||
$result = extractYamlEnvironmentComments($yaml);
|
||||
|
||||
expect($result)->toBe([]);
|
||||
});
|
||||
|
||||
test('extractYamlEnvironmentComments extracts inline comments from map format', function () {
|
||||
$yaml = <<<'YAML'
|
||||
version: "3.8"
|
||||
services:
|
||||
web:
|
||||
image: nginx:latest
|
||||
environment:
|
||||
FOO: bar # This is a comment
|
||||
BAZ: qux
|
||||
YAML;
|
||||
|
||||
$result = extractYamlEnvironmentComments($yaml);
|
||||
|
||||
expect($result)->toBe([
|
||||
'FOO' => 'This is a comment',
|
||||
]);
|
||||
});
|
||||
|
||||
test('extractYamlEnvironmentComments extracts inline comments from array format', function () {
|
||||
$yaml = <<<'YAML'
|
||||
version: "3.8"
|
||||
services:
|
||||
web:
|
||||
image: nginx:latest
|
||||
environment:
|
||||
- FOO=bar # This is a comment
|
||||
- BAZ=qux
|
||||
YAML;
|
||||
|
||||
$result = extractYamlEnvironmentComments($yaml);
|
||||
|
||||
expect($result)->toBe([
|
||||
'FOO' => 'This is a comment',
|
||||
]);
|
||||
});
|
||||
|
||||
test('extractYamlEnvironmentComments handles quoted values containing hash symbols', function () {
|
||||
$yaml = <<<'YAML'
|
||||
version: "3.8"
|
||||
services:
|
||||
web:
|
||||
image: nginx:latest
|
||||
environment:
|
||||
COLOR: "#FF0000" # hex color code
|
||||
DB_URL: "postgres://user:pass#123@localhost" # database URL
|
||||
PLAIN: value # no quotes
|
||||
YAML;
|
||||
|
||||
$result = extractYamlEnvironmentComments($yaml);
|
||||
|
||||
expect($result)->toBe([
|
||||
'COLOR' => 'hex color code',
|
||||
'DB_URL' => 'database URL',
|
||||
'PLAIN' => 'no quotes',
|
||||
]);
|
||||
});
|
||||
|
||||
test('extractYamlEnvironmentComments handles single quoted values containing hash symbols', function () {
|
||||
$yaml = <<<'YAML'
|
||||
version: "3.8"
|
||||
services:
|
||||
web:
|
||||
image: nginx:latest
|
||||
environment:
|
||||
PASSWORD: 'secret#123' # my password
|
||||
YAML;
|
||||
|
||||
$result = extractYamlEnvironmentComments($yaml);
|
||||
|
||||
expect($result)->toBe([
|
||||
'PASSWORD' => 'my password',
|
||||
]);
|
||||
});
|
||||
|
||||
test('extractYamlEnvironmentComments skips full-line comments', function () {
|
||||
$yaml = <<<'YAML'
|
||||
version: "3.8"
|
||||
services:
|
||||
web:
|
||||
image: nginx:latest
|
||||
environment:
|
||||
# This is a full line comment
|
||||
FOO: bar # This is an inline comment
|
||||
# Another full line comment
|
||||
BAZ: qux
|
||||
YAML;
|
||||
|
||||
$result = extractYamlEnvironmentComments($yaml);
|
||||
|
||||
expect($result)->toBe([
|
||||
'FOO' => 'This is an inline comment',
|
||||
]);
|
||||
});
|
||||
|
||||
test('extractYamlEnvironmentComments handles multiple services', function () {
|
||||
$yaml = <<<'YAML'
|
||||
version: "3.8"
|
||||
services:
|
||||
web:
|
||||
image: nginx:latest
|
||||
environment:
|
||||
WEB_PORT: 8080 # web server port
|
||||
db:
|
||||
image: postgres:15
|
||||
environment:
|
||||
POSTGRES_USER: admin # database admin user
|
||||
POSTGRES_PASSWORD: secret
|
||||
YAML;
|
||||
|
||||
$result = extractYamlEnvironmentComments($yaml);
|
||||
|
||||
expect($result)->toBe([
|
||||
'WEB_PORT' => 'web server port',
|
||||
'POSTGRES_USER' => 'database admin user',
|
||||
]);
|
||||
});
|
||||
|
||||
test('extractYamlEnvironmentComments handles variables without values', function () {
|
||||
$yaml = <<<'YAML'
|
||||
version: "3.8"
|
||||
services:
|
||||
web:
|
||||
image: nginx:latest
|
||||
environment:
|
||||
- DEBUG # enable debug mode
|
||||
- VERBOSE
|
||||
YAML;
|
||||
|
||||
$result = extractYamlEnvironmentComments($yaml);
|
||||
|
||||
expect($result)->toBe([
|
||||
'DEBUG' => 'enable debug mode',
|
||||
]);
|
||||
});
|
||||
|
||||
test('extractYamlEnvironmentComments handles array format with colons', function () {
|
||||
$yaml = <<<'YAML'
|
||||
version: "3.8"
|
||||
services:
|
||||
web:
|
||||
image: nginx:latest
|
||||
environment:
|
||||
- DATABASE_URL: postgres://localhost # connection string
|
||||
YAML;
|
||||
|
||||
$result = extractYamlEnvironmentComments($yaml);
|
||||
|
||||
expect($result)->toBe([
|
||||
'DATABASE_URL' => 'connection string',
|
||||
]);
|
||||
});
|
||||
|
||||
test('extractYamlEnvironmentComments does not treat hash inside unquoted values as comment start', function () {
|
||||
$yaml = <<<'YAML'
|
||||
version: "3.8"
|
||||
services:
|
||||
web:
|
||||
image: nginx:latest
|
||||
environment:
|
||||
API_KEY: abc#def
|
||||
OTHER: xyz # this is a comment
|
||||
YAML;
|
||||
|
||||
$result = extractYamlEnvironmentComments($yaml);
|
||||
|
||||
// abc#def has no space before #, so it's not treated as a comment
|
||||
expect($result)->toBe([
|
||||
'OTHER' => 'this is a comment',
|
||||
]);
|
||||
});
|
||||
|
||||
test('extractYamlEnvironmentComments handles empty environment section', function () {
|
||||
$yaml = <<<'YAML'
|
||||
version: "3.8"
|
||||
services:
|
||||
web:
|
||||
image: nginx:latest
|
||||
environment:
|
||||
ports:
|
||||
- "80:80"
|
||||
YAML;
|
||||
|
||||
$result = extractYamlEnvironmentComments($yaml);
|
||||
|
||||
expect($result)->toBe([]);
|
||||
});
|
||||
|
||||
test('extractYamlEnvironmentComments handles environment inline format (not supported)', function () {
|
||||
// Inline format like environment: { FOO: bar } is not supported for comment extraction
|
||||
$yaml = <<<'YAML'
|
||||
version: "3.8"
|
||||
services:
|
||||
web:
|
||||
image: nginx:latest
|
||||
environment: { FOO: bar }
|
||||
YAML;
|
||||
|
||||
$result = extractYamlEnvironmentComments($yaml);
|
||||
|
||||
// No comments extracted from inline format
|
||||
expect($result)->toBe([]);
|
||||
});
|
||||
|
||||
test('extractYamlEnvironmentComments handles complex real-world docker-compose', function () {
|
||||
$yaml = <<<'YAML'
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: myapp:latest
|
||||
environment:
|
||||
NODE_ENV: production # Set to development for local
|
||||
DATABASE_URL: "postgres://user:pass@db:5432/mydb" # Main database
|
||||
REDIS_URL: "redis://cache:6379"
|
||||
API_SECRET: "${API_SECRET}" # From .env file
|
||||
LOG_LEVEL: debug # Options: debug, info, warn, error
|
||||
ports:
|
||||
- "3000:3000"
|
||||
|
||||
db:
|
||||
image: postgres:15
|
||||
environment:
|
||||
POSTGRES_USER: user # Database admin username
|
||||
POSTGRES_PASSWORD: "${DB_PASSWORD}"
|
||||
POSTGRES_DB: mydb
|
||||
|
||||
cache:
|
||||
image: redis:7
|
||||
environment:
|
||||
- REDIS_MAXMEMORY=256mb # Memory limit for cache
|
||||
YAML;
|
||||
|
||||
$result = extractYamlEnvironmentComments($yaml);
|
||||
|
||||
expect($result)->toBe([
|
||||
'NODE_ENV' => 'Set to development for local',
|
||||
'DATABASE_URL' => 'Main database',
|
||||
'API_SECRET' => 'From .env file',
|
||||
'LOG_LEVEL' => 'Options: debug, info, warn, error',
|
||||
'POSTGRES_USER' => 'Database admin username',
|
||||
'REDIS_MAXMEMORY' => 'Memory limit for cache',
|
||||
]);
|
||||
});
|
||||
|
||||
test('extractYamlEnvironmentComments handles comment with multiple hash symbols', function () {
|
||||
$yaml = <<<'YAML'
|
||||
version: "3.8"
|
||||
services:
|
||||
web:
|
||||
environment:
|
||||
FOO: bar # comment # with # hashes
|
||||
YAML;
|
||||
|
||||
$result = extractYamlEnvironmentComments($yaml);
|
||||
|
||||
expect($result)->toBe([
|
||||
'FOO' => 'comment # with # hashes',
|
||||
]);
|
||||
});
|
||||
|
||||
test('extractYamlEnvironmentComments handles variables with empty comments', function () {
|
||||
$yaml = <<<'YAML'
|
||||
version: "3.8"
|
||||
services:
|
||||
web:
|
||||
environment:
|
||||
FOO: bar #
|
||||
BAZ: qux #
|
||||
YAML;
|
||||
|
||||
$result = extractYamlEnvironmentComments($yaml);
|
||||
|
||||
// Empty comments should not be included
|
||||
expect($result)->toBe([]);
|
||||
});
|
||||
|
||||
test('extractYamlEnvironmentComments properly exits environment block on new section', function () {
|
||||
$yaml = <<<'YAML'
|
||||
version: "3.8"
|
||||
services:
|
||||
web:
|
||||
image: nginx:latest
|
||||
environment:
|
||||
FOO: bar # env comment
|
||||
ports:
|
||||
- "80:80" # port comment should not be captured
|
||||
volumes:
|
||||
- ./data:/data # volume comment should not be captured
|
||||
YAML;
|
||||
|
||||
$result = extractYamlEnvironmentComments($yaml);
|
||||
|
||||
// Only environment variables should have comments extracted
|
||||
expect($result)->toBe([
|
||||
'FOO' => 'env comment',
|
||||
]);
|
||||
});
|
||||
|
||||
test('extractYamlEnvironmentComments handles SERVICE_ variables', function () {
|
||||
$yaml = <<<'YAML'
|
||||
version: "3.8"
|
||||
services:
|
||||
web:
|
||||
environment:
|
||||
SERVICE_FQDN_WEB: /api # Path for the web service
|
||||
SERVICE_URL_WEB: # URL will be generated
|
||||
NORMAL_VAR: value # Regular variable
|
||||
YAML;
|
||||
|
||||
$result = extractYamlEnvironmentComments($yaml);
|
||||
|
||||
expect($result)->toBe([
|
||||
'SERVICE_FQDN_WEB' => 'Path for the web service',
|
||||
'SERVICE_URL_WEB' => 'URL will be generated',
|
||||
'NORMAL_VAR' => 'Regular variable',
|
||||
]);
|
||||
});
|
||||
Loading…
Reference in a new issue