-
-
Notifications
You must be signed in to change notification settings - Fork 201
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 building a container from context archive #671
Conversation
✅ Deploy Preview for testcontainers-node ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
"@types/tmp": "^0.2.5" | ||
"@types/tar-stream": "^3.1.3", | ||
"@types/tmp": "^0.2.5", | ||
"tar-stream": "^3.1.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.
tar-stream
shouldn't be in dependencies 🤔 ?
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 only used it in the tests to generate a test tar stream. It is not used in the package's production code.
private readonly dockerfileName: string, | ||
private readonly uuid: Uuid = new RandomUuid() | ||
) {} | ||
|
||
public withBuildArgs(buildArgs: BuildArgs): GenericContainerBuilder { | ||
public withBuildArgs(buildArgs: BuildArgs): this { |
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 to do the return this pattern consistent here 👏
@@ -103,3 +105,27 @@ describe("GenericContainer Dockerfile", () => { | |||
await startedContainer.stop(); | |||
}); | |||
}); | |||
|
|||
describe("GenericContainer fromContextArchive", () => { |
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 to add tests 🎉
import { getAuthConfig } from "../../auth/get-auth-config"; | ||
import { ImageName } from "../../image-name"; | ||
import { ImageClient } from "./image-client"; | ||
import AsyncLock from "async-lock"; | ||
import { log, buildLog, pullLog } from "../../../common"; | ||
import stream from "stream"; |
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 node stream api https://nodejs.org/api/stream.html#stream right?
sometimes when I dont read it as
const stream = require('node:stream');
has this doubt 🤔
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.
Yes, it is. I can change the import name with node:stream
, but I don't know the difference.
@@ -105,3 +138,17 @@ export class GenericContainerBuilder { | |||
.reduce((prev, next) => ({ ...prev, ...next }), {} as RegistryConfig); | |||
} | |||
} | |||
|
|||
async function newDockerignoreFilter(context: string): Promise<(path: string) => boolean> { |
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.
Only to curiosity in your approach, why newDockerignoreFilter
function is not part from class GenericContainerBuilder
as a private method take into account that you are only using it to this class responsabilities?
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 class methods should access or modify the object instance.
In this case, newDockerignoreFilter
does not access nor modify any object fields. It can be seen as a factory method. It is a function that, given a context path, returns a .dockerignore
based filter function.
After reviewing the PR I think that for me it is LGTM and approved. @LNSD Thanks for the explanations about how the docker and dockerode api works to build an image with a dockerfile (recipe) and a files source (ingredients aka context), either through a path on our disk or through an external data source, In both cases, we have to package these files into a tar file and create a stream from it which is as the docker api requires in its context. The feature itself is very powerful, being able to programmatically create a dynamic dockerfile and feed the stream with ingredients that do not exactly belong to our disk, a use case, an AWS stream. I see the refactor that you include to allow the feature appropriate, promoting the creation of the source tar file which we will create the stream from. Only there are some questions that I put in my review to give you my approval |
@@ -44,6 +48,12 @@ export class GenericContainerBuilder { | |||
return this; | |||
} | |||
|
|||
/** |
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 really really really really appreciate these comments for our methods and functions but, since they are not anywhere in the library, I don't know if it would be convenient to start doing it. 🤔
This comment was marked as resolved.
This comment was marked as resolved.
try { | ||
log.debug(`Building image "${opts.t}" with context "${context}"...`); |
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.
What's the reasoning behind moving this logic to the GenericContainerBuilder
? I liked the idea that the DockerImageClient
can be used standalone and supports .dockerignore
files out of the box. After this change this is no longer the case
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 understand your point.
I decided to raise the filtering to a higher abstraction level, recognizing that the .dockerignore
filtering is a specific instance of passing a tar stream to the container runtime client. This approach streamlines the code and renders the DockerImageClient
agnostic to the underlying filesystem, enhancing its versatility.
At the time of implementation, I faced two options:
-
Introduce a method in
GenericContainerBuilder
andDockerImageClient
for passing the stream directly to the container runtime client without.dockerignore
filtering. This would create an additional flow in both classes. -
Maintain the
DockerImageClient
agnostic to the filesystem (not utilizingtar-fs
) and keep the API straightforward by passing a stream to the container runtime client. Since the filtered stream is a subset of the passed stream, this approach avoids needing two separate APIs in both the client and theGenericContainerBuilder
class.
This PR adds support for building a container image from a tar archive stream (i.e.,
NodeJs.ReadableStream
).This covers the case where one wants to programmatically create a dynamic
Dockerfile
and context (e.g., using a template engine) without the overhead of writing to disk.