Docker.DotNet icon indicating copy to clipboard operation
Docker.DotNet copied to clipboard

Example how to run command in running container

Open akash3456 opened this issue 6 years ago • 17 comments

has anyone been able to run a mkdir in a running container successfully using the dotnet api?

I cant seem to see the directory being created in the container. this is the code i have tried: -

var created = await Docker.Containers.ExecCreateContainerAsync("3375e8971d875972dd0d5bb324c43270335a5266fa51946731b1d52192709f23", new ContainerExecCreateParameters() { AttachStdin = true, AttachStdout = true, Cmd = new List() { "mkdir", "test" }, Tty = true, AttachStderr = true,

        });

        await Docker.Containers.StartContainerExecAsync(created.ID);

end result no folder in the container.

any ideas would be great.

akash3456 avatar Feb 04 '19 19:02 akash3456

Here is a working example, i use it to make exec possible trough websocket to be able to use it from xtermjs (exec does not provide ws endpoint like attach) Its probably badly written and lacks proper cancellation etc, but you get the idea

var execCreateResp = await client.Containers.ExecCreateContainerAsync(id, new Docker.DotNet.Models.ContainerExecCreateParameters()
{
    AttachStderr = true,
    AttachStdin = true,
    AttachStdout = true,
    Cmd = new string[] { "powershell" },
    Detach = false,
    Tty = true,
    Privileged = true
}
    );                    


using (var stream = await client.Containers.StartAndAttachContainerExecAsync(execCreateResp.ID, false, default(CancellationToken)))
{
    // Get Info of Exec Instance
    var execInspectResp = await client.Containers.InspectContainerExecAsync(execCreateResp.ID, default(CancellationToken));
    var pid = execInspectResp.Pid;
    _logger.LogInformation($"Started Container Console (env-id: {environment.Id}, pid: {pid}");


    // Read from Docker to WS
    var tRead = Task.Run(async () =>
    {
        var dockerBuffer = System.Buffers.ArrayPool<byte>.Shared.Rent(81920);
        try
        {
            while (true)
            {
                // Clear buffer
                Array.Clear(dockerBuffer, 0, dockerBuffer.Length);
                var dockerReadResult = await stream.ReadOutputAsync(dockerBuffer, 0, dockerBuffer.Length, default(CancellationToken));

                if (dockerReadResult.EOF)
                    break;
                

                if (dockerReadResult.Count > 0)
                {
                    bool endOfMessage = true; 
                        await _webSocket.SendAsync(new ArraySegment<byte>(dockerBuffer, 0, dockerReadResult.Count), WebSocketMessageType.Text, endOfMessage, CancellationToken.None);
                }
                else
                    break;
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failure during Read from Docker Exec to WebSocket");
        }
        System.Buffers.ArrayPool<byte>.Shared.Return(dockerBuffer);
    });


    // Write WS to Docker                             
    var tWrite = Task.Run(async () =>
    {
        WebSocketReceiveResult wsReadResult = null;
        // Read only small amount of chars at once (performance)!
        var wsBuffer = System.Buffers.ArrayPool<byte>.Shared.Rent(10);
        try
        {                                
            while (true)
            {
                // Clear buffer
                Array.Clear(wsBuffer, 0, wsBuffer.Length);
                wsReadResult = await _webSocket.ReceiveAsync(new ArraySegment<byte>(wsBuffer), CancellationToken.None);
                await stream.WriteAsync(wsBuffer, 0, wsBuffer.Length, default(CancellationToken));
                if (wsReadResult.CloseStatus.HasValue)
                {
                    _logger.LogInformation($"Stop Container Console (env-id: {environment.Id}, pid: {pid}");
                    var killSequence = Encoding.ASCII.GetBytes($"Stop-Process -Id {pid}{Environment.NewLine}");
                    await stream.WriteAsync(killSequence, 0, killSequence.Length,
                        default(CancellationToken));
                    break;
                }
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failure during Write to Docker Exec from WebSocket");
        }
        System.Buffers.ArrayPool<byte>.Shared.Return(wsBuffer);
    });

    await tRead;
    await tWrite;
}

Maybe this can help you... at least you should be able to read stdout/stderr

marknitek avatar Feb 05 '19 06:02 marknitek

This does not seem to work as i get a response declaring the exec instance is not running after starting the exec instance:- this is the code i have tried :- [TestMethod] public async System.Threading.Tasks.Task TestMethod1Async() { IDockerClient _Client = new DockerClientConfiguration(new Uri("npipe://./pipe/docker_engine")).CreateClient();

        DockerContainerResponse _containerResponse = new DockerContainerResponse();

        try
        {
            List<string> commands = new List<string>();
            commands.Add("mkdir test_instance");

            ContainerExecCreateParameters inParameters = new ContainerExecCreateParameters();
            inParameters.AttachStdin = true;
            inParameters.AttachStdout = true;
            inParameters.AttachStderr = true;
            inParameters.Cmd = commands;
            inParameters.Tty = false;
            inParameters.Detach = false;
            inParameters.User = "";
            inParameters.Privileged = true;


            var execID = await _Client.Containers.ExecCreateContainerAsync("sad_mestorf", inParameters);

            await _Client.Containers.StartAndAttachContainerExecAsync(execID.ID,false);

            var response = await _Client.Containers.InspectContainerExecAsync(execID.ID);

            var temp = response.Running;
            
        }
        catch (Exception oException)
        {

        }

akash3456 avatar Feb 05 '19 11:02 akash3456

@marknitek Would you mind sharing your full code (in private)? I think I saw you around the NAV community, am I right? I am currently working on a tool for easier container administration for NAV / BC (https://github.com/tfenster/BCinaB) and am somewhat struggling to implement a browser-based terminal for the containers, which you already seem to have in place, if I read your post correctly?

I would of course include full credits to you but maybe could spend my time on other improvements if I could include your code

tfenster avatar Feb 05 '19 21:02 tfenster

@tfenster Yes NAV is my background and i used the mentioned code in a very similar project to BCinaB. Sure i can share you the code, i will contact you by mail. I think it would be great to share some ideas since our projects are very similar!

Yes i managed to integrate a terminal (to the running docker container) in the browser by using Docker.DotNet/ASP.NET Core/Websockets /xtermjs

marknitek avatar Feb 06 '19 06:02 marknitek

@akash3456 I'am sorry to hear that it does not work. Does inspect tell you its not running? Could it be that it is because mkdir is already executed/exited at that time? Maybe you should try executing a shell and writing to it like in my example, then you are sure the process is not exited?

marknitek avatar Feb 06 '19 06:02 marknitek

@akash3456 your code is working for me. Take a look if you have privileges to create directory in root, because your command will create the directory in root. If not try to create the directory in /tmp.

...
Cmd = new List<string>()
{
   "mkdir",
   "/tmp/test"
},
...

glucaci avatar Feb 15 '19 17:02 glucaci

@marknitek I'd be really interested to see a working setup for using Docker.DotNet, xterm.js, and websockets. Do you have anything you could share?

jamiewest avatar Feb 28 '19 06:02 jamiewest

The naming of this is very confusing - how is Exec a "Create" operation? It is executing a process in an existing container

Cloudmersive avatar Aug 16 '20 05:08 Cloudmersive

ExecCreateContainerAsync is missing in the newest version. Has it any new equivalent to emulate running "docker exec" command?

Stadzior avatar Dec 01 '22 16:12 Stadzior

ExecCreateContainerAsync is missing in the newest version. Has it any new equivalent to emulate running "docker exec" command?

What makes you think that?

https://github.com/dotnet/Docker.DotNet/blob/30181cc146076ee8dc7ab839535ae89128672531/src/Docker.DotNet/Endpoints/IExecOperations.cs#L24

HofmeisterAn avatar Dec 01 '22 16:12 HofmeisterAn

@HofmeisterAn oh ok it was moved from "Containers" to "Exec" so it can be reached via client.Exec.ExecCreateContainerAsync instead of client.Containers.ExecCreateContainerAsync.

Stadzior avatar Dec 01 '22 16:12 Stadzior

I'm trying to run the following commands inside a rabbitmq container:

docker exec incoming.queue rabbitmqadmin declare exchange name=test-exchange type=direct
docker exec incoming.queue rabbitmqadmin declare queue name=test-queue durable=false
docker exec incoming.queue rabbitmqadmin declare binding source=test-exchange destination_type=queue destination=test-queue routing_key=test-key

My latest try looks like this:

        var queueParameters = new CreateContainerParameters
        {
            Name = "incoming.queue",
            Image = "rabbitmq:3.11-management",
            ExposedPorts = new Dictionary<string, EmptyStruct>
            {
                {"15672", default}
            },
            HostConfig = new HostConfig
            {
                //AutoRemove = true,
                PortBindings = new Dictionary<string, IList<PortBinding>>
                {
                    {"15672", new List<PortBinding> { new() { HostPort = "15673" } }}
                }
            },
            Labels = new Dictionary<string, string>
            {
                {"purpose", "queue"},
                {"case", "queue"},
                {"apptype", "consoleapp"}
            }
        };

        var queueContainer = await client.Containers.CreateContainerAsync(queueParameters);
        await client.Containers.StartContainerAsync(queueContainer.ID, new ContainerStartParameters());
        Console.WriteLine($"Started queue container with id: {queueContainer.ID}");

        var execDeclareExchangeParameters = new ContainerExecCreateParameters
        {
            Cmd = new List<string> { "rabbitmqadmin", "declare", "exchange", "name=test-exchange","type=direct" }
        };
        var execDeclareExchange = await client.Exec.ExecCreateContainerAsync(queueContainer.ID, execDeclareExchangeParameters);
        await client.Exec.StartAndAttachContainerExecAsync(execDeclareExchange.ID, false);
        Console.WriteLine($"Declared exchange inside queue container with id: {queueContainer.ID}");

        var execDeclareQueueParameters = new ContainerExecCreateParameters
        {
            Cmd = new List<string> { "rabbitmqadmin" ,"declare" ,"queue" ,"name=test-queue" ,"durable=false" }
        };
        var execDeclareQueue = await client.Exec.ExecCreateContainerAsync(queueContainer.ID, execDeclareQueueParameters);
        await client.Exec.StartAndAttachContainerExecAsync(execDeclareQueue.ID, false);
        Console.WriteLine($"Declared queue inside queue container with id: {queueContainer.ID}");

        var execDeclareBindingParameters = new ContainerExecCreateParameters
        {
            Cmd = new List<string> { "rabbitmqadmin" ,"declare" ,"binding" ,"source=test-exchange" ,"destination_type=queue" ,"destination=test-queue" ,"routing_key=test-key" }
        };
        var execDeclareBinding = await client.Exec.ExecCreateContainerAsync(queueContainer.ID, execDeclareBindingParameters);
        await client.Exec.StartAndAttachContainerExecAsync(execDeclareBinding.ID, false);
        Console.WriteLine($"Declared binding inside queue container with id: {queueContainer.ID}");

What's funny, it works with "mkdir" (e.g. creating 3 directories with 3 execs) but does not work with rabbitmq. I'm not getting any logs nor error messages from the container except those that are generated during rabbitmq startup. Any ideas on what's missing?

Stadzior avatar Dec 02 '22 17:12 Stadzior

Any ideas on what's missing?

You need to attach to stdout and stderr to retrieve the output and then inspect the exec response.

HofmeisterAn avatar Dec 02 '22 18:12 HofmeisterAn

Ok so the error is: Could not connect: [Errno 99] Cannot assign requested address which means rabbitmq did not start yet when the command was executed. Thank you very much @HofmeisterAn for your help.

Stadzior avatar Dec 02 '22 19:12 Stadzior

@HofmeisterAn I'm sorry but I was wrong. The rabbitmq is started while the commands are executed. E.g. changing:

        var execDeclareExchangeParameters = new ContainerExecCreateParameters
        {
            Cmd = new List<string> { "rabbitmqadmin", "declare", "exchange", "name=test-exchange","type=direct" }
        };

to:

        var execDeclareExchangeParameters = new ContainerExecCreateParameters
        {
            Cmd = new List<string> { "rabbitmqadmin" }
        };

gave me the following error:

ERROR: Action not specified
rabbitmqadmin --help for help

I'm not sure if that's a RabbitMQ or Docker.DotNet issue, but running docker exec from cmd on host works fine so I'm leaning towards accusing Docker.DotNet.

Please see for yourself here if you are still interested in helping out resolving this strange issue: https://github.com/Stadzior/DotNetContenerization/tree/main/ElToroRojo

Stadzior avatar Dec 02 '22 21:12 Stadzior

Probably the RabbitMQ service is not running or ready to use yet. I encountered no issues. At Testcontainers we use StartAndAttachContainerExecAsync frequently. I doubt it is an issue with Docker.DotNet.

HofmeisterAn avatar Dec 02 '22 22:12 HofmeisterAn

@HofmeisterAn you were right, It took RabbitMQ several more seconds to be ready to use. Thanks once again.

Stadzior avatar Dec 04 '22 09:12 Stadzior