Skip to content

Commit 3dbf4d9

Browse files
Minion3665Mic92
authored andcommitted
feat!: Update output path writing
- Allow pull requests to create output paths Previously, it was not possible to get unmerged pull request outputs. I would like to access these (e.g. for hosting a preview version of a website in a similar way to https://docs.netlify.com/site-deploys/deploy-previews/ or https://vercel.com/docs/deployments/preview-deployments) These are now surfaced under `{owner}/{repo}/pulls/{pr number}` - Fix repository attribute name conflicts Previously there was no difference between samely-named attributes in different repositories. This has been changed so outputs are under `{owner}/{repo}/{branch}` - Allow full attribute names, still stripping path traversal Previously, presumably to prevent path traversal, if your attribute name contained slashes buildbot-nix would only take the last segment as an output. This has been replaced by interpreting slashes as subdirectories and refusing any segments which don't descend into a subdirectory (i.e. are attempting path traversal) - Still create outputs on skipped builds Previously when something was skipped, for example a build that was completed in a pull request, the output wouldn't be updated. This made the outputs directory quite unreliable. Outputs will now always be updated, no matter whether a build was actually executed. BREAKING-CHANGE: This stops old output locations being outputted. If you rely on these locations, you will need to update whatever relies on them. fix: Skip writing path with failed builds Previously, if a build failed to produce any output we would try to write "None" to the output file. This doesn't work, causing the job to error. Instead, we should skip writing the output and preserve the original "failed" status.
1 parent d53f7fb commit 3dbf4d9

File tree

1 file changed

+56
-6
lines changed

1 file changed

+56
-6
lines changed

buildbot_nix/__init__.py

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -756,16 +756,57 @@ def __init__(self, project: GitProject, path: Path, **kwargs: Any) -> None:
756756
self.project = project
757757
self.path = path
758758

759+
def join_traversalsafe(self, root: Path, joined: Path) -> Path:
760+
root = root.resolve()
761+
762+
for part in joined.parts:
763+
new_root = (root / part).resolve()
764+
765+
if not new_root.is_relative_to(root):
766+
msg = f"Joined path attempted to traverse upwards when processing {root} against {part} (gave {new_root})"
767+
raise ValueError(msg)
768+
769+
root = new_root
770+
771+
return root
772+
773+
def join_all_traversalsafe(self, root: Path, *paths: Path) -> Path:
774+
for path in paths:
775+
root = self.join_traversalsafe(root, path)
776+
777+
return root
778+
779+
@defer.inlineCallbacks
759780
def run(self) -> Generator[Any, object, Any]:
760781
props = self.build.getProperties()
761-
if props.getProperty("branch") != self.project.default_branch:
782+
783+
pr = props.getProperty("pr_number")
784+
785+
if not pr and props.getProperty("branch") != self.project.default_branch:
762786
return util.SKIPPED
763787

764-
attr = Path(props.getProperty("attr")).name
765788
out_path = props.getProperty("out_path")
766-
# XXX don't hardcode this
767-
self.path.mkdir(parents=True, exist_ok=True)
768-
(self.path / attr).write_text(out_path)
789+
790+
if not out_path: # if, e.g., the build fails and doesn't produce an output
791+
return util.SKIPPED
792+
793+
project_name = Path(props.getProperty("projectname"))
794+
795+
target = Path(f"pulls/{pr}") if pr else Path(props.getProperty("branch"))
796+
797+
attr = Path(props.getProperty("attr"))
798+
799+
try:
800+
file = self.join_all_traversalsafe(self.path, project_name, target, attr)
801+
except ValueError as e:
802+
error_log: StreamLog = yield self.addLog("path_error")
803+
error_log.addStderr(f"Path traversal prevented ... skipping update: {e}")
804+
return util.FAILURE
805+
806+
file.parent.mkdir(parents=True, exist_ok=True)
807+
808+
file.write_text(out_path)
809+
769810
return util.SUCCESS
770811

771812

@@ -1074,6 +1115,7 @@ def nix_cached_failure_config(
10741115
def nix_skipped_build_config(
10751116
project: GitProject,
10761117
worker_names: list[str],
1118+
outputs_path: Path | None = None,
10771119
) -> BuilderConfig:
10781120
"""Dummy builder that is triggered when a build is skipped."""
10791121
factory = util.BuildFactory()
@@ -1102,6 +1144,14 @@ def nix_skipped_build_config(
11021144
set_properties={"report_status": False},
11031145
),
11041146
)
1147+
if outputs_path is not None:
1148+
factory.addStep(
1149+
UpdateBuildOutput(
1150+
project=project,
1151+
name="Update build output",
1152+
path=outputs_path,
1153+
),
1154+
)
11051155
return util.BuilderConfig(
11061156
name=f"{project.name}/nix-skipped-build",
11071157
project=project.name,
@@ -1250,7 +1300,7 @@ def config_for_project(
12501300
outputs_path=outputs_path,
12511301
post_build_steps=post_build_steps,
12521302
),
1253-
nix_skipped_build_config(project, [SKIPPED_BUILDER_NAME]),
1303+
nix_skipped_build_config(project, [SKIPPED_BUILDER_NAME], outputs_path),
12541304
nix_failed_eval_config(project, [SKIPPED_BUILDER_NAME]),
12551305
nix_dependency_failed_config(project, [SKIPPED_BUILDER_NAME]),
12561306
nix_cached_failure_config(project, [SKIPPED_BUILDER_NAME]),

0 commit comments

Comments
 (0)