Repository: ReproNim/neurodocker

Issue:
no longer produces `/neurodocker/startup.sh`  in docker recipes, in effect ignores neurodocker/templates/_header.yaml?
Discovered while working on
- https://github.com/PeerHerholz/BIDSonym/pull/81

although there is [neurodocker/templates/_header.yaml](https://github.com/ReproNim/neurodocker/blob/77cb740e0b524f8cdb8597907472aab13cec9866/neurodocker/templates/_header.yaml#L29) stating that 

```
# Instructions to be run in the beginning of every Dockerfile generated by
# Neurodocker.
```

such promise is not fulfilled:

```shell
$ docker run -i --rm repronim/neurodocker:1.0.0 generate docker --base-image ubuntu:20.04 --pkg-manager apt
# Generated by Neurodocker and Reproenv.

FROM ubuntu:20.04

# Save specification to JSON.
RUN printf '{ \
  "pkg_manager": "apt", \
  "existing_users": [ \
    "root" \
  ], \
  "instructions": [ \
    { \
      "name": "from_", \
      "kwds": { \
        "base_image": "ubuntu:20.04" \
      } \
    } \
  ] \
}' > /.reproenv.json
# End saving to specification to JSON.
```

Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/ReproNim/neurodocker/pull/623?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ReproNim) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 88.06%. Comparing base [(`b7ddf6f`)](https://app.codecov.io/gh/ReproNim/neurodocker/commit/b7ddf6ffe8e2c9bb3d6c738840e7f24340d45570?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ReproNim) to head [(`41a3eef`)](https://app.codecov.io/gh/ReproNim/neurodocker/commit/41a3eef49390465d6a1535135fc7b23d0edbd39d?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ReproNim).
> Report is 20 commits behind head on master.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master     #623      +/-   ##
==========================================
+ Coverage   88.00%   88.06%   +0.05%     
==========================================
  Files          11       11              
  Lines        1034     1039       +5     
==========================================
+ Hits          910      915       +5     
  Misses        124      124              
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/ReproNim/neurodocker/pull/623?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ReproNim).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ReproNim).


Patch:
diff --git a/neurodocker/cli/generate.py b/neurodocker/cli/generate.py
index 2626792..d670d31 100644
--- a/neurodocker/cli/generate.py
+++ b/neurodocker/cli/generate.py
@@ -307,6 +307,8 @@ def _get_params_for_registered_templates() -> list[click.Parameter]:
     names_tmpls = list(registered_templates_items())
     names_tmpls.sort(key=lambda r: r[0])  # sort by name
     for name, tmpl in names_tmpls:
+        if name.startswith("_"):  # Templates starting with _ are private
+            continue
         hlp = _create_help_for_template(Template(tmpl))
         param = OptionEatAll(
             [f"--{name.lower()}"], type=KeyValuePair(), multiple=True, help=hlp
@@ -438,6 +440,18 @@ def _base_generate(
     `generate singularity`. The difference between those two is the renderer used.
     """
     renderer_dict = _params_to_renderer_dict(ctx=ctx, pkg_manager=pkg_manager)
+
+    # Make sure to add default instructions if they are available among the templates
+    if "_default" in registered_templates():
+        # Add header to the instructions, after the base image.
+        renderer_dict["instructions"].insert(1, {"name": "_default", "kwds": {}})
+        # Use default entrypoint if user did not specify one.
+        instruction_names = [instr["name"] for instr in renderer_dict["instructions"]]
+        if "entrypoint" not in instruction_names:
+            renderer_dict["instructions"].append(
+                {"name": "entrypoint", "kwds": {"args": ["/neurodocker/startup.sh"]}}
+            )
+
     r = renderer.from_dict(renderer_dict)
 
     # Print the instructions in JSON if that's what the user wants.
diff --git a/neurodocker/templates/_header.yaml b/neurodocker/templates/_default.yaml
similarity index 69%
rename from neurodocker/templates/_header.yaml
rename to neurodocker/templates/_default.yaml
index 6a09a4b..98a401f 100644
--- a/neurodocker/templates/_header.yaml
+++ b/neurodocker/templates/_default.yaml
@@ -4,7 +4,7 @@
 
 # TODO: read this about locales http://jaredmarkell.com/docker-and-locales/
 
-name: _header
+name: _default
 url: n/a
 # Not actually source, but we do not want to provide URLs.
 source:
@@ -28,14 +28,14 @@ source:
         LC_ALL: en_US.UTF-8
         ND_ENTRYPOINT: /neurodocker/startup.sh
     instructions: |
-        export ND_ENTRYPOINT="{{ _header._env['ND_ENTRYPOINT'] }}"
-        {{ _header.install_dependencies() }}
-        {%- if _header.pkg_manager == "apt" %}
-        sed -i -e 's/# {{ _header._env['LC_ALL'] }} UTF-8/{{ _header._env['LC_ALL'] }} UTF-8/' /etc/locale.gen
+        export ND_ENTRYPOINT="{{ self.env['ND_ENTRYPOINT'] }}"
+        {{ self.install_dependencies() }}
+        {%- if self.pkg_manager == "apt" %}
+        sed -i -e 's/# {{ self.env['LC_ALL'] }} UTF-8/{{ self.env['LC_ALL'] }} UTF-8/' /etc/locale.gen
         dpkg-reconfigure --frontend=noninteractive locales
-        update-locale LANG="{{ _header._env['LANG'] }}"
-        {%- elif _header.pkg_manager == "yum" %}
-        localedef -i {{ _header._env['LC_ALL'].split('.')[0] }} -f {{ _header._env['LC_ALL'].split('.')[1] }} {{ _header._env['LC_ALL'] }}
+        update-locale LANG="{{ self.env['LANG'] }}"
+        {%- elif self.pkg_manager == "yum" %}
+        localedef -i {{ self.env['LC_ALL'].split('.')[0] }} -f {{ self.env['LC_ALL'].split('.')[1] }} {{ self.env['LC_ALL'] }}
         {%- endif %}
         chmod 777 /opt && chmod a+s /opt
         mkdir -p /neurodocker


Test patch:
diff --git a/neurodocker/cli/tests/test_cli.py b/neurodocker/cli/tests/test_cli.py
index ba54339..88dcf47 100644
--- a/neurodocker/cli/tests/test_cli.py
+++ b/neurodocker/cli/tests/test_cli.py
@@ -177,3 +177,43 @@ def test_render_registered(cmd: str, pkg_manager: str):
     assert result.exit_code == 0, result.output
     assert "jq-1.5/jq-linux64" in result.output
     assert "jq-1.6/jq-linux64" in result.output
+
+
+# Test that we add the default header and default/custom entrypoints
+@pytest.mark.parametrize("cmd", _cmds)
+@pytest.mark.parametrize("pkg_manager", ["apt", "yum"])
+@pytest.mark.parametrize("entrypoint", ["default", "custom"])
+def test_default_header_and_entrypoint(cmd: str, pkg_manager: str, entrypoint: str):
+    runner = CliRunner()
+    cmd_ = [
+        cmd,
+        "--base-image",
+        "debian:buster",
+        "--pkg-manager",
+        pkg_manager,
+    ]
+    if entrypoint == "custom":
+        cmd_.extend(["--entrypoint", "I", "decide"])
+    result = runner.invoke(generate, cmd_)
+    assert result.exit_code == 0, result.output
+
+    if cmd == "docker":
+        assert (
+            'ND_ENTRYPOINT="/neurodocker/startup.sh"\n'
+            'RUN export ND_ENTRYPOINT="/neurodocker/startup.sh"'
+        ) in result.output
+
+        if entrypoint == "default":
+            assert '\nENTRYPOINT ["/neurodocker/startup.sh"]\n' in result.output
+        else:
+            assert '\nENTRYPOINT ["I", "decide"]\n' in result.output
+    else:
+        assert (
+            '\nexport ND_ENTRYPOINT="/neurodocker/startup.sh"\n\n'
+            '%post\nexport ND_ENTRYPOINT="/neurodocker/startup.sh"\n'
+        ) in result.output
+
+        if entrypoint == "default":
+            assert "%runscript\n/neurodocker/startup.sh\n" in result.output
+        else:
+            assert "%runscript\nI decide\n" in result.output