Skip to content

Commit 08eba83

Browse files
authored
Merge pull request #24 from samsrabin/podman-ubuntu-fix
Fix Podman builds on Ubuntu
2 parents 9506e84 + b9a7ed8 commit 08eba83

File tree

5 files changed

+135
-51
lines changed

5 files changed

+135
-51
lines changed

doc_builder/build_commands.py

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
DEFAULT_IMAGE = "ghcr.io/escomp/ctsm/ctsm-docs:v1.0.1"
1111

12-
# The path in the container's filesystem where the user's home directory is mounted
12+
# The path in the container's filesystem where doc-builder's parent repo checkout is mounted
1313
_CONTAINER_HOME = "/home/user/mounted_home"
1414

1515
# CLI tools we can try to use to run our container, in decreasing order of preference
@@ -122,16 +122,13 @@ def get_build_command(
122122

123123
# But if we're using a container, we have more work to do to create the command....
124124

125-
# Mount the user's home directory in the container; this assumes that both
126-
# run_from_dir and build_dir reside somewhere under the user's home directory (we
127-
# check this assumption below).
128-
container_mountpoint = os.path.expanduser("~")
129-
130-
errmsg_if_not_under_mountpoint = "build_docs must be run from somewhere in your home directory"
125+
# Mount the parent directory of doc-builder in the container; this assumes that both
126+
# run_from_dir and build_dir are in this parent directory.
127+
container_mountpoint = sys_utils.get_toplevel_of_doc_builder_parent()
131128
container_workdir = _container_path_from_local_path(
132129
local_path=run_from_dir,
133130
container_mountpoint=container_mountpoint,
134-
errmsg_if_not_under_mountpoint=errmsg_if_not_under_mountpoint,
131+
msg_start="build_docs must be run from",
135132
)
136133

137134
if os.path.isabs(build_dir):
@@ -141,7 +138,7 @@ def get_build_command(
141138
container_build_dir = _container_path_from_local_path(
142139
local_path=build_dir_abs,
143140
container_mountpoint=container_mountpoint,
144-
errmsg_if_not_under_mountpoint="build directory must reside under your home directory",
141+
msg_start="build directory must be",
145142
)
146143

147144
# Get current user's UID and GID
@@ -167,8 +164,8 @@ def get_build_command(
167164
container_name,
168165
"--user",
169166
f"{uid}:{gid}",
170-
"--mount",
171-
f"type=bind,source={container_mountpoint},target={_CONTAINER_HOME}",
167+
"-v",
168+
f"{container_mountpoint}:{_CONTAINER_HOME}:U",
172169
"--workdir",
173170
container_workdir,
174171
"-t", # "-t" is needed for colorful output
@@ -202,21 +199,20 @@ def _get_make_command(build_dir, build_target, num_make_jobs, warnings_as_warnin
202199
return ["make", sphinxopts, builddir_arg, "-j", str(num_make_jobs), build_target]
203200

204201

205-
def _container_path_from_local_path(
206-
local_path, container_mountpoint, errmsg_if_not_under_mountpoint
207-
):
202+
def _container_path_from_local_path(local_path, container_mountpoint, msg_start):
208203
"""Given a path on the local file system, return the equivalent path in container space
209204
210205
Args:
211206
- local_path: string: absolute path on local file system; this must reside under
212207
container_mountpoint
213208
- container_mountpoint: string: path on local file system that is mounted to _CONTAINER_HOME
214-
- errmsg_if_not_under_mountpoint: string: message to print if local_path does not
215-
reside under container_mountpoint
209+
- msg_start: string: start of the error message
216210
"""
217211
if not os.path.isabs(local_path):
218212
raise RuntimeError(f"Expect absolute path; got {local_path}")
219213

214+
errmsg_if_not_under_mountpoint = f"{msg_start} somewhere in {container_mountpoint}"
215+
220216
local_pathobj = pathlib.Path(local_path)
221217
try:
222218
relpath = local_pathobj.relative_to(container_mountpoint)

doc_builder/build_docs_shared_args.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ def bd_parser(parser, site_root_required=False):
2828
"rather than relying on locally-installed versions of Sphinx, etc.\n"
2929
"This checks that a compatible container tool is installed and running on your system.\n"
3030
"\n"
31-
"NOTE: This mounts your home directory in the container.\n"
31+
"NOTE: This mounts your CTSM checkout in the container.\n"
3232
"Therefore, both the current directory (containing the Makefile for\n"
3333
"building the documentation) and the documentation build directory\n"
34-
"must reside somewhere within your home directory."
34+
"must reside somewhere within your CTSM checkout."
3535
"\n"
3636
f"Default image: {DEFAULT_IMAGE}\n"
3737
"This can be changed with -i/--container-image.",

doc_builder/sys_utils.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,13 @@ def get_toplevel_git_dir(file_or_dir):
102102
return toplevel
103103

104104

105+
def get_toplevel_of_doc_builder_parent():
106+
"""Get the top level of the repo of which doc-builder is a submodule"""
107+
topdir_doc_builder = get_toplevel_git_dir(__file__)
108+
topdir_parent = get_toplevel_git_dir(os.path.join(topdir_doc_builder, os.pardir))
109+
return topdir_parent
110+
111+
105112
def git_current_branch():
106113
"""Determines the name of the current git branch
107114
@@ -124,3 +131,15 @@ def git_current_branch():
124131
branch_name = branch_name.strip()
125132

126133
return branch_found, branch_name
134+
135+
136+
def is_under(child, parent_dir):
137+
"""Check whether child is in parent_dir
138+
139+
Args:
140+
child (str): Path of child directory or file
141+
parent_dir (str): Path of parent directory
142+
"""
143+
if parent_dir[-1] != os.sep:
144+
parent_dir += os.sep
145+
return child.startswith(parent_dir)

test/test_unit_get_build_command.py

Lines changed: 28 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,10 @@ def test_custom_conf_py_path_dir(self):
103103
]
104104
self.assertEqual(expected, build_command)
105105

106-
@patch("os.path.expanduser")
107-
def test_container(self, mock_expanduser):
106+
@patch("doc_builder.sys_utils.get_toplevel_of_doc_builder_parent")
107+
def test_container(self, mock_get_toplevel_of_doc_builder_parent):
108108
"""Tests usage with container"""
109-
mock_expanduser.return_value = "/path/to/username"
109+
mock_get_toplevel_of_doc_builder_parent.return_value = "/path/to/username"
110110
conf_py_path = os.path.join(os.path.dirname(__file__), "conf.py")
111111
build_command = get_build_command(
112112
build_dir="/path/to/username/foorepos/foodocs/versions/main",
@@ -125,8 +125,8 @@ def test_container(self, mock_expanduser):
125125
"foo",
126126
"--user",
127127
self.uid_gid,
128-
"--mount",
129-
"type=bind,source=/path/to/username,target=/home/user/mounted_home",
128+
"-v",
129+
"/path/to/username:/home/user/mounted_home:U",
130130
"--workdir",
131131
"/home/user/mounted_home/foorepos/foocode/doc",
132132
"-t",
@@ -144,10 +144,10 @@ def test_container(self, mock_expanduser):
144144
print("build_command: +", " ".join(build_command))
145145
self.assertEqual(expected, build_command)
146146

147-
@patch("os.path.expanduser")
148-
def test_container_relpath(self, mock_expanduser):
147+
@patch("doc_builder.sys_utils.get_toplevel_of_doc_builder_parent")
148+
def test_container_relpath(self, mock_get_toplevel_of_doc_builder_parent):
149149
"""Tests usage with container, with a relative path to build_dir"""
150-
mock_expanduser.return_value = "/path/to/username"
150+
mock_get_toplevel_of_doc_builder_parent.return_value = "/path/to/username"
151151
build_command = get_build_command(
152152
build_dir="../../foodocs/versions/main",
153153
run_from_dir="/path/to/username/foorepos/foocode/doc",
@@ -164,8 +164,8 @@ def test_container_relpath(self, mock_expanduser):
164164
"foo",
165165
"--user",
166166
self.uid_gid,
167-
"--mount",
168-
"type=bind,source=/path/to/username,target=/home/user/mounted_home",
167+
"-v",
168+
"/path/to/username:/home/user/mounted_home:U",
169169
"--workdir",
170170
"/home/user/mounted_home/foorepos/foocode/doc",
171171
"-t",
@@ -182,10 +182,10 @@ def test_container_relpath(self, mock_expanduser):
182182
]
183183
self.assertEqual(expected, build_command)
184184

185-
@patch("os.path.expanduser")
186-
def test_container_no_clitool_given(self, mock_expanduser):
185+
@patch("doc_builder.sys_utils.get_toplevel_of_doc_builder_parent")
186+
def test_container_no_clitool_given(self, mock_get_toplevel_of_doc_builder_parent):
187187
"""Tests usage with container"""
188-
mock_expanduser.return_value = "/path/to/username"
188+
mock_get_toplevel_of_doc_builder_parent.return_value = "/path/to/username"
189189
conf_py_path = os.path.join(os.path.dirname(__file__), "conf.py")
190190
build_command = get_build_command(
191191
build_dir="/path/to/username/foorepos/foodocs/versions/main",
@@ -203,8 +203,8 @@ def test_container_no_clitool_given(self, mock_expanduser):
203203
"foo",
204204
"--user",
205205
self.uid_gid,
206-
"--mount",
207-
"type=bind,source=/path/to/username,target=/home/user/mounted_home",
206+
"-v",
207+
"/path/to/username:/home/user/mounted_home:U",
208208
"--workdir",
209209
"/home/user/mounted_home/foorepos/foocode/doc",
210210
"-t",
@@ -224,13 +224,11 @@ def test_container_no_clitool_given(self, mock_expanduser):
224224
build_command in [["podman"] + expected, build_command == ["docker"] + expected]
225225
)
226226

227-
@patch("os.path.expanduser")
228-
def test_container_builddir_not_in_home(self, mock_expanduser):
229-
"""If build_dir is not in the user's home directory, should raise an exception"""
230-
mock_expanduser.return_value = "/path/to/username"
231-
with self.assertRaisesRegex(
232-
RuntimeError, "build directory must reside under your home directory"
233-
):
227+
@patch("doc_builder.sys_utils.get_toplevel_of_doc_builder_parent")
228+
def test_container_builddir_not_in_db_checkout(self, mock_get_toplevel_of_doc_builder_parent):
229+
"""If build_dir is not in parent of doc-builder, should raise an exception"""
230+
mock_get_toplevel_of_doc_builder_parent.return_value = "/path/to/username"
231+
with self.assertRaisesRegex(RuntimeError, "build directory must be somewhere in"):
234232
_ = get_build_command(
235233
build_dir="/path/to/other/foorepos/foodocs/versions/main",
236234
run_from_dir="/path/to/username/foorepos/foocode/doc",
@@ -240,14 +238,11 @@ def test_container_builddir_not_in_home(self, mock_expanduser):
240238
version="None",
241239
)
242240

243-
@patch("os.path.expanduser")
244-
def test_container_runfromdir_not_in_home(self, mock_expanduser):
245-
"""If run_from_dir is not in the user's home directory, should raise an exception"""
246-
mock_expanduser.return_value = "/path/to/username"
247-
with self.assertRaisesRegex(
248-
RuntimeError,
249-
"build_docs must be run from somewhere in your home directory",
250-
):
241+
@patch("doc_builder.sys_utils.get_toplevel_of_doc_builder_parent")
242+
def test_container_runfromdir_not_in_db_parent(self, mock_get_toplevel_of_doc_builder_parent):
243+
"""If run_from_dir is not in parent of doc-builder, should raise an exception"""
244+
mock_get_toplevel_of_doc_builder_parent.return_value = "/path/to/username"
245+
with self.assertRaisesRegex(RuntimeError, "build_docs must be run from somewhere in"):
251246
_ = get_build_command(
252247
build_dir="/path/to/username/foorepos/foodocs/versions/main",
253248
run_from_dir="/path/to/other/foorepos/foocode/doc",
@@ -257,10 +252,10 @@ def test_container_runfromdir_not_in_home(self, mock_expanduser):
257252
version="None",
258253
)
259254

260-
@patch("os.path.expanduser")
261-
def test_container_runfromdir_relative(self, mock_expanduser):
255+
@patch("doc_builder.sys_utils.get_toplevel_of_doc_builder_parent")
256+
def test_container_runfromdir_relative(self, mock_get_toplevel_of_doc_builder_parent):
262257
"""If run_from_dir is relative, should raise an exception"""
263-
mock_expanduser.return_value = "/path/to/username"
258+
mock_get_toplevel_of_doc_builder_parent.return_value = "/path/to/username"
264259
with self.assertRaisesRegex(
265260
RuntimeError,
266261
"Expect absolute path; got",

test/test_unit_is_under.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#!/usr/bin/env python3
2+
"""Tests of check_permanent_file
3+
4+
These are integration tests, since they interact with the OS and git,
5+
and so are slower than typical unit tests.
6+
"""
7+
8+
import unittest
9+
import tempfile
10+
import shutil
11+
import os
12+
13+
# pylint: disable=import-error,no-name-in-module
14+
from doc_builder.sys_utils import is_under
15+
16+
17+
class TestIsUnder(unittest.TestCase):
18+
"""Test the is_under function"""
19+
20+
# ------------------------------------------------------------------------
21+
# Helper methods
22+
# ------------------------------------------------------------------------
23+
24+
def setUp(self):
25+
self._return_dir = os.getcwd()
26+
self._tempdir = os.path.realpath(tempfile.mkdtemp())
27+
os.chdir(self._tempdir)
28+
29+
def tearDown(self):
30+
os.chdir(self._return_dir)
31+
shutil.rmtree(self._tempdir, ignore_errors=True)
32+
33+
# ------------------------------------------------------------------------
34+
# Begin tests
35+
# ------------------------------------------------------------------------
36+
37+
def test_isunder_true(self):
38+
"""Test if child is under parent"""
39+
my_dir = "some_dir"
40+
os.makedirs(my_dir)
41+
filename = os.path.join(my_dir, "some_file")
42+
with open(filename, "a", encoding="utf8") as myfile:
43+
myfile.write("ietbeconae0nr0einr")
44+
self.assertTrue(is_under(filename, my_dir))
45+
self.assertFalse(is_under(my_dir, filename))
46+
47+
def test_isunder_true_extraslash(self):
48+
"""Test if child is under parent and parent has a path separator at the end"""
49+
my_dir = "some_dir" + os.sep
50+
os.makedirs(my_dir)
51+
filename = os.path.join(my_dir, "some_file")
52+
with open(filename, "a", encoding="utf8") as myfile:
53+
myfile.write("ietbeconae0nr0einr")
54+
self.assertTrue(is_under(filename, my_dir))
55+
self.assertFalse(is_under(my_dir, filename))
56+
57+
def test_isunder_false(self):
58+
"""Test if true"""
59+
my_dir = "some_dir"
60+
os.makedirs(my_dir)
61+
filename = "some_file"
62+
with open(filename, "a", encoding="utf8") as myfile:
63+
myfile.write("ietbeconae0nr0einr")
64+
self.assertFalse(is_under(filename, my_dir))
65+
66+
def test_isunder_true_neither_exist(self):
67+
"""Test true even if neither exists"""
68+
my_dir = "some_dir"
69+
filename = "some_file"
70+
self.assertFalse(is_under(filename, my_dir))
71+
72+
73+
if __name__ == "__main__":
74+
unittest.main()

0 commit comments

Comments
 (0)