-
Notifications
You must be signed in to change notification settings - Fork 27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for go workspaces #457
Add support for go workspaces #457
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is still a draft I only had some immediate comments on the proposal but didn't do a detailed in-depth review of the logic fitting the overall design.
63706be
to
3be712f
Compare
c67073f
to
a1cbcdb
Compare
3c186c7
to
a9b5e69
Compare
0e85b79
to
40477f4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good, I learnt something new during the review, e.g. that any repo-local submodule still needs to be declared uniquely within all Go modules and so it is imperative one still uses the replace
keyword to denote that the local implementation should be used even though that is the only one!
Anyway, I think we need some documentation update on workspaces, don't we?
main_module_name = go([*go_list, "-m"], run_params).rstrip() | ||
modules_json_stream = go([*go_list, "-m", "-json"], run_params).rstrip() | ||
main_module_dict, workspace_dict_list = _process_modules_json_stream( | ||
app_dir, modules_json_stream | ||
) | ||
|
||
path = main_module_dict["Path"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having recently worked on the toolchain selection which gave me headaches wrt/ unit tests because _resolve_gomod
is a beast of a function my impression has become that anything that needs to execute a Go command should go into a separate function to potentially make mocking in test_resolve_gomod
much easier.
Also from logical perspective this particular block seems to be open-coded quite a bit. What if we introduced a function, say _parse_modules
along the following lines:
def _parse_modules(app_dir, version_resolver) -> list[ParsedModule]:
run_go_list_json
process_modules_json_stream
...
return [main_module] + workspace_modules
and then in resolve_gomod
we would just pop the main module out of the list for some further processing, but the idea is to make the code in resolve_gomod
cleaner, leaner and easier to follow. Do you think the ^above would help achieving that by consolidating the special casing of the main module we're doing here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I completely agree this is a better approach, but since this is refactoring work, can we do it as a follow up?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be afraid it would remain TODO forever :), but okay, UNLESS it turns out more substantial changes are needed within this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, I went back and implemented this, and the code looks better now.
I'm now wondering if the workspaces scenario that we added to test_resolve_gomod
is worth it at all. It brings more complexity to the test and a buch of new files committed to the repo, and I think the only coverage it brings us is this line here. All the rest is tested individually, AFAICT.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm now wondering if the workspaces scenario that we added to test_resolve_gomod is worth it at all. It brings more complexity to the test and a buch of new files committed to the repo, and I think the only coverage it brings us is this line here
You're sure about the coverage? test_resolve_gomod
has nothing to do with _create_modules_from_parsed_data
- that one is only ever called from fetch_gomod_source
which is out of context for test_resolve_gomod
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I'm stupid, I tagged the wrong line. This is the right one.
cc7eb2d
to
362afd4
Compare
My question is this: what is the main problem of letting users point cachi2 to the |
5221b60
to
45c5e64
Compare
45c5e64
to
9b7965a
Compare
Erik and I had a private chat about this, and we figured out that @eskultety I played with the idea a little bit. The only problem we have is if the directory that contains the So I tried our second idea, that was electing the first workspace listed in |
The conclusion we reached about this suggestion is that, although we want to provide users more flexibility by allowing them to point to a directory that contains a The main problem that we reached is how to work around the need of having what Cachi2 defines as a "main module", since for each of the input packages fed to the CLI, a call to Here's a small (and likely incomplete) list of impacted functions that would need to be changed:
I personally believe that all of those can be worked around, and the code will likely end up cleaner after the refactoring. So we can tackle this as a follow up task. @eskultety Please add any details I might've missed. |
I'd just add that solutions should be more often than not proposed as whole in their entirety. That said, I do have to agree that in this particular case we're talking about significant conceptual changes to how we perceive a Go project structure to represent it and process it which in comparison to the changes being proposed in this PR would unarguably only provide a marginal improvement to the consumer facing UI. If this weren't the case or the changes being proposed would also end up making substantial modifications to the code base then it would be up for a lengthy discussion and likely lead to a request for a complete solution.
Yeah, the concept for the need of a "main" module seems off in context of workspaces, so we definitely need a conceptual change here.
With most Go commands being workspace context aware ^this particular change should be insignificant compared to others
IIUC ^this one should be taken care of in #553. It should also be straight forward since
I think ^this one may the biggest problem to figure out in context of the suggested future refactor to introduce the option of pointing cachi2 to a directory containing a
In conclusion I agree with the reasoning @brunoapimentel provided and therefore we should continue with the original approach, thanks for the deep investigation @brunoapimentel! I will follow up by creating an issue tracking the future refactor to host the |
37620e5
to
5476d23
Compare
""" | ||
|
||
app_dir = RootedPath("/path/to/project") | ||
version_resolver.get_golang_version = lambda _, __: "1.0.0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any better way to mock the result of this function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
diff --git a/tests/unit/package_managers/test_gomod.py b/tests/unit/package_managers/test_gomod.py
index 5f617d33..7362f851 100644
--- a/tests/unit/package_managers/test_gomod.py
+++ b/tests/unit/package_managers/test_gomod.py
@@ -608,7 +608,7 @@ def test_parse_local_modules(go: mock.Mock, version_resolver: mock.Mock) -> None
"""
app_dir = RootedPath("/path/to/project")
- version_resolver.get_golang_version = lambda _, __: "1.0.0"
+ version_resolver.get_golang_version.return_value = "1.0.0"
main_module, workspace_modules = _parse_local_modules(go, [], {}, app_dir, version_resolver)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@brunoapimentel I think we're good. A couple of trivial things, but I trust you on fixing them :).
tmp_path: Path, | ||
) -> None: | ||
mock_run.return_value = path_to_go_work_file.substitute({"tmp_path": tmp_path}) | ||
|
||
repo_root = RootedPath(tmp_path) | ||
|
||
go_work_path = _get_go_work_path(repo_root) | ||
|
||
if should_return_none: | ||
assert go_work_path is None | ||
else: | ||
assert go_work_path == repo_root | ||
|
||
|
||
@mock.patch("cachi2.core.package_managers.gomod.Go.__call__") | ||
def test_get_go_work_path_when_go_work_is_outside_of_repo( | ||
mock_run: mock.Mock, tmp_path: Path | ||
) -> None: | ||
mock_run.return_value = "/a/random/path/go.work" | ||
|
||
repo_root = RootedPath(tmp_path) | ||
|
||
error_message = f"Joining path '/a/random/path' to '{tmp_path}': target is outside '{tmp_path}'" | ||
|
||
with pytest.raises(PathOutsideRoot, match=error_message): | ||
_get_go_work_path(repo_root) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: You can use our rooted_tmp_path
instead and use it directly in all calls/accesses.
offset = 0 | ||
if has_workspaces: | ||
assert mock_run.call_args_list[0][0][0] == [GO_CMD_PATH, "work", "edit", "-json"] | ||
# one extra Go command is called when workspaces are present | ||
offset = 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
based on how you set up parametrize, has_workspaces
cannot be true at the same time with force_gomod_tidy
, so the offset variable isn't needed at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assuming that version detection works when the workspace modules are tagged with different version tags, this LGTM
version=main_module_version, | ||
replace=replaced_module, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By setting replace
and giving the replaced_module
a relative path, the version eventually ends up getting detected by version_resolver.get_golang_version(workspace_module_name, workspace_module_path)
?
(It's a little confusing, but should be correct. Maybe just don't set version=main_module_version
to reduce confusion)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tested locally, it does work. Would be worth adding to the integration test (can be a follow-up)
git clone https://github.com/cachito-testing/cachi2-gomod
cd cachi2-gomod
git checkout go_workspaces
git tag workspace_modules/hello/v1.1.0
git tag workspace_modules/hi/hiii/v1.2.0
git tag workspace_modules/echo/v4.2.0
cachi2 --log-level debug fetch-deps '{"type": "gomod", "path": "workspace_modules/hello"}'
jq < cachi2-output/bom.json '.components[].purl | select(test("workspace_modules.*type=module"))' -r
pkg:golang/github.com/cachito-testing/cachi2-gomod/workspace_modules/[email protected]?type=module
pkg:golang/github.com/cachito-testing/cachi2-gomod/workspace_modules/[email protected]?type=module
pkg:golang/github.com/cachito-testing/cachi2-gomod/workspace_modules/hi/[email protected]?type=module
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll implement the integration test as a follow up, then.
Whenever workspaces are enabled, the "go list -m" command will return a list of all workspaces modules instead of the usual single module present in the path being processed by Cachi2. For this reason, we need to properly parse this extra data so that they can be included in the resulting SBOM. Signed-off-by: ejegrova <[email protected]>
All go.sum files and go.work.sum are checked for checksums. If not found, the property cachi2:missing_hash:in_file has value of go.work.sum. Signed-off-by: Bruno Pimentel <[email protected]>
Signed-off-by: ejegrova <[email protected]>
5476d23
to
d0e81fe
Compare
5292603
discussion: #398
checksum validation:
cachi2:missing_hash:in_file
has value of go.work.sumJIRA: STONEBLD-2043
Maintainers will complete the following section
Note: if the contribution is external (not from an organization member), the CI
pipeline will not run automatically. After verifying that the CI is safe to run:
/ok-to-test
(as is the standard for Pipelines as Code)