mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-08 11:46:35 +00:00
A swarm docker stack deploy env-file value must be a single physical line; the multi-line GITLAB_OMNIBUS_CONFIG was read as separate whitespace-named env entries and aborted the deploy. Add a depth-aware ruby_single_line filter that collapses the Ruby program to one line (statement-boundary newlines at bracket depth 0 become ';', newlines inside an open bracket or a string become a space so hash/array literals like the OIDC omniauth_providers stay valid) and apply it on the swarm branch only; compose keeps the multi-line value unchanged. Covered by tests/unit/plugins/filter/test_ruby.py (10 cases incl. a realistic omnibus config, escaped quotes and nested literals). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
120 lines
3.2 KiB
Python
120 lines
3.2 KiB
Python
#
|
|
# Ansible filter for safely embedding values into Ruby double-quoted strings.
|
|
#
|
|
# Example:
|
|
# {{ my_password | ruby_dq }}
|
|
#
|
|
# Output:
|
|
# "escaped_value"
|
|
#
|
|
# Safe for GitLab Omnibus (gitlab.rb), Chef, Ruby DSLs.
|
|
|
|
|
|
class FilterModule:
|
|
def filters(self):
|
|
return {
|
|
"ruby_dq": self.ruby_double_quoted_string,
|
|
"ruby_single_line": self.ruby_single_line,
|
|
}
|
|
|
|
@staticmethod
|
|
def ruby_single_line(value):
|
|
"""Collapse a multi-line Ruby program to a single physical line.
|
|
|
|
A swarm ``docker stack deploy`` env-file value MUST be one physical
|
|
line; embedded newlines are read as separate (whitespace-named)
|
|
env entries and abort the deploy. Newlines at bracket depth 0 are
|
|
statement boundaries and become ``;``; newlines inside an open
|
|
``(``/``[``/``{`` (or a quoted string) are continuations and become
|
|
a space, which keeps Ruby hash/array literals valid.
|
|
|
|
Args:
|
|
value: Multi-line Ruby source (e.g. GITLAB_OMNIBUS_CONFIG).
|
|
|
|
Returns:
|
|
The same program as a single line.
|
|
"""
|
|
if value is None:
|
|
return ""
|
|
|
|
s = str(value)
|
|
out = []
|
|
depth = 0
|
|
in_str = False
|
|
quote = ""
|
|
i = 0
|
|
n = len(s)
|
|
while i < n:
|
|
c = s[i]
|
|
if in_str:
|
|
if c == "\\" and i + 1 < n:
|
|
out.append(c)
|
|
out.append(s[i + 1])
|
|
i += 2
|
|
continue
|
|
if c == "\n":
|
|
# A literal newline inside a string would still corrupt the
|
|
# single-line env-file; collapse it to a space.
|
|
out.append(" ")
|
|
i += 1
|
|
continue
|
|
out.append(c)
|
|
if c == quote:
|
|
in_str = False
|
|
i += 1
|
|
continue
|
|
if c in ("'", '"'):
|
|
in_str = True
|
|
quote = c
|
|
out.append(c)
|
|
elif c in "([{":
|
|
depth += 1
|
|
out.append(c)
|
|
elif c in ")]}":
|
|
depth = max(0, depth - 1)
|
|
out.append(c)
|
|
elif c == "\n":
|
|
out.append(";" if depth == 0 else " ")
|
|
else:
|
|
out.append(c)
|
|
i += 1
|
|
return "".join(out)
|
|
|
|
@staticmethod
|
|
def ruby_double_quoted_string(value):
|
|
"""
|
|
Escape a value for use inside a Ruby double-quoted string
|
|
and wrap it in double quotes.
|
|
|
|
Ruby rules handled:
|
|
- " -> \"
|
|
- \\ -> \\\\
|
|
- newlines -> \n
|
|
- carriage return -> \r
|
|
- tab -> \t
|
|
|
|
NOT escaped on purpose:
|
|
- $
|
|
- €
|
|
- !
|
|
- '
|
|
"""
|
|
|
|
if value is None:
|
|
return '""'
|
|
|
|
s = str(value)
|
|
|
|
# Escape backslash first
|
|
s = s.replace("\\", "\\\\")
|
|
|
|
# Escape double quotes
|
|
s = s.replace('"', '\\"')
|
|
|
|
# Escape control characters
|
|
s = s.replace("\n", "\\n")
|
|
s = s.replace("\r", "\\r")
|
|
s = s.replace("\t", "\\t")
|
|
|
|
return f'"{s}"'
|