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
Use shiplift HTTP Docker client library instead of CLI #216
Conversation
Creating and running container working, using tokio::runtime to get around the fact tha shiplift is mostly async
Hey @thomaseizinger , shiplift library is pretty good so far. (At least the git repo is, 0.6.0 is pretty outdated) I have a directional question: shiplift is async by nature and testcontainers-rs aren't doing that yet. Do you think I should start making the Docker trait async or use tokio::runtime to hack around it to emulate what Cli module is doing right now. I attached some WIP code, the |
Yes, I think making the trait async is the way to go. The migration of existing code while certainly possible would be quite tedious. Maybe we can start with adding a For async-traits, you will need https://docs.rs/async-trait/0.1.41/async_trait/. |
Also, thanks for picking this up, I am excited to land this :) |
Got it, I'll get started on async trait route then. It'll save me a lot of dancing around with log handling too. I think it's a good approach. |
Also fixed a lifetime error: Box by detaul assumes static
make test use tokio::test
@thomaseizinger ready for some constructive feedback! |
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.
First, thank you so much for working on this @ywalterh! :)
Using an HTTP-based docker client is something I've envisioned from the very beginning! It is nice to see it happening now!
I've left several comments with many of them being fairly small.
There is one bigger topic regarding refactoring how wait_until_ready
works. I believe it would be worthwhile try and get rid of the ImageAsync
trait so that we don't have to redefine all images and they are automatically compatible with several clients.
Let me know if you are still keen to work on this! If you do, it would be good to have this refactoring as a separate commit at the beginning of this PR (or maybe even as a completely separate PR and we rebase this on top of that) so that I can easily revert the async stuff in the releases branches until shiplift
cuts a release!
@@ -18,6 +18,10 @@ rand = "0.7" | |||
serde = { version = "1", features = ["derive"] } | |||
serde_json = "1" | |||
sha2 = "0.9" | |||
shiplift = {git = "https://github.com/softprops/shiplift" } | |||
tokio = { version = "0.2", features = ["macros"] } | |||
futures = "0.3.6" |
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.
Can we reduce the dependency footprint here by disabling default features and only taking what we need?
@@ -32,4 +36,5 @@ rusoto_sqs = "0.45" | |||
spectral = "0.6" | |||
reqwest = { version = "0.10", features = ["blocking"] } | |||
json = "0.12" | |||
tokio = { version = "0.2", features = ["macros"] } | |||
tokio-test = "0.2.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.
I think we are not actually using this!
@@ -18,6 +18,10 @@ rand = "0.7" | |||
serde = { version = "1", features = ["derive"] } | |||
serde_json = "1" | |||
sha2 = "0.9" | |||
shiplift = {git = "https://github.com/softprops/shiplift" } | |||
tokio = { version = "0.2", features = ["macros"] } |
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 believe we are only using tokio::test
, correct? Then this can move to dev-dependencies
!
// this is doing unnecessary stuff | ||
let network_list_optons = NetworkListOptions::default(); | ||
let networks = client.networks().list(&network_list_optons).await.unwrap(); | ||
println!("{:?}", networks); |
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 should be a log statement if we want to keep it.
fn drop(&mut self) { | ||
let guard = self.created_networks.read().expect("failed to lock RwLock"); | ||
for network in guard.iter() { | ||
block_on(async { self.client.networks().get(network).delete().await.unwrap() }); |
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.
Nit: This should do the trick as well :)
block_on(async { self.client.networks().get(network).delete().await.unwrap() }); | |
block_on(self.client.networks().get(network).delete()).unwrap(); |
} | ||
|
||
/// Log streams of running container (Stream<Item = Vec<u8>>). | ||
#[derive(derivative::Derivative)] |
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've dropped the Derivative
dependency in dev
already. Given that we are not actually including any fields within the debug output, we can easily construct it manually and can therefore avoid this dependency!
self | ||
} | ||
|
||
pub fn with_mapped_port<P: Into<Port>>(mut self, port: P) -> Self { |
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 are moving this functionality into the client with #228 to make images easier to implement. Please adapt this implementation accordingly!
pub async fn logs<'a>(&'a self) -> LogsAsync<'a> { | ||
let id_clone: String = String::from(&self.id); | ||
self.docker_client | ||
.logs(Box::leak(id_clone.into_boxed_str())) |
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 Box::leak
is a bit concerning. I think we have a memory leak here if I am reading the docs correctly.
Feel free to change the signature of .logs
if you don't want to mess around with manual dropping here.
/// | ||
/// Most implementations will very likely want to make use of this to wait for a particular | ||
/// message to be emitted. | ||
async fn wait_until_ready<D: DockerAsync + Sync>( |
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 is the only function within the ImageAsync
trait that is actually async. I think we should refactor this whole "waiting" concept in a way that works for blocking and async clients. That would allow us to reuse the same image definition for both blocking and async clients. Currently, we need to redefine every image with the ImageAsync
trait to use it with an async client :/
One idea on how to achieve this would be to remove some flexibility in what images can do here. In particular, there seem to be primarily three things that images do to determine that they are ready:
- Wait for a message on stdout
- Wait for a message on stderr
- Wait for an arbitrary amount of time
I think we can model that be having a function that returns a Vec<WaitFor>
where WaitFor
is:
enum WaitFor {
Message { message: String, stream: Stream },
Time { ms: u64 }
}
@@ -18,6 +18,10 @@ rand = "0.7" | |||
serde = { version = "1", features = ["derive"] } | |||
serde_json = "1" | |||
sha2 = "0.9" | |||
shiplift = {git = "https://github.com/softprops/shiplift" } |
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.
Let's hope they are cutting a release soon! We can't release with git
dependencies so if we merge this before hand, we will need to do some manual work to exclude this code on every release. That shouldn't be too hard to so it is not actually a blocker for merging this PR!
GitFlow for the win! 😁
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.
To make this easier, could you squash all of the commits into a single one once we ready to merge this so I can just revert that when we make releases?
No problem at all. Sorry I was on Thanksgiving break and didn't reply to this sooner. Let me address these issues first and look into the feasibility of removing |
Making this feature declarative has several advantages: - It reduces the amount of code one needs to write for a new image. - It avoids the need for an `ImageAsync` trait once we introduce that, see discussions in #216 for details. - It allows for a re-usable abstraction of waiting for a specific amount of time, which fixes #39.
Making this feature declarative has several advantages: - It reduces the amount of code one needs to write for a new image. - It avoids the need for an `ImageAsync` trait once we introduce that, see discussions in #216 for details. - It allows for a re-usable abstraction of waiting for a specific amount of time, which fixes #39.
Making this feature declarative has several advantages: - It reduces the amount of code one needs to write for a new image. - It avoids the need for an `ImageAsync` trait once we introduce that, see discussions in #216 for details. - It allows for a re-usable abstraction of waiting for a specific amount of time, which fixes #39.
Making this feature declarative has several advantages: - It reduces the amount of code one needs to write for a new image. - It avoids the need for an `ImageAsync` trait once we introduce that, see discussions in #216 for details. - It allows for a re-usable abstraction of waiting for a specific amount of time, which fixes #39.
253: Replace `wait_until_ready` with declarative `ready_conditions` r=thomaseizinger a=thomaseizinger Making this feature declarative has several advantages: - It reduces the amount of code one needs to write for a new image. - It avoids the need for an `ImageAsync` trait once we introduce that, see discussions in #216 for details. cc @ywalterh - It allows for a re-usable abstraction of waiting for a specific amount of time, which fixes #39. Co-authored-by: Thomas Eizinger <thomas@eizinger.io>
Making this feature declarative has several advantages: - It reduces the amount of code one needs to write for a new image. - It avoids the need for an `ImageAsync` trait once we introduce that, see discussions in #216 for details. - It allows for a re-usable abstraction of waiting for a specific amount of time, which fixes #39.
Making this feature declarative has several advantages: - It reduces the amount of code one needs to write for a new image. - It avoids the need for an `ImageAsync` trait once we introduce that, see discussions in #216 for details. - It allows for a re-usable abstraction of waiting for a specific amount of time, which fixes #39.
Followed the discussion of issue #204
Added shiplift library implementing basic Docker trait
Make shiplift implementation feature equivilent
Replace Cli in library and pass all test
Figure out Drop pattern
Implement GenericImageAsync
Implement a canned image in Async trait
Add more integration test to shiplift client
Worklog: