From cab6fce923f1c530291aa52320ac73e1bfab1fb6 Mon Sep 17 00:00:00 2001 From: Simon Li Date: Sun, 22 Jun 2025 14:22:51 +0100 Subject: [PATCH 01/11] Move runtime.txt handling into base class This will allow it to be used for Python (and other languages) in future --- repo2docker/buildpacks/base.py | 39 ++++++++++++++++++++++ repo2docker/buildpacks/pipfile/__init__.py | 9 ++--- repo2docker/buildpacks/python/__init__.py | 22 ++++-------- repo2docker/buildpacks/r.py | 37 ++++++++++---------- tests/unit/test_buildpack.py | 29 +++++++++++++++- 5 files changed, 94 insertions(+), 42 deletions(-) diff --git a/repo2docker/buildpacks/base.py b/repo2docker/buildpacks/base.py index f433029e..4a6a9417 100644 --- a/repo2docker/buildpacks/base.py +++ b/repo2docker/buildpacks/base.py @@ -1,3 +1,4 @@ +import datetime import hashlib import io import logging @@ -750,3 +751,41 @@ class BaseImage(BuildPack): # the only path evaluated at container start time rather than build time return os.path.join("${REPO_DIR}", start) return None + + @property + def runtime_info(self): + """ + Return parsed contents of runtime.txt + + Returns (runtime, version, date), tuple components may be None. + Returns (None, None, None) if runtime.txt not found. + """ + if hasattr(self, "_runtime"): + return self._runtime + + self._runtime = (None, None, None) + + runtime_path = self.binder_path("runtime.txt") + try: + with open(runtime_path) as f: + runtime_txt = f.read().strip() + except FileNotFoundError: + return self._runtime + + runtime = None + version = None + date = None + + parts = runtime_txt.split("-", 2) + if len(parts) > 0 and parts[0]: + runtime = parts[0] + if len(parts) > 1 and parts[1]: + version = parts[1] + if len(parts) > 2 and parts[2]: + date = parts[2] + if not re.match(r"\d\d\d\d-\d\d-\d\d", date): + raise ValueError(f"Invalid date, expected YYYY-MM-DD: {date}") + date = datetime.datetime.fromisoformat(date).date() + + self._runtime = (runtime, version, date) + return self._runtime diff --git a/repo2docker/buildpacks/pipfile/__init__.py b/repo2docker/buildpacks/pipfile/__init__.py index 2e24d614..c816e88e 100644 --- a/repo2docker/buildpacks/pipfile/__init__.py +++ b/repo2docker/buildpacks/pipfile/__init__.py @@ -187,12 +187,9 @@ class PipfileBuildPack(CondaBuildPack): def detect(self): """Check if current repo should be built with the Pipfile buildpack.""" # first make sure python is not explicitly unwanted - runtime_txt = self.binder_path("runtime.txt") - if os.path.exists(runtime_txt): - with open(runtime_txt) as f: - runtime = f.read().strip() - if not runtime.startswith("python-"): - return False + runtime = self.runtime_info[0] + if runtime and runtime != "python": + return False pipfile = self.binder_path("Pipfile") pipfile_lock = self.binder_path("Pipfile.lock") diff --git a/repo2docker/buildpacks/python/__init__.py b/repo2docker/buildpacks/python/__init__.py index 1530e347..3b33fe31 100644 --- a/repo2docker/buildpacks/python/__init__.py +++ b/repo2docker/buildpacks/python/__init__.py @@ -15,14 +15,10 @@ class PythonBuildPack(CondaBuildPack): if hasattr(self, "_python_version"): return self._python_version - try: - with open(self.binder_path("runtime.txt")) as f: - runtime = f.read().strip() - except FileNotFoundError: - runtime = "" + runtime, version, _ = self.runtime_info - if not runtime.startswith("python-"): - # not a Python runtime (e.g. R, which subclasses this) + if runtime != "python" or not version: + # Either not specified, or not a Python runtime (e.g. R, which subclasses this) # use the default Python self._python_version = self.major_pythons["3"] self.log.warning( @@ -30,7 +26,7 @@ class PythonBuildPack(CondaBuildPack): ) return self._python_version - py_version_info = runtime.split("-", 1)[1].split(".") + py_version_info = version.split(".") py_version = "" if len(py_version_info) == 1: py_version = self.major_pythons[py_version_info[0]] @@ -138,16 +134,10 @@ class PythonBuildPack(CondaBuildPack): def detect(self): """Check if current repo should be built with the Python buildpack.""" requirements_txt = self.binder_path("requirements.txt") - runtime_txt = self.binder_path("runtime.txt") setup_py = "setup.py" - if os.path.exists(runtime_txt): - with open(runtime_txt) as f: - runtime = f.read().strip() - if runtime.startswith("python-"): - return True - else: - return False + if self.runtime_info[0]: + return self.runtime_info[0] == "python" if not self.binder_dir and os.path.exists(setup_py): return True return os.path.exists(requirements_txt) diff --git a/repo2docker/buildpacks/r.py b/repo2docker/buildpacks/r.py index 93148a94..447ce84c 100644 --- a/repo2docker/buildpacks/r.py +++ b/repo2docker/buildpacks/r.py @@ -1,6 +1,6 @@ import datetime import os -import re +import warnings from functools import lru_cache import requests @@ -49,7 +49,14 @@ class RBuildPack(PythonBuildPack): def runtime(self): """ Return contents of runtime.txt if it exists, '' otherwise + + Deprecated, use `runtime_info` instead. """ + warnings.warn( + "`{self.__class__.__name__}.runtime` is deprecated. Use `runtime_info` instead", + DeprecationWarning, + ) + if not hasattr(self, "_runtime"): runtime_path = self.binder_path("runtime.txt") try: @@ -90,11 +97,11 @@ class RBuildPack(PythonBuildPack): r_version = version_map["4.2"] if not hasattr(self, "_r_version"): - parts = self.runtime.split("-") + runtime, version, date = self.runtime_info # If runtime.txt is not set, or if it isn't of the form r----
, # we don't use any of it in determining r version and just use the default - if len(parts) == 5: - r_version = parts[1] + if version and date: + r_version = version # For versions of form x.y, we want to explicitly provide x.y.z - latest patchlevel # available. Users can however explicitly specify the full version to get something specific if r_version in version_map: @@ -116,15 +123,11 @@ class RBuildPack(PythonBuildPack): Returns '' if no date is specified """ if not hasattr(self, "_checkpoint_date"): - match = re.match(r"r-(\d.\d(.\d)?-)?(\d\d\d\d)-(\d\d)-(\d\d)", self.runtime) - if not match: - self._checkpoint_date = False + runtime, version, date = self.runtime_info + if runtime == "r" and date: + self._checkpoint_date = date else: - # turn the last three groups of the match into a date - self._checkpoint_date = datetime.date( - *[int(s) for s in match.groups()[-3:]] - ) - + self._checkpoint_date = False return self._checkpoint_date def detect(self): @@ -142,13 +145,9 @@ class RBuildPack(PythonBuildPack): description_R = "DESCRIPTION" if not self.binder_dir and os.path.exists(description_R): - if not self.checkpoint_date: - # no R snapshot date set through runtime.txt - # Set it to two days ago from today - self._checkpoint_date = datetime.date.today() - datetime.timedelta( - days=2 - ) - self._runtime = f"r-{str(self._checkpoint_date)}" + # no R snapshot date set through runtime.txt + # Set it to two days ago from today + self._checkpoint_date = datetime.date.today() - datetime.timedelta(days=2) return True @lru_cache diff --git a/tests/unit/test_buildpack.py b/tests/unit/test_buildpack.py index 38fc5c23..224a834b 100644 --- a/tests/unit/test_buildpack.py +++ b/tests/unit/test_buildpack.py @@ -1,9 +1,14 @@ +from datetime import date from os.path import join as pjoin from tempfile import TemporaryDirectory import pytest -from repo2docker.buildpacks import LegacyBinderDockerBuildPack, PythonBuildPack +from repo2docker.buildpacks import ( + BaseImage, + LegacyBinderDockerBuildPack, + PythonBuildPack, +) from repo2docker.utils import chdir @@ -46,3 +51,25 @@ def test_unsupported_python(tmpdir, python_version, base_image): assert bp.python_version == python_version with pytest.raises(ValueError): bp.render() + + +@pytest.mark.parametrize( + "runtime_txt, expected", + [ + (None, (None, None, None)), + ("", (None, None, None)), + ("abc", ("abc", None, None)), + ("abc-001", ("abc", "001", None)), + ("abc-001-2025-06-22", ("abc", "001", date(2025, 6, 22))), + ("a_b/c-0.0.1-2025-06-22", ("a_b/c", "0.0.1", date(2025, 6, 22))), + ], +) +def test_runtime_txt(tmpdir, runtime_txt, expected, base_image): + tmpdir.chdir() + + if runtime_txt is not None: + with open("runtime.txt", "w") as f: + f.write(runtime_txt) + + base = BaseImage(base_image) + assert base.runtime_info == expected From 7ec064c128d276a8e58f4c3665051645c582dc1f Mon Sep 17 00:00:00 2001 From: Simon Li Date: Sun, 22 Jun 2025 15:42:48 +0100 Subject: [PATCH 02/11] BREAKING: replace `RBuildPack.runtime` with `BuildPack.runtime` --- docs/source/changelog.md | 5 ++++ repo2docker/buildpacks/base.py | 8 +++---- repo2docker/buildpacks/pipfile/__init__.py | 4 ++-- repo2docker/buildpacks/python/__init__.py | 9 +++---- repo2docker/buildpacks/r.py | 28 +++------------------- tests/unit/test_buildpack.py | 4 ++-- 6 files changed, 21 insertions(+), 37 deletions(-) diff --git a/docs/source/changelog.md b/docs/source/changelog.md index 58cef1cf..29fa7c93 100644 --- a/docs/source/changelog.md +++ b/docs/source/changelog.md @@ -1,5 +1,10 @@ # Changelog +## Unreleased breaking changes + +`RBuildPack.runtime` previously returned the contents of `runtime.txt` as a string. +It has been replaced by `BuildPack.runtime` which returns a tuple `(name, version, date)`. + ## 2024.07.0 ([full changelog](https://github.com/jupyterhub/repo2docker/compare/2024.03.0...2024.07.0)) diff --git a/repo2docker/buildpacks/base.py b/repo2docker/buildpacks/base.py index 4a6a9417..f3e1f4cd 100644 --- a/repo2docker/buildpacks/base.py +++ b/repo2docker/buildpacks/base.py @@ -753,7 +753,7 @@ class BaseImage(BuildPack): return None @property - def runtime_info(self): + def runtime(self): """ Return parsed contents of runtime.txt @@ -772,13 +772,13 @@ class BaseImage(BuildPack): except FileNotFoundError: return self._runtime - runtime = None + name = None version = None date = None parts = runtime_txt.split("-", 2) if len(parts) > 0 and parts[0]: - runtime = parts[0] + name = parts[0] if len(parts) > 1 and parts[1]: version = parts[1] if len(parts) > 2 and parts[2]: @@ -787,5 +787,5 @@ class BaseImage(BuildPack): raise ValueError(f"Invalid date, expected YYYY-MM-DD: {date}") date = datetime.datetime.fromisoformat(date).date() - self._runtime = (runtime, version, date) + self._runtime = (name, version, date) return self._runtime diff --git a/repo2docker/buildpacks/pipfile/__init__.py b/repo2docker/buildpacks/pipfile/__init__.py index c816e88e..da3f1fd3 100644 --- a/repo2docker/buildpacks/pipfile/__init__.py +++ b/repo2docker/buildpacks/pipfile/__init__.py @@ -187,8 +187,8 @@ class PipfileBuildPack(CondaBuildPack): def detect(self): """Check if current repo should be built with the Pipfile buildpack.""" # first make sure python is not explicitly unwanted - runtime = self.runtime_info[0] - if runtime and runtime != "python": + name = self.runtime[0] + if name and name != "python": return False pipfile = self.binder_path("Pipfile") diff --git a/repo2docker/buildpacks/python/__init__.py b/repo2docker/buildpacks/python/__init__.py index 3b33fe31..6c9a6d6e 100644 --- a/repo2docker/buildpacks/python/__init__.py +++ b/repo2docker/buildpacks/python/__init__.py @@ -15,9 +15,9 @@ class PythonBuildPack(CondaBuildPack): if hasattr(self, "_python_version"): return self._python_version - runtime, version, _ = self.runtime_info + name, version, _ = self.runtime - if runtime != "python" or not version: + if name != "python" or not version: # Either not specified, or not a Python runtime (e.g. R, which subclasses this) # use the default Python self._python_version = self.major_pythons["3"] @@ -136,8 +136,9 @@ class PythonBuildPack(CondaBuildPack): requirements_txt = self.binder_path("requirements.txt") setup_py = "setup.py" - if self.runtime_info[0]: - return self.runtime_info[0] == "python" + name = self.runtime[0] + if name: + return name == "python" if not self.binder_dir and os.path.exists(setup_py): return True return os.path.exists(requirements_txt) diff --git a/repo2docker/buildpacks/r.py b/repo2docker/buildpacks/r.py index 447ce84c..91c5183b 100644 --- a/repo2docker/buildpacks/r.py +++ b/repo2docker/buildpacks/r.py @@ -45,28 +45,6 @@ class RBuildPack(PythonBuildPack): R is installed from https://docs.rstudio.com/resources/install-r/ """ - @property - def runtime(self): - """ - Return contents of runtime.txt if it exists, '' otherwise - - Deprecated, use `runtime_info` instead. - """ - warnings.warn( - "`{self.__class__.__name__}.runtime` is deprecated. Use `runtime_info` instead", - DeprecationWarning, - ) - - if not hasattr(self, "_runtime"): - runtime_path = self.binder_path("runtime.txt") - try: - with open(runtime_path) as f: - self._runtime = f.read().strip() - except FileNotFoundError: - self._runtime = "" - - return self._runtime - @property def r_version(self): """Detect the R version for a given `runtime.txt` @@ -97,7 +75,7 @@ class RBuildPack(PythonBuildPack): r_version = version_map["4.2"] if not hasattr(self, "_r_version"): - runtime, version, date = self.runtime_info + _, version, date = self.runtime # If runtime.txt is not set, or if it isn't of the form r----
, # we don't use any of it in determining r version and just use the default if version and date: @@ -123,8 +101,8 @@ class RBuildPack(PythonBuildPack): Returns '' if no date is specified """ if not hasattr(self, "_checkpoint_date"): - runtime, version, date = self.runtime_info - if runtime == "r" and date: + name, version, date = self.runtime + if name == "r" and date: self._checkpoint_date = date else: self._checkpoint_date = False diff --git a/tests/unit/test_buildpack.py b/tests/unit/test_buildpack.py index 224a834b..23bbc0c6 100644 --- a/tests/unit/test_buildpack.py +++ b/tests/unit/test_buildpack.py @@ -64,7 +64,7 @@ def test_unsupported_python(tmpdir, python_version, base_image): ("a_b/c-0.0.1-2025-06-22", ("a_b/c", "0.0.1", date(2025, 6, 22))), ], ) -def test_runtime_txt(tmpdir, runtime_txt, expected, base_image): +def test_runtime(tmpdir, runtime_txt, expected, base_image): tmpdir.chdir() if runtime_txt is not None: @@ -72,4 +72,4 @@ def test_runtime_txt(tmpdir, runtime_txt, expected, base_image): f.write(runtime_txt) base = BaseImage(base_image) - assert base.runtime_info == expected + assert base.runtime == expected From 53cc506e8e807b7e55dc4c53454e56e0fdb10d2a Mon Sep 17 00:00:00 2001 From: Simon Li Date: Sun, 22 Jun 2025 18:38:18 +0100 Subject: [PATCH 03/11] runtime.txt: allow name-yyyy-mm-dd (no version) --- repo2docker/buildpacks/base.py | 23 ++++++++++++++++------- tests/unit/test_buildpack.py | 24 ++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/repo2docker/buildpacks/base.py b/repo2docker/buildpacks/base.py index f3e1f4cd..74bfa331 100644 --- a/repo2docker/buildpacks/base.py +++ b/repo2docker/buildpacks/base.py @@ -759,6 +759,11 @@ class BaseImage(BuildPack): Returns (runtime, version, date), tuple components may be None. Returns (None, None, None) if runtime.txt not found. + + Supported formats: + name-version + name-version-yyyy-mm-dd + name-yyyy-mm-dd """ if hasattr(self, "_runtime"): return self._runtime @@ -776,15 +781,19 @@ class BaseImage(BuildPack): version = None date = None - parts = runtime_txt.split("-", 2) - if len(parts) > 0 and parts[0]: - name = parts[0] - if len(parts) > 1 and parts[1]: + parts = runtime_txt.split("-") + if len(parts) not in (2, 4, 5) or any(not (p) for p in parts): + raise ValueError(f"Invalid runtime.txt: {runtime_txt}") + + name = parts[0] + + if len(parts) in (2, 5): version = parts[1] - if len(parts) > 2 and parts[2]: - date = parts[2] + + if len(parts) in (4, 5): + date = "-".join(parts[-3:]) if not re.match(r"\d\d\d\d-\d\d-\d\d", date): - raise ValueError(f"Invalid date, expected YYYY-MM-DD: {date}") + raise ValueError(f"Invalid runtime.txt date: {date}") date = datetime.datetime.fromisoformat(date).date() self._runtime = (name, version, date) diff --git a/tests/unit/test_buildpack.py b/tests/unit/test_buildpack.py index 23bbc0c6..ce84df2f 100644 --- a/tests/unit/test_buildpack.py +++ b/tests/unit/test_buildpack.py @@ -57,10 +57,9 @@ def test_unsupported_python(tmpdir, python_version, base_image): "runtime_txt, expected", [ (None, (None, None, None)), - ("", (None, None, None)), - ("abc", ("abc", None, None)), ("abc-001", ("abc", "001", None)), ("abc-001-2025-06-22", ("abc", "001", date(2025, 6, 22))), + ("abc-2025-06-22", ("abc", None, date(2025, 6, 22))), ("a_b/c-0.0.1-2025-06-22", ("a_b/c", "0.0.1", date(2025, 6, 22))), ], ) @@ -73,3 +72,24 @@ def test_runtime(tmpdir, runtime_txt, expected, base_image): base = BaseImage(base_image) assert base.runtime == expected + + +@pytest.mark.parametrize( + "runtime_txt", + [ + "", + "abc", + "abc-001-25-06-22", + ], +) +def test_invalid_runtime(tmpdir, runtime_txt, base_image): + tmpdir.chdir() + + if runtime_txt is not None: + with open("runtime.txt", "w") as f: + f.write(runtime_txt) + + base = BaseImage(base_image) + + with pytest.raises(ValueError, match=r"^Invalid runtime.txt.*"): + base.runtime From 9bf010eea2fa44ba378eee48c4520d64436c7099 Mon Sep 17 00:00:00 2001 From: Raniere Gaia Costa da Silva Date: Fri, 22 Aug 2025 16:38:05 +0200 Subject: [PATCH 04/11] Add check for docker buildx Closes https://github.com/jupyterhub/repo2docker/issues/1452 --- repo2docker/docker.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/repo2docker/docker.py b/repo2docker/docker.py index e6d3958c..984c074d 100644 --- a/repo2docker/docker.py +++ b/repo2docker/docker.py @@ -107,6 +107,13 @@ class DockerEngine(ContainerEngine): ): if not shutil.which("docker"): raise RuntimeError("The docker commandline client must be installed") + + # docker buildx is based in a plugin that might not be installed + # https://github.com/docker/buildx + docker_buildx_version = subprocess.run(["docker", "buildx", "version"]) + if docker_buildx_version.returncode: + raise RuntimeError("The docker buildx plugin must be installed") + args = ["docker", "buildx", "build", "--progress", "plain"] if load: if push: From 13134ce88de1393bde819151d37d01cee17217a3 Mon Sep 17 00:00:00 2001 From: shane knapp Date: Mon, 25 Aug 2025 10:46:04 -0700 Subject: [PATCH 05/11] add support for R 4.5.1 --- repo2docker/buildpacks/r.py | 1 + 1 file changed, 1 insertion(+) diff --git a/repo2docker/buildpacks/r.py b/repo2docker/buildpacks/r.py index 9a81f046..094c0ae1 100644 --- a/repo2docker/buildpacks/r.py +++ b/repo2docker/buildpacks/r.py @@ -69,6 +69,7 @@ class RBuildPack(PythonBuildPack): """ # Available versions at https://cran.r-project.org/src/base/ version_map = { + "4.5": "4.5.1", "4.4": "4.4.2", "4.3": "4.3.3", "4.2": "4.2.3", From e58829348d6ec1a2962f0aaac2b3ce1a95dc5063 Mon Sep 17 00:00:00 2001 From: shane knapp Date: Tue, 26 Aug 2025 11:37:08 -0700 Subject: [PATCH 06/11] porting tests to r4.5.1 --- tests/r/r4.4-rspm/runtime.txt | 1 - tests/r/{r4.4-rspm => r4.5-rspm}/install.R | 0 tests/r/r4.5-rspm/runtime.txt | 1 + tests/r/{r4.4-rspm => r4.5-rspm}/verify | 4 ++-- 4 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 tests/r/r4.4-rspm/runtime.txt rename tests/r/{r4.4-rspm => r4.5-rspm}/install.R (100%) create mode 100644 tests/r/r4.5-rspm/runtime.txt rename tests/r/{r4.4-rspm => r4.5-rspm}/verify (65%) diff --git a/tests/r/r4.4-rspm/runtime.txt b/tests/r/r4.4-rspm/runtime.txt deleted file mode 100644 index 1375871d..00000000 --- a/tests/r/r4.4-rspm/runtime.txt +++ /dev/null @@ -1 +0,0 @@ -r-4.4-2025-01-01 diff --git a/tests/r/r4.4-rspm/install.R b/tests/r/r4.5-rspm/install.R similarity index 100% rename from tests/r/r4.4-rspm/install.R rename to tests/r/r4.5-rspm/install.R diff --git a/tests/r/r4.5-rspm/runtime.txt b/tests/r/r4.5-rspm/runtime.txt new file mode 100644 index 00000000..83911bf8 --- /dev/null +++ b/tests/r/r4.5-rspm/runtime.txt @@ -0,0 +1 @@ +r-4.5.1-2025-05-13 diff --git a/tests/r/r4.4-rspm/verify b/tests/r/r4.5-rspm/verify similarity index 65% rename from tests/r/r4.4-rspm/verify rename to tests/r/r4.5-rspm/verify index 0e02bdd4..6d913061 100755 --- a/tests/r/r4.4-rspm/verify +++ b/tests/r/r4.5-rspm/verify @@ -1,9 +1,9 @@ #!/usr/bin/env Rscript library('digest') -# Fail if version is not 4.0 +# Fail if version is not 4.5 print(version) -if (!(version$major == "4" && as.double(version$minor) >= 4 && as.double(version$minor) < 5)) { +if (!(version$major == "4" && as.double(version$minor) >= 5 && as.double(version$minor) < 6)) { quit("yes", 1) } From 541229b01021c5c8e32e31a56ca9a8cf5dfdf852 Mon Sep 17 00:00:00 2001 From: shane knapp Date: Tue, 26 Aug 2025 11:40:48 -0700 Subject: [PATCH 07/11] fat-fingered month from the cran repo: https://cran.r-project.org/src/base/R-4/ --- tests/r/r4.5-rspm/runtime.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/r/r4.5-rspm/runtime.txt b/tests/r/r4.5-rspm/runtime.txt index 83911bf8..a5718197 100644 --- a/tests/r/r4.5-rspm/runtime.txt +++ b/tests/r/r4.5-rspm/runtime.txt @@ -1 +1 @@ -r-4.5.1-2025-05-13 +r-4.5.1-2025-06-13 From 48cf0399657b51d1eff5b80ea1be80373b499ec6 Mon Sep 17 00:00:00 2001 From: shane knapp Date: Wed, 27 Aug 2025 11:36:56 -0700 Subject: [PATCH 08/11] s/contributing/contribute/g --- CONTRIBUTING.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1dfc1957..0056f4c3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,23 +5,23 @@ (And thank you particularly for coming to read the guidelines! :heart_eyes:) The repo2docker developer documentation is all rendered on our documentation website: [https://repo2docker.readthedocs.io](https://repo2docker.readthedocs.io). -If you're here, you're probably looking for the [Contributing to repo2docker development](https://repo2docker.readthedocs.io/en/latest/contributing/contributing.html) page. +If you're here, you're probably looking for the [Contributing to repo2docker development](https://repo2docker.readthedocs.io/en/latest/contribute/contributing.html) page. Please make sure you've read the following sections before opening an issue/pull request: -- [Process for making a contribution](https://repo2docker.readthedocs.io/en/latest/contributing/contributing.html#process-for-making-a-contribution). +- [Process for making a contribution](https://repo2docker.readthedocs.io/en/latest/contribute/contributing.html#process-for-making-a-contribution). - These steps talk you through choosing the right issue template (bug report or feature request) and making a change. -- [Guidelines to getting a Pull Request merged](https://repo2docker.readthedocs.io/en/latest/contributing/contributing.html#guidelines-to-getting-a-pull-request-merged). +- [Guidelines to getting a Pull Request merged](https://repo2docker.readthedocs.io/en/latest/contribute/contributing.html#guidelines-to-getting-a-pull-request-merged). - These are tips and tricks to help make your contribution as smooth as possible for you and for the repo2docker maintenance team. There are a few other pages to highlight: -- [Our roadmap](https://repo2docker.readthedocs.io/en/latest/contributing/roadmap.html) +- [Our roadmap](https://repo2docker.readthedocs.io/en/latest/contribute/roadmap.html) - We use the roadmap to develop a shared understanding of the project's vision and direction amongst the community of users, contributors, and maintainers. This is a great place to get a feel for what the maintainers are thinking about for the short, medium, and long term future of the project. - [Design of repo2docker](https://repo2docker.readthedocs.io/en/latest/design.html) - This page explains some of the design principles behind repo2docker. Its a good place to understand _why_ the team have made the decisions that they have along the way! - We absolutely encourage discussion around refactoring, updating or extending repo2docker, but please make sure that you've understood this page before opening an issue to discuss the change you'd like to propose. -- [Common developer tasks and how-tos](https://repo2docker.readthedocs.io/en/latest/contributing/tasks.html) +- [Common developer tasks and how-tos](https://repo2docker.readthedocs.io/en/latest/contribute/tasks.html) - Some notes on running tests, buildpack dependencies, creating a release, and keeping the pip files up to date. From 2a143125e2d7d3991996c7195252d6b5ba15f8a8 Mon Sep 17 00:00:00 2001 From: Chris Holdgraf Date: Thu, 28 Aug 2025 10:34:29 +0200 Subject: [PATCH 09/11] Add missing 2025.08.0 release in changelog Closes https://github.com/jupyterhub/repo2docker/issues/1449 --- docs/source/changelog.md | 70 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/docs/source/changelog.md b/docs/source/changelog.md index dc415aca..1b6d5719 100644 --- a/docs/source/changelog.md +++ b/docs/source/changelog.md @@ -1,5 +1,75 @@ # Changelog +## 2025.08.0 + +([full changelog](https://github.com/jupyterhub/repo2docker/compare/4da768765372c602c06606cb79d21a398fcc2987...6cf91e45d5b03f79e365cd82eda09d1178d03327)) + +### API and Breaking Changes + +- Switch to using CLI for everything except running the container [#1421](https://github.com/jupyterhub/repo2docker/pull/1421) ([@yuvipanda](https://github.com/yuvipanda), [@manics](https://github.com/manics), [@minrk](https://github.com/minrk)) +- Require Python 3.9 to run repo2docker [#1411](https://github.com/jupyterhub/repo2docker/pull/1411) ([@manics](https://github.com/manics), [@minrk](https://github.com/minrk), [@yuvipanda](https://github.com/yuvipanda)) +- Shell out to `docker buildx build` to build images [#1402](https://github.com/jupyterhub/repo2docker/pull/1402) ([@yuvipanda](https://github.com/yuvipanda), [@manics](https://github.com/manics), [@minrk](https://github.com/minrk)) +- notebook 7 [#1363](https://github.com/jupyterhub/repo2docker/pull/1363) ([@minrk](https://github.com/minrk), [@manics](https://github.com/manics)) +- Major version bump node (18->20) and jupyterhub-singleuser (3->5) [#1359](https://github.com/jupyterhub/repo2docker/pull/1359) ([@consideRatio](https://github.com/consideRatio), [@yuvipanda](https://github.com/yuvipanda)) + +### Enhancements made + +- add triplets for r versions 4.3, 4.4 [#1403](https://github.com/jupyterhub/repo2docker/pull/1403) ([@minrk](https://github.com/minrk), [@yuvipanda](https://github.com/yuvipanda), [@manics](https://github.com/manics)) +- Upgrade Shiny server to 2024.12 [#1400](https://github.com/jupyterhub/repo2docker/pull/1400) ([@rgaiacs](https://github.com/rgaiacs), [@manics](https://github.com/manics)) +- Upgrade RStudio to 2024.12 [#1399](https://github.com/jupyterhub/repo2docker/pull/1399) ([@rgaiacs](https://github.com/rgaiacs), [@manics](https://github.com/manics)) +- Use REST APIs to resolve DOIs + cleanup dataverse provider [#1390](https://github.com/jupyterhub/repo2docker/pull/1390) ([@yuvipanda](https://github.com/yuvipanda), [@minrk](https://github.com/minrk), [@pdurbin](https://github.com/pdurbin)) +- exclude defaults channel by default [#1365](https://github.com/jupyterhub/repo2docker/pull/1365) ([@minrk](https://github.com/minrk), [@rgaiacs](https://github.com/rgaiacs)) + +### Bugs fixed + +- [MRG] Disable bash trace output in conda activation script [#1425](https://github.com/jupyterhub/repo2docker/pull/1425) ([@mfisher87](https://github.com/mfisher87), [@manics](https://github.com/manics)) +- Simulate json output from docker buildx build [#1413](https://github.com/jupyterhub/repo2docker/pull/1413) ([@yuvipanda](https://github.com/yuvipanda), [@manics](https://github.com/manics), [@minrk](https://github.com/minrk)) +- `set -e` should not be set after conda environment is sourced [#1409](https://github.com/jupyterhub/repo2docker/pull/1409) ([@manics](https://github.com/manics), [@yuvipanda](https://github.com/yuvipanda)) +- Use self.log rather than logging module directly [#1378](https://github.com/jupyterhub/repo2docker/pull/1378) ([@yuvipanda](https://github.com/yuvipanda), [@manics](https://github.com/manics)) +- julia_project.py: fix Pkg REPL api warning [#1376](https://github.com/jupyterhub/repo2docker/pull/1376) ([@fonsp](https://github.com/fonsp), [@GeorgianaElena](https://github.com/GeorgianaElena), [@agoose77](https://github.com/agoose77)) +- TarFile.add: don't add recursively [#1371](https://github.com/jupyterhub/repo2docker/pull/1371) ([@manics](https://github.com/manics), [@minrk](https://github.com/minrk)) + +### Maintenance and upkeep improvements + +- Bump mamba from 2.0.5 to 2.1.0 [#1423](https://github.com/jupyterhub/repo2docker/pull/1423) ([@weiji14](https://github.com/weiji14), [@yuvipanda](https://github.com/yuvipanda)) +- maint: Adapt mamba activation [#1419](https://github.com/jupyterhub/repo2docker/pull/1419) ([@jjerphan](https://github.com/jjerphan), [@yuvipanda](https://github.com/yuvipanda), [@minrk](https://github.com/minrk)) +- Add a test for runtime.txt with full R version specified [#1416](https://github.com/jupyterhub/repo2docker/pull/1416) ([@yuvipanda](https://github.com/yuvipanda), [@minrk](https://github.com/minrk)) +- Bump alpine docker to 3.21 [#1412](https://github.com/jupyterhub/repo2docker/pull/1412) ([@manics](https://github.com/manics), [@minrk](https://github.com/minrk)) +- Refreeze base environment for package upgrades [#1407](https://github.com/jupyterhub/repo2docker/pull/1407) ([@yuvipanda](https://github.com/yuvipanda), [@manics](https://github.com/manics)) +- Remove unused _urlopen method [#1392](https://github.com/jupyterhub/repo2docker/pull/1392) ([@yuvipanda](https://github.com/yuvipanda), [@minrk](https://github.com/minrk)) +- Upgraded to micromamba 2.0.5 [#1387](https://github.com/jupyterhub/repo2docker/pull/1387) ([@JohanMabille](https://github.com/JohanMabille), [@yuvipanda](https://github.com/yuvipanda), [@SylvainCorlay](https://github.com/SylvainCorlay)) +- Use self.log.warning instead of warnings.warn [#1384](https://github.com/jupyterhub/repo2docker/pull/1384) ([@yuvipanda](https://github.com/yuvipanda), [@manics](https://github.com/manics)) +- call close_handlers before garbage collection [#1380](https://github.com/jupyterhub/repo2docker/pull/1380) ([@minrk](https://github.com/minrk), [@manics](https://github.com/manics)) +- [pre-commit.ci] pre-commit autoupdate [#1377](https://github.com/jupyterhub/repo2docker/pull/1377) ([@minrk](https://github.com/minrk)) +- Update to mamba 1.5.9 [#1370](https://github.com/jupyterhub/repo2docker/pull/1370) ([@SylvainCorlay](https://github.com/SylvainCorlay), [@manics](https://github.com/manics)) +- Refreeze conda packages (JupyterLab 4.2.3 -> 4.2.5) [#1369](https://github.com/jupyterhub/repo2docker/pull/1369) ([@manics](https://github.com/manics), [@minrk](https://github.com/minrk)) +- Update for mamba 1.5.8 [#1367](https://github.com/jupyterhub/repo2docker/pull/1367) ([@jjerphan](https://github.com/jjerphan), [@manics](https://github.com/manics), [@SylvainCorlay](https://github.com/SylvainCorlay)) +- update import of shlex.quote [#1364](https://github.com/jupyterhub/repo2docker/pull/1364) ([@minrk](https://github.com/minrk), [@manics](https://github.com/manics)) +- [pre-commit.ci] pre-commit autoupdate [#1362](https://github.com/jupyterhub/repo2docker/pull/1362) ([@minrk](https://github.com/minrk)) + +### Documentation improvements + +- Revamp our documentation and refactor a bit [#1433](https://github.com/jupyterhub/repo2docker/pull/1433) ([@choldgraf](https://github.com/choldgraf), [@chuckwondo](https://github.com/chuckwondo), [@rgaiacs](https://github.com/rgaiacs), [@yuvipanda](https://github.com/yuvipanda)) +- bug report.rst [#1429](https://github.com/jupyterhub/repo2docker/pull/1429) ([@nadiaguiffant](https://github.com/nadiaguiffant), [@manics](https://github.com/manics)) +- Add changelog for 2024.07.0 [#1356](https://github.com/jupyterhub/repo2docker/pull/1356) ([@yuvipanda](https://github.com/yuvipanda), [@consideRatio](https://github.com/consideRatio), [@manics](https://github.com/manics)) + +### Continuous integration improvements + +- Add basic UI Playwright tests [#1410](https://github.com/jupyterhub/repo2docker/pull/1410) ([@manics](https://github.com/manics), [@minrk](https://github.com/minrk)) +- Don't run scheduled workflows on forks [#1408](https://github.com/jupyterhub/repo2docker/pull/1408) ([@manics](https://github.com/manics), [@yuvipanda](https://github.com/yuvipanda)) +- build(deps): bump codecov/codecov-action from 4 to 5 [#1381](https://github.com/jupyterhub/repo2docker/pull/1381) ([@minrk](https://github.com/minrk)) +- [pre-commit.ci] pre-commit autoupdate [#1358](https://github.com/jupyterhub/repo2docker/pull/1358) ([@minrk](https://github.com/minrk)) +- build(deps): bump docker/build-push-action from 5 to 6 [#1357](https://github.com/jupyterhub/repo2docker/pull/1357) ([@manics](https://github.com/manics)) + +### Contributors to this release + +The following people contributed discussions, new ideas, code and documentation contributions, and review. +See [our definition of contributors](https://github-activity.readthedocs.io/en/latest/#how-does-this-tool-define-contributions-in-the-reports). + +([GitHub contributors page for this release](https://github.com/jupyterhub/repo2docker/graphs/contributors?from=2024-07-01&to=2025-08-03&type=c)) + +@agoose77 ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Frepo2docker+involves%3Aagoose77+updated%3A2024-07-01..2025-08-03&type=Issues)) | @AliMirlou ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Frepo2docker+involves%3AAliMirlou+updated%3A2024-07-01..2025-08-03&type=Issues)) | @betatim ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Frepo2docker+involves%3Abetatim+updated%3A2024-07-01..2025-08-03&type=Issues)) | @choldgraf ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Frepo2docker+involves%3Acholdgraf+updated%3A2024-07-01..2025-08-03&type=Issues)) | @chuckwondo ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Frepo2docker+involves%3Achuckwondo+updated%3A2024-07-01..2025-08-03&type=Issues)) | @consideRatio ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Frepo2docker+involves%3AconsideRatio+updated%3A2024-07-01..2025-08-03&type=Issues)) | @d70-t ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Frepo2docker+involves%3Ad70-t+updated%3A2024-07-01..2025-08-03&type=Issues)) | @felder ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Frepo2docker+involves%3Afelder+updated%3A2024-07-01..2025-08-03&type=Issues)) | @fonsp ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Frepo2docker+involves%3Afonsp+updated%3A2024-07-01..2025-08-03&type=Issues)) | @GeorgianaElena ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Frepo2docker+involves%3AGeorgianaElena+updated%3A2024-07-01..2025-08-03&type=Issues)) | @Hind-M ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Frepo2docker+involves%3AHind-M+updated%3A2024-07-01..2025-08-03&type=Issues)) | @hiroyuki-sato ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Frepo2docker+involves%3Ahiroyuki-sato+updated%3A2024-07-01..2025-08-03&type=Issues)) | @jjerphan ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Frepo2docker+involves%3Ajjerphan+updated%3A2024-07-01..2025-08-03&type=Issues)) | @JohanMabille ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Frepo2docker+involves%3AJohanMabille+updated%3A2024-07-01..2025-08-03&type=Issues)) | @manics ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Frepo2docker+involves%3Amanics+updated%3A2024-07-01..2025-08-03&type=Issues)) | @mfisher87 ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Frepo2docker+involves%3Amfisher87+updated%3A2024-07-01..2025-08-03&type=Issues)) | @minrk ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Frepo2docker+involves%3Aminrk+updated%3A2024-07-01..2025-08-03&type=Issues)) | @nadiaguiffant ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Frepo2docker+involves%3Anadiaguiffant+updated%3A2024-07-01..2025-08-03&type=Issues)) | @pdurbin ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Frepo2docker+involves%3Apdurbin+updated%3A2024-07-01..2025-08-03&type=Issues)) | @rgaiacs ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Frepo2docker+involves%3Argaiacs+updated%3A2024-07-01..2025-08-03&type=Issues)) | @ryanlovett ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Frepo2docker+involves%3Aryanlovett+updated%3A2024-07-01..2025-08-03&type=Issues)) | @SylvainCorlay ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Frepo2docker+involves%3ASylvainCorlay+updated%3A2024-07-01..2025-08-03&type=Issues)) | @weiji14 ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Frepo2docker+involves%3Aweiji14+updated%3A2024-07-01..2025-08-03&type=Issues)) | @yuvipanda ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Frepo2docker+involves%3Ayuvipanda+updated%3A2024-07-01..2025-08-03&type=Issues)) + ## 2024.07.0 ([full changelog](https://github.com/jupyterhub/repo2docker/compare/2024.03.0...2024.07.0)) From 42154849fe0b794085707e4e591d1ae4a5cf3c6e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 28 Aug 2025 08:38:23 +0000 Subject: [PATCH 10/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/source/changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/changelog.md b/docs/source/changelog.md index 1b6d5719..6fd445fe 100644 --- a/docs/source/changelog.md +++ b/docs/source/changelog.md @@ -36,7 +36,7 @@ - Add a test for runtime.txt with full R version specified [#1416](https://github.com/jupyterhub/repo2docker/pull/1416) ([@yuvipanda](https://github.com/yuvipanda), [@minrk](https://github.com/minrk)) - Bump alpine docker to 3.21 [#1412](https://github.com/jupyterhub/repo2docker/pull/1412) ([@manics](https://github.com/manics), [@minrk](https://github.com/minrk)) - Refreeze base environment for package upgrades [#1407](https://github.com/jupyterhub/repo2docker/pull/1407) ([@yuvipanda](https://github.com/yuvipanda), [@manics](https://github.com/manics)) -- Remove unused _urlopen method [#1392](https://github.com/jupyterhub/repo2docker/pull/1392) ([@yuvipanda](https://github.com/yuvipanda), [@minrk](https://github.com/minrk)) +- Remove unused \_urlopen method [#1392](https://github.com/jupyterhub/repo2docker/pull/1392) ([@yuvipanda](https://github.com/yuvipanda), [@minrk](https://github.com/minrk)) - Upgraded to micromamba 2.0.5 [#1387](https://github.com/jupyterhub/repo2docker/pull/1387) ([@JohanMabille](https://github.com/JohanMabille), [@yuvipanda](https://github.com/yuvipanda), [@SylvainCorlay](https://github.com/SylvainCorlay)) - Use self.log.warning instead of warnings.warn [#1384](https://github.com/jupyterhub/repo2docker/pull/1384) ([@yuvipanda](https://github.com/yuvipanda), [@manics](https://github.com/manics)) - call close_handlers before garbage collection [#1380](https://github.com/jupyterhub/repo2docker/pull/1380) ([@minrk](https://github.com/minrk), [@manics](https://github.com/manics)) From bc5704d73873827a63797681daf3e7b40ef4ea51 Mon Sep 17 00:00:00 2001 From: Simon Li Date: Mon, 1 Sep 2025 12:24:54 +0100 Subject: [PATCH 11/11] Always call `ContainerEngine.build()` when `push`ing This is so that if the image is built but not pushed it will still be pushed --- repo2docker/app.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/repo2docker/app.py b/repo2docker/app.py index b9684c11..7a70a470 100755 --- a/repo2docker/app.py +++ b/repo2docker/app.py @@ -733,7 +733,10 @@ class Repo2Docker(Application): try: self.fetch(self.repo, self.ref, checkout_path) - if self.find_image(): + if self.find_image() and not self.push: + # If push is requested we don't have a general way to query the registry. + # ContainerEngine.build() also handles pushing, so always "build" and + # rely on the implementation to decide whether to rebuild. self.log.info( f"Reusing existing image ({self.output_image_spec}), not building." )