-
Notifications
You must be signed in to change notification settings - Fork 290
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
feat: reusable containers #636
base: main
Are you sure you want to change the base?
feat: reusable containers #636
Conversation
... second_id == container._container.id | ||
>>> print(first_id == second_id) | ||
True | ||
|
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.
Should the user be warned that by using this feature, containers need to be removed manually? (That this feature should not be used in a CI)
Also, do we need to make clear how this feature works (explaining the hash in use). -> If a container's run configuration changes, the hash changes and a new container will be used.
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.
seems like you have added these comments to the doc, i think that is fine. the hash would be great to add as users would benefit from knowing exactly what is hashed.
self.image,
self._command,
self.env,
self.ports,
self._name,
self.volumes,
str(tuple(sorted(self._kwargs.items()))),
- this may fail and why i want to have this be tucked away inside an obviously readableif
block
44660f1
to
74c8df1
Compare
adresses testcontainers#109 Co-authored-by: Levi Szamek <[email protected]>
50e6160
to
e87e782
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #636 +/- ##
=======================================
Coverage ? 77.27%
=======================================
Files ? 11
Lines ? 616
Branches ? 93
=======================================
Hits ? 476
Misses ? 113
Partials ? 27 ☔ View full report in Codecov by Sentry. |
will review and merge if you have no other plans for changes |
@alexanderankin we would welcome a review from you, thanks. They are no further plans right now except addressing what ever comes up in the code review. |
args = ( | ||
self.image, | ||
self._command, | ||
self.env, | ||
self.ports, | ||
self._name, | ||
self.volumes, | ||
str(tuple(sorted(self._kwargs.items()))), | ||
) | ||
hash_ = hashlib.sha256(bytes(str(args), encoding="utf-8")).hexdigest() | ||
|
||
if self._reuse and (not c.tc_properties_testcontainers_reuse_enable or not c.ryuk_disabled): | ||
logging.warning( | ||
"Reuse was requested (`with_reuse`) but the environment does not " | ||
+ "support the reuse of containers. To enable container reuse, add " | ||
+ "the 'testcontainers.reuse.enable=true' to " | ||
+ "'~/.testcontainers.properties' and disable ryuk by setting the " | ||
+ "environment variable 'TESTCONTAINERS_RYUK_DISABLED=true'" | ||
) | ||
|
||
if self._reuse and c.tc_properties_testcontainers_reuse_enable: | ||
docker_client = self.get_docker_client() | ||
container = docker_client.find_container_by_hash(hash_) | ||
if container: | ||
if container.status != "running": | ||
container.start() | ||
logger.info("Existing container started: %s", container.id) | ||
logger.info("Container is already running: %s", container.id) | ||
self._container = container | ||
else: | ||
self._start(hash_) | ||
else: | ||
self._start(hash_) |
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 refactor this so that is is obvious where the if clause is that triggers this?
want to make sure
- we are doing the hash inside the clause
- want to make it more readable - not in general but specifically for ensuring correcteness of logic that disables or enables reuse
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.
Thanks for the feedback! I will revisit this part next week and try to improve upon it.
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.
@alexanderankin I moved the generation of the hash inside the if-clause and removed passing the hash_
to start if reuse is not in use. I think that makes it better readable in general.
self._container.remove(force=force, v=delete_volume) | ||
if self._reuse and c.tc_properties_testcontainers_reuse_enable: | ||
self._container.stop() | ||
else: | ||
self._container.remove(force=force, v=delete_volume) |
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.
hm, isnt the point to not even stop it so it is warm for next run? i guess if people are using the explicit api then whatever. I do see a bit of a mirror with start so i guess it will just have to be consistent and maybe clear in docs.
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.
In other languages, having a reusable container does not change the contract of the stop()
method. This is obviously something that needs to be considered to make this a full fledged use case, but as of now, I would suggest we start with an experimental reusable implementation, that mirrors the Java implementation.
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.
Okay, I see. I updated the code and documentation (how to use reusable containers) to not change the contract of the stop()
method.
index.rst
Outdated
Reusable Containers (Experimental) | ||
---------------------------------- | ||
|
||
Containers can be reused across consecutive test runs. To reuse a container, the container configuration must be the same. | ||
|
||
Containers that are set up for reuse will not be automatically removed. Thus, those containers need to be removed manually. |
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.
"...removed manually."
maybe add:
"In re-usable mode, the 'stop' api on a container will now 'stop' a container, rather than 'remove' it"
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.
After this discussion, the stop method has not been changed.
... second_id == container._container.id | ||
>>> print(first_id == second_id) | ||
True | ||
|
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.
seems like you have added these comments to the doc, i think that is fine. the hash would be great to add as users would benefit from knowing exactly what is hashed.
self.image,
self._command,
self.env,
self.ports,
self._name,
self.volumes,
str(tuple(sorted(self._kwargs.items()))),
- this may fail and why i want to have this be tucked away inside an obviously readableif
block
if self._network: | ||
self._network.connect(self._container.id, self._network_aliases) | ||
return 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.
apparently this part is also fairly jank and we should remove/rework so as a note to myself i can only do that after this pr merges
Thanks for looking into this @matthiasschaub 👋 I already left some comments within the PR.
Given the above, if a container has |
self.volumes, | ||
str(tuple(sorted(self._kwargs.items()))), | ||
) | ||
hash_ = hashlib.sha256(bytes(str(args), encoding="utf-8")).hexdigest() |
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.
Ideally we use the full container create request as the hash input. In tc-java, this is the CreateContainerCmd
from docker-java, I guess we have some equivalent request object from the Docker Python SDK somewhere available?
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.
Yeah, that would be the ideal solution. Unfortunately, I could not find an equivalent function to CreateContainerCmd
in the Docker SDK for Python: Not by going through the documentation and not by browsing the code base.
do not create Ryuk cleanup instance if reuse enabled and container has been start with `with_reuse`
Thanks for the review @kiview! I agree. It is sensible to follow the Java implementation. In commit 1ea9ed1 I do not create a Reaper instance during container start-up if reuse is enabled and container has been started with |
adresses #109
Todo:
with_reuse
in use but ryuk is disabled.Open questions:
reuse_enable
also be configurable via environment variable?with_reuse
in use but ryuk is disabled.~/.testcontainers.properties
. This file should not be present during test run.DockerContainer.reusable: bool = True
)