gdraheim / docker-systemctl-replacement

docker systemctl replacement - allows to deploy to systemd-controlled containers without starting an actual systemd daemon (e.g. centos7, ubuntu16)
European Union Public License 1.2
1.39k stars 399 forks source link

Multiline Environment= declarations aren't properly parsed #183

Open piotrp opened 2 weeks ago

piotrp commented 2 weeks ago

I am trying to use systemctl3.py with services that need long environment variables with newlines, and I noticed that newlines aren't supported.

Example from my service:

Environment="FLINK_ENV_JAVA_OPTS_JM=-Dcom.sun.management.jmxremote \
              -Dcom.sun.management.jmxremote.port=9951 \
              -Dcom.sun.management.jmxremote.rmi.port=9951 \
              -Dcom.sun.management.jmxremote.authenticate=false \
              -Dcom.sun.management.jmxremote.ssl=false \
              -Dcom.sun.management.jmxremote.local.only=false \
              -Djava.rmi.server.hostname=172.20.128.132"

A shorter standalone test case:

[Unit]
Description=Env test

[Service]
Environment="MULTILINE=line1 \
  line2"
ExecStart=/usr/bin/env

[Install]
WantedBy=default.target

Output logged after starting this service:

...
OLDPWD=/root
MULTILINE=line1
MAINPID=
...

A quick test with additional logging in read_env_part (logg.info("env part: %s", env_part)) shows that the value is read correctly, and the only issue is with the way that read_env_part interprets this data:

$ systemctl --user -vv start envtest
INFO:systemctl:EXEC BEGIN /usr/bin/systemctl start envtest --user
INFO:systemctl:env part: "MULTILINE=line1 \
  line2"

INFO:systemctl:simple start '/usr/bin/env'
piotrp commented 2 weeks ago

I did a quick test and looks like this read_env_part implementation works better:

            for found in re.finditer(r'\s*("[\w_]+=[^"]*"|[\w_]+=\S*)', env_part.strip()):
                part = found.group(1)
                if part.startswith('"'):
                    part = part[1:-1]
                name, value = part.split("=", 1)
                yield name, value.replace("\\\n", "")

Replacing at the end is needed because \ at the end of line is meant to remove the newline character and join lines.