-
Notifications
You must be signed in to change notification settings - Fork 8
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
Support local buildpacks and meta-buildpacks in libcnb-test (outdated) #590
Conversation
The following changes have made to support local testing of buildpacks and meta-buildpack: - refactored shared functionality between `libcnb-cargo` and `libcnb-test` into `libcnb-package` - modified the test runner to support a `BuildpackReference::Local` variant which is similar to `BuildpackReference::Crate` because both represent buildpacks on the file-system which need to be compiled - after local and crate references are compiled, they are packaged into Docker images to get around an issue with pack not handling meta-buildpacks correctly (buildpacks/pack#1320) - all the images created during the test are then cleaned up afterwards
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.
Ed and Manuel: I have some questions and comments related to libcnb-package in here. You can respond out of band or I can open an issue where it would be better to have a discussion. Raising them here since it's top of mind, and one or both of you will need to review before we can merge.
Colin: Thanks again for undertaking this. I also appreciate the walkthrough. I marked some things as change requested, we mostly covered those on the call. We also talked about adding an example usage to the existing TestRunner
examples to show that it's an option.
I left a bunch of comments, they're marked as such. Writing helps me process. Also I marked other changes as optional.
In addition I also want to mention that we talked about the possibility of a performance optimization. After discussion I don't think we should do it now, but it's worth exploring later. This section is purely for future reference about what we talked about
Compile once
The current implementation will compile the required local buildpack for every test invocation. We talked about moving to a compile once. This is the model used in Cutlass. To do it I used:
- A class similar to the
Local
variant https://github.com/heroku/cutlass/blob/ea2e67a3f3ccd1a3f14bb3e67cfa37b36537297c/lib/cutlass/local_buildpack.rb - Teardown/destroy was provided by the test execution framework: https://github.com/heroku/cutlass#initial-config-localbuildpack-for-packagetoml
One challenge of the current implementation is that the buildpacks are compiled and then put into containers. This container packaging is done to support multibuildpacks. If we wanted to optimize to only compile once, then we would end up with a resource that could possibly leak. Also Rust makes sharing across threads/tests significantly harder.
Originally I proposed using something like lazy_static
to create and retain a single temp dir where all buildpacks could be compiled and referenced by directory namespace. The OCI part is harder. Also if it's worth optimizing, it's probably worth profiling a bit to gauge how expensive the container creation and stuffing is versus the compilation (my hunch is that it's much quicker, but I would like to verify).
I think it's worth validating the existing logic and implementation and getting this working (I want to use it soon) and then work to refactor for performance later.
|
||
### Changed | ||
|
||
- `libcnb-package` |
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.
[Question for maintainers, no action] More of a question for Manuel and Ed. This package is mostly exposed for re-use in our tooling. I would assume that we don't expect others to use these interfaces directly or do we?
Are we treating libcnb-package
as a "true" cargo project with version guarantees, or is it more like this internal serde crate https://crates.io/crates/serde_derive_internals?
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.
Are we treating
libcnb-package
as a "true" cargo project with version guarantees, or is it more like this internal serde crate
Good question. I'd thought the former, but I'm open to either really, as long as we're clear with potential external consumers of the create. Also at the moment with libcnb being pre-v1, we're bumping the major version most releases, so this mostly won't be an issue - and later once we go v1 I suspect libcnb-package
will have settled down enough that we don't expect too much breakage.
One thing to watch out for though - if we were to switch to being more relaxed about the major version for libcnb-package
- we'd really have to switch to using hard version pins instead of ranges for intra-crate dependencies. ie: libcnb-cargo
would need to depend on libcnb-package
version ==1.2.3
not ^1.2.3
. (We should perhaps make that switch regardless given our unified release versions approach.)
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.
@@ -251,6 +251,8 @@ pub enum BuildpackReference { | |||
Crate, | |||
/// References another buildpack by id, local directory or tarball. | |||
Other(String), | |||
|
|||
Local(PathBuf), |
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.
[Change Request]: Talked about on the call. I would like to see this be more descriptive about the desired functionality, something like CompileLocal
. Also add a docstring to the variant.
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.
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.
we also discussed that it could be a buildpack id within your project space.
Right. I like the ergonomics of that more, if it's not too difficult to implement. One thought is that it might get confusing to see:
Other(String::from("heroku/yarn-engine")),
CompileLocal(String::from("heroku/nodejs-engine"))
Which is related to your question to Manuel.
IDK what he thinks, but my preference is generally to try to separate out refactorings from new features unless they're in conflict. It makes it easier to follow the changes and review, though it's more work for implementers. I'm suggesting we push refactoring/changing Other()
until after this PR is merged. Also, I'm willing to do some of that work if needed.
To the end of should it be changed. I think the interface is already confusing. It was unclear to me that specifying heroku/jvm-internal
in the java buildpack test would pull in the one from the builder and not the local one. To that end I think it's worth considering what it does already it handles:
- Path to already compiled buildpack (zipped or not)
- Buildpack Id
- Something else?
Maybe we separate these out into their own variants. For the Buildpack Id case it will work if a buildpack is specified via the builder or via the CLI flags i.e. --buildpack <path/to/heroku/node>
I'm not sure what to name or call that. Maybe "ExternalBuildpack" or "ExternalBuildpackID"?
); | ||
} | ||
#[derive(Debug)] | ||
pub enum FindCargoWorkspaceError { |
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.
[Question, no action] Question to maintainers:
I noticed this doesn't have a Display
implementation. If we say that libcnb-package
is an "actual" crate then I think we would want that and some docs for the variants (or if we thiserror
the display would function as a doc of sorts.
Looking at other examples in libcnb-package
I'm seeing that:
- No
pub enum
comes with a display implementation, at least one doesn't even haveDebug
(CrossCompileAssistance) so scratch that comment - Documentation on
pub enum
is inconsistent. Some have enum level docs, some have variant level docs. I would defer to Manuel and Ed for some guidance on when to do which.
For libcnb-package we should possibly consider labeling it as a stronger "unstable" rather than simply "you should consider using a different package instead". I still believe in documenting private interfaces (in shared code) we should still have some consistency.
.args(["locate-project", "--workspace", "--message-format", "plain"]) | ||
.current_dir(dir_in_workspace) | ||
.output() | ||
.map_err(FindCargoWorkspaceError::SpawnCommand)?; |
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.
[Comment, no action] - Invoking a command to get this information jumped out as being unusual. At first I thought you were shelling to cargo libcnb
but then realized this is calling cargo locate-project
and parsing the output. I found the code https://github.com/rust-lang/cargo/blob/b40be8bdcf2eff9ed81702594d44bf96c27973a6/src/bin/cargo/commands/locate_project.rs it does seem like it's not exposed anywhere, which would have been convenient.
/// | ||
/// ## Errors | ||
/// | ||
/// Will return an `Err` if the root workspace directory cannot be located. |
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.
[Optional] It wasn't clear to me the first read through that this could be intentional (i.e. everything worked correctly and cargo couldn't find a worspace) or accidental (running a binary with this function on a system that doesn't have cargo installed, somehow or the OS throws an error like OOM, etc.)
We could expand the errors section here.
pub fn assemble_meta_buildpack_directory( | ||
destination_path: impl AsRef<Path>, | ||
buildpack_source_dir: impl AsRef<Path>, | ||
buildpack_path: impl AsRef<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.
[Question, possible action requested] What's the difference between buildpack_path
and buildpack_source_dir
? I looked at the call sites:
&buildpack_package.path,
&buildpack_package.buildpack_data.buildpack_descriptor_path,
and
assemble_meta_buildpack_directory(
target_dir,
&buildpack_package.path,
&buildpack_package.buildpack_data.buildpack_descriptor_path,
buildpack_package
.buildpackage_data
.as_ref()
.map(|data| &data.buildpackage_descriptor),
buildpack_output_directory_locator,
)
.map_err(PackageBuildpackError::AssembleBuildpackDirectory)
It's still unclear. It would be good to get a comment or a more descriptive variable name.
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.
buildpack_path
is thebuildpack.toml
filebuildpack_source_dir
which is the directory that contains thebuildpack.toml
file.
the buildpack_source_dir
could be derived from buildpack_path
though since it's the parent directory. alternatively, the buildpack_path
could be replaced with BuildpackData
which might add some clarity to the signature as well.
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.
the buildpack_source_dir could be derived from buildpack_path though since it's the parent directory. alternatively, the buildpack_path could be replaced with BuildpackData which might add some clarity to the signature as well.
Thanks for the clarification. I think a buildpack_toml
with impl AsRef<Path>
would be clearer if you want to change it and a larger refactor doesn't work out.
Regarding changing it from two inputs to one: I'm unsure if there's ever a valid reason for a buildpack path to be different than the toml path.
Regarding changing it to pass in the data directly: In Ruby I generally like passing in derived values i.e. instead of passing in a filename if we need its contents, we could pass in the contents directly. Or passing in a boolean of whether a hash has a key instead of passing in the whole hash. In rust this seems less needed but still a nice pattern when it can be used. To that end I like passing in BuildpackData
directly, with the caveat that I don't want you to have to jump through hoops, especially if it means duplication.
Co-authored-by: Richard Schneeman <richard.schneeman+no-recruiters@gmail.com>
Linking some in-progress use cases that we can come back to after merge:
@colincasey I see some activity since our conversation are you ready for a review from Manuel/Ed or still working on updates? |
this PR needs to sync up with recent changes to |
Thanks to Colin's PR buildpacks/pack#1841, that issue is now fixed in Pack CLI v0.30.0-rc1: As such, I think the workaround should be removed from this PR, since: |
} | ||
|
||
#[derive(Clone, Debug)] | ||
pub(crate) struct PackBuildpackPackageCommand { |
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.
This change can be dropped now that the Pack CLI bug has been fixed
} | ||
} | ||
|
||
pub(crate) fn run_buildpack_package_command<C: Into<Command>>(command: C) { |
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.
(there's the new run_command
util after #619 btw; though moot point given this can be removed due to the Pack CLI bug being fixed)
let _image_delete_result = | ||
self.runner | ||
let mut images = vec![&self.image_name]; | ||
images.extend(&self.buildpack_images); |
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.
This change can be dropped now that the Pack CLI bug has been fixed
@@ -101,6 +101,15 @@ impl TestRunner { | |||
) { | |||
let config = config.borrow(); | |||
|
|||
let mut test_context = TestContext { |
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.
This change can be dropped now that the Pack CLI bug has been fixed
@@ -132,14 +133,29 @@ fn starting_containers() { | |||
|
|||
#[test] | |||
#[ignore = "integration test"] | |||
#[should_panic( | |||
expected = "Could not package current crate as buildpack: BuildBinariesError(ConfigError(NoBinTargetsFound))" |
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.
This test refactoring might be best as a separate PR that lands before this one
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.
Thank you for working on this!
I've had a quick look through the PR, but am finding it quite hard to review quickly given the size of the PR.
Now that the Pack CLI bug has been fixed, it seems a fair bit of the complexity/size of this PR might go away, since the solution no longer needs to create an intermediate Docker image for the meta-buildpacks (and keep track of cleaning them up etc).
Another thing that might make the PR easier to review is to split out any changes that can be made separately. For example, unrelated refactorings/changes, or even changes that are related to this work, but are preparatory in nature, and could be made in a prior PR. For example, for the bollard migration work I ended up opening half a dozen PRs, only the last of which was actually the migration itself - which made it much easier for the PR reviewers.
Closing this for #666 |
The following changes have made to support local testing of buildpacks and meta-buildpacks:
libcnb-cargo
andlibcnb-test
intolibcnb-package
BuildpackReference::Local
variant which is similar toBuildpackReference::Crate
because both represent buildpacks on the file-system which need to be compiledpack build --buildpack <path>
apply configuration inpackage.toml
buildpacks/pack#1320)These changes also affects #583 since the functionality for getting a target buildpack output directory path was moved into
BuildpackOutputDirectoryLocator
.If you want to try this out locally in a buildpack project:
libcnb-test
dependencyBuildpackReference::Local
variant to specify a path to a buildpack or meta-buildpackGUS-W-13871688.