Package | NuGet |
---|---|
FluentDocker | |
Microsoft Test | |
XUnit Test |
This library enables docker
and docker-compose
interactions usinga Fluent API. It is supported on Linux, Windows and Mac. It also has support for the legazy docker-machine
interactions.
Sample Fluent API usage
using ( var container = new Builder().UseContainer() .UseImage("kiasaki/alpine-postgres") .ExposePort(5432) .WithEnvironment("POSTGRES_PASSWORD=mysecretpassword") .WaitForPort("5432/tcp", 30000 /*30s*/) .Build() .Start()) { var config = container.GetConfiguration(true); Assert.AreEqual(ServiceRunningState.Running, config.State.ToServiceState()); }
This fires up a postgres and waits for it to be ready. To use compose, just do it like this:
:bulb: NOTE: Use the AssumeComposeVersion(ComposeVersion.V2) to use the V2 behaviour, default is still V1 (to be changed to default to V2 later this year)
var file = Path.Combine(Directory.GetCurrentDirectory(), (TemplateString) "Resources/ComposeTests/WordPress/docker-compose.yml"); // @formatter:off using (var svc = new Builder() .UseContainer() .UseCompose() .FromFile(file) .RemoveOrphans() .WaitForHttp("wordpress", "http://localhost:8000/wp-admin/install.php") .Build().Start()) // @formatter:on { // We now have a running WordPress with a MySql database var installPage = await "http://localhost:8000/wp-admin/install.php".Wget(); Assert.IsTrue(installPage.IndexOf("https://wordpress.org/", StringComparison.Ordinal) != -1); Assert.AreEqual(1, svc.Hosts.Count); // The host used by compose Assert.AreEqual(2, svc.Containers.Count); // We can access each individual container Assert.AreEqual(2, svc.Images.Count); // And the images used. }
:bulb Note for Linux Users: Docker requires sudo by default and the library by default expects that executing user do not need to do sudo in order to talk to the docker daemon. More description can be found in the Talking to Docker Daemon chapter.
The fluent API builds up one or more services. Each service may be composite or singular. Therefore it is possible to e.g. fire up several docker-compose based services and manage each of them as a single service or dig in and use all underlying services on each docker-compose service. It is also possible to use services directly e.g.
var file = Path.Combine(Directory.GetCurrentDirectory(), (TemplateString) "Resources/ComposeTests/WordPress/docker-compose.yml"); using (var svc = new DockerComposeCompositeService(DockerHost, new DockerComposeConfig { ComposeFilePath = new List<string> { file }, ForceRecreate = true, RemoveOrphans = true, StopOnDispose = true })) { svc.Start(); // We now have a running WordPress with a MySql database var installPage = await $"http://localhost:8000/wp-admin/install.php".Wget(); Assert.IsTrue(installPage.IndexOf("https://wordpress.org/", StringComparison.Ordinal) != -1); }
The above example creates a docker-compose service from a single compose file. When the service is disposed all underlying services is automatically stopped.
The library is supported by .NET full 4.51 framework and higher, .NET standard 1.6, 2.0. It is divided into three thin layers, each layer is accessible:
The Majority of the service methods are extension methods and not hardwired into the service itself, making them lightweight and customizable. Since everything is accessible it is e.g. easy to add extensions method for a service that uses the layer 1 commands to provide functionality.
I do welcome contribution, though there is no contribution guideline as of yet, make sure to adhere to .editorconfig when doing the Pull Requests. Otherwise the build will fail. I'll update with a real guideline sooner or later this year.
All commands needs a DockerUri
to work with. It is the Uri to the docker daemon, either locally or remote. It can be discoverable or hardcoded. Discovery of local DockerUri
can be done by
var hosts = new Hosts().Discover(); var _docker = hosts.FirstOrDefault(x => x.IsNative) ?? hosts.FirstOrDefault(x => x.Name == "default");
The example snipped will check for native, or docker beta "native" hosts, if not choose the docker-machine "default" as host. If you're using docker-machine and no machine exists or is not started it is easy to create / start a docker-machine by e.g. "test-machine".Create(1024,20000000,1)
. This will create a docker machine named "test-machine" with 1GB of RAM, 20GB Disk, and use one CPU.
It is now possible to use the Uri to communicate using the commands. For example to get the version of client and server docker binaries:
var result = _docker.Host.Version(_docker.Certificates); Debug.WriteLine(result.Data); // Will Print the Client and Server Version and API Versions respectively.
All commands return a CommandResponse<T> such that it is possible to check success factor by response.Success
. If any data associated with the command it is returned in the response.Data
property.
Then it is simple as below to start and stop include delete a container using the commands. Below starts a container and do a PS on it and then deletes it.
var id = _docker.Host.Run("nginx:latest", null, _docker.Certificates).Data; var ps = _docker.Host.Ps(null, _docker.Certificates).Data; _docker.Host.RemoveContainer(id, true, true, null, _docker.Certificates);
When running on windows, one can choose to run linux or windows container. Use the LinuxDaemon
or WindowsDaemon
to control which daemon to talk to.
_docker.LinuxDaemon(); // ensures that it will talk to linux daemon, if windows daemon it will switch
Some commands returns a stream of data when e.g. events or logs is wanted using a continuous stream. Streams can be used in background tasks and support CancellationToken
. Below example tails a log.
using (var logs = _docker.Host.Logs(id, _docker.Certificates)) { while (!logs.IsFinished) { var line = logs.TryRead(5000); // Do a read with timeout if (null == line) { break; } Debug.WriteLine(line); } }
Utility methods exists for commands. They come in different flaviours such as networking etc. For example when reading a log to the end:
using (var logs = _docker.Host.Logs(id, _docker.Certificates)) { foreach (var line in logs.ReadToEnd()) { Debug.WriteLine(line); } }
The highest layer of this library is the fluent API where you can define and control machines, images, and containers. For example to setup a load balancer with two nodejs servers reading from a redis server can look like this (node image is custom built if not found in the repository).
var fullPath = (TemplateString) @"${TEMP}/fluentdockertest/${RND}"; var nginx = Path.Combine(fullPath, "nginx.conf"); Directory.CreateDirectory(fullPath); typeof(NsResolver).ResourceExtract(fullPath, "index.js"); using (var services = new Builder() // Define custom node image to be used .DefineImage("mariotoffia/nodetest").ReuseIfAlreadyExists() .From("ubuntu") .Maintainer("Mario Toffia <mario.toffia@xyz.com>") .Run("apt-get update &&", "apt-get -y install curl &&", "curl -sL https://deb.nodesource.com/setup | sudo bash - &&", "apt-get -y install python build-essential nodejs") .Run("npm install -g nodemon") .Add("emb:Ductus.FluentDockerTest/Ductus.FluentDockerTest.MultiContainerTestFiles/package.txt", "/tmp/package.json") .Run("cd /tmp && npm install") .Run("mkdir -p /src && cp -a /tmp/node_modules /src/") .UseWorkDir("/src") .Add("index.js", "/src") .ExposePorts(8080) .Command("nodemon", "/src/index.js").Builder() // Redis Db Backend .UseContainer().WithName("redis").UseImage("redis").Builder() // Node server 1 & 2 .UseContainer().WithName("node1").UseImage("mariotoffia/nodetest").Link("redis").Builder() .UseContainer().WithName("node2").UseImage("mariotoffia/nodetest").Link("redis").Builder() // Nginx as load balancer .UseContainer().WithName("nginx").UseImage("nginx").Link("node1", "node2") .CopyOnStart(nginx, "/etc/nginx/nginx.conf") .ExposePort(80).Builder() .Build().Start()) { Assert.AreEqual(4, services.Containers.Count); var ep = services.Containers.First(x => x.Name == "nginx").ToHostExposedEndpoint("80/tcp"); Assert.IsNotNull(ep); var round1 = $"http://{ep.Address}:{ep.Port}".Wget(); Assert.AreEqual("This page has been viewed 1 times!", round1); var round2 = $"http://{ep.Address}:{ep.Port}".Wget(); Assert.AreEqual("This page has been viewed 2 times!", round2); }
The above example defines a Dockerfile, builds it, for the node image. It then uses vanilla redis and nginx. If you just want to use an existing Dockerfile it can be done like this.
using (var services = new Builder() .DefineImage("mariotoffia/nodetest").ReuseIfAlreadyExists() .FromFile("/tmp/Dockerfile") .Build().Start()) { // Container either build to reused if found in registry and started here. }
The fluent API supports from defining a docker-machine to a set of docker instances. It has built-in support for e.g.
waiting for a specific port or a process within the container before Build()
completes and thus can be safely
be used within a using statement. If specific management on wait timeouts etc. you can always build and start the
container and use extension methods to do the waiting on the container itself.
To create a container just omit the start. For example:
using ( var container = new Builder().UseContainer() .UseImage("kiasaki/alpine-postgres") .WithEnvironment("POSTGRES_PASSWORD=mysecretpassword") .Build()) { Assert.AreEqual(ServiceRunningState.Stopped, container.State); }
This example creates a container with postgres, configure one environment variable. Within the using statement it is possible to start the IContainerService
. Thus each built container is wrapped in a IContainerService
. It is also possible to use the IHostService.GetContainers(...)
to obtain the created, running, and exited containers. From the IHostService
it is also possible to get all the images in the local repository to create containers from.
Whe you want to run a single container do use the fluent or container service start method. For example:
using ( var container = new Builder().UseContainer() .UseImage("kiasaki/alpine-postgres") .WithEnvironment("POSTGRES_PASSWORD=mysecretpassword") .Build() .Start()) { var config = container.GetConfiguration(); Assert.AreEqual(ServiceRunningState.Running, container.State); Assert.IsTrue(config.Config.Env.Any(x => x == "POSTGRES_PASSWORD=mysecretpassword")); }
By default the container is stopped and deleted when the Dispose method is run, in order to keep the container in archve, use the KeepContainer()
on the fluent API. When Dispose()
is invoked it will be stopped but not deleted. It is also possible to keep it running after dispose as well.
It is possible to expose ports both explicit or randomly. Either way it is possible to resolve the IP (in case of machine) and the port (in case of random port) to use in code. For example:
using ( var container = new Builder().UseContainer() .UseImage("kiasaki/alpine-postgres") .ExposePort(40001, 5432) .WithEnvironment("POSTGRES_PASSWORD=mysecretpassword") .Build() .Start()) { var endpoint = container.ToHostExposedEndpoint("5432/tcp"); Assert.AreEqual(40001, endpoint.Port); }
Here we map the container port 5432 to host port 40001 explicitly. Note the use of container.ToHostExposedEndpoint(...)
. This is to always resolve to a working ip and port to communicate with the docker container. It is also possible to map a random port, i.e. let Docker choose a available port. For example:
using ( var container = new Builder().UseContainer() .UseImage("kiasaki/alpine-postgres") .ExposePort(5432) .WithEnvironment("POSTGRES_PASSWORD=mysecretpassword") .Build() .Start()) { var endpoint = container.ToHostExposedEndpoint("5432/tcp"); Assert.AreNotEqual(0, endpoint.Port); }
The only difference here is that only one argument is used when ExposePort(...)
was used to configure the container. The same usage applies otherwise and thus is transparent for the code.
In order to know when a certain service is up and running before starting to e.g. connect to it. It is possible to wait for a specific port to be open. For example:
using ( var container = new Builder().UseContainer() .UseImage("kiasaki/alpine-postgres") .ExposePort(5432) .WithEnvironment("POSTGRES_PASSWORD=mysecretpassword") .WaitForPort("5432/tcp", 30000 /*30s*/) .Build() .Start()) { var config = container.GetConfiguration(true); Assert.AreEqual(ServiceRunningState.Running, config.State.ToServiceState()); }
In the above example we wait for the container port 5432 to be opened within 30 seconds. If it fails, it will throw an exception and thus the container will be disposed and removed (since we dont have any keep container etc. configuration).
using ( var container = new Builder().UseContainer() .UseImage("kiasaki/alpine-postgres") .ExposePort(5432) .WithEnvironment("POSTGRES_PASSWORD=mysecretpassword") .WaitForPort("5432/tcp", 30000 /*30s*/, "127.0.0.1") .Build() .Start()) { var config = container.GetConfiguration(true); Assert.AreEqual(ServiceRunningState.Running, config.State.ToServiceState()); }
Sometimes it is not possible to directly reach the container, by local ip and port, instead e.g. the container has an exposed port on the loopback interface (127.0.0.1) and that is the only way of reaching the container from the program. The above example forces the address to be 127.0.0.1 but still resolves the host port. By default, FluentDocker uses the network inspect on the container to determine the network configuration.
Sometime it is not sufficient to just wait for a port. Sometimes a container process is much more vital to wait for. Therefore a wait for process method exist in the fluent API as well as an extension method on the container object. For example:
using ( var container = new Builder().UseContainer() .UseImage("kiasaki/alpine-postgres") .ExposePort(5432) .WithEnvironment("POSTGRES_PASSWORD=mysecretpassword") .WaitForProcess("postgres", 30000 /*30s*/) .Build() .Start()) { var config = container.GetConfiguration(true); Assert.AreEqual(ServiceRunningState.Running, config.State.ToServiceState()); }
In the above example Build()
will return control when the process "postgres" have been started within the container.
In order to make use
一键生成PPT和Word,让学习生活更轻松
讯飞智文是一个利用 AI 技术的项目,能够帮助用户生成 PPT 以及各类文档。无论是商业领域的市场分析报告、年度目标制定,还是学生群体的职业生涯规划、实习避坑指南,亦或是活动策划、旅游攻略等内容,它都能提供支持,帮助用户精准表达,轻松呈现各种信息。
深度推理能力全新升级,全面对标OpenAI o1
科大讯飞的星火大模型,支持语言理解、知识问答和文本创作等多功能,适用于多种文件和业务场景,提升办公和日常生活的效率。讯飞星火是一个提供丰富智能服务的平台,涵盖科技资讯、图像创作、写作辅助、编程解答、科研文献解读等功能,能为不同需求的用户提供便捷高效的帮助,助力用户轻松获取信息、解决问题,满足多样化使用场景。
一种基于大语言模型的高效单流解耦语音令牌文本到语音合成模型
Spark-TTS 是一个基于 PyTorch 的开源文本到语音合成项目,由多个知名机构联合参与。该项目提供了高效的 LLM(大语言模型)驱动的语音合成方案,支持语音克隆和语音创建功能,可通过命令行界面(CLI)和 Web UI 两种方式使用。用户可以根据需求调整语音的性别、音高、速度等参数,生成高质量的语音。该项目适用于多种场景,如有声读物制作、智能语音助手开发等。
字节跳动发布的AI编程神器IDE
Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。
AI助力,做PPT更简单!
咔片是一款轻量化在线演示设计工具,借助 AI 技术,实现从内容生成到智能设计的一站式 PPT 制作服务。支持多种文档格式导入生成 PPT,提供海量模板、智能美化、素材替换等功能,适用于销售、教师、学生等各类人群,能高效制作出高品质 PPT,满足不同场景演示需求。
选题、配图、成文,一站式创作,让内容运营更高效
讯飞绘文,一个AI集成平台,支持写作、选题、配图、排版和发布。高效生成适用于各类媒体的定制内容,加速品牌传播,提升内容营销效果。
专业的AI公文写作平台,公文写作神器
AI 材料星,专业的 AI 公文写作辅助平台,为体制内工作人员提供高效的公文写作解决方案。拥有海量公文文库、9 大核心 AI 功能,支持 30 + 文稿类型生成,助力快速完成领导讲话、工作总结、述职报告等材料,提升办公效率,是体制打工人的得力写作神器。
OpenAI Agents SDK,助力开发者便捷使用 OpenAI 相关功能。
openai-agents-python 是 OpenAI 推出的一款强大 Python SDK,它为开发者提供了与 OpenAI 模型交互的高效工具,支持工具调用、结果处理、追踪等功能,涵盖多种应用场景,如研究助手、财务研究等,能显著提升开发效率,让开发者更轻松地利用 OpenAI 的技术优势。
高分辨率纹理 3D 资产生成
Hunyuan3D-2 是腾讯开发的用于 3D 资产生成的强大工具,支持从文本描述、单张图片或多视角图片生成 3D 模型,具备快速形状生成能力,可生成带纹理的高质量 3D 模型,适用于多个领域,为 3D 创作提供了高效解决方案。
一个具备存储、管理和客户端操作等多种功能的分布式文件系统相关项目。
3FS 是一个功能强大的分布式文件系统项目,涵盖了存储引擎、元数据管理、客户端工具等多个模块。它支持多种文件操作,如创建文件和目录、设置布局等,同时具备高效的事件循环、节点选择和协程池管理等特性。适用于需要大规模数据存储和管理的场景,能够提高系统的性能和可靠性,是分布式存储领域的优质解决方案。
最新AI工具、AI资讯
独家AI资源、AI项目落地
微信扫一扫关注公众号