mariotoffia / FluentDocker

Use docker, docker-compose local and remote in tests and your .NET core/full framework apps via a FluentAPI
Apache License 2.0
1.31k stars 97 forks source link

Add podman support #300

Open atrauzzi opened 1 year ago

atrauzzi commented 1 year ago

Given Dockers decline in reputation over the last while, there's a lot of movement towards podman.

I just tried using this library to run a container on my local podman (linux, Fedora, no Windows!) environment and got the following:

Unhandled exception. Newtonsoft.Json.JsonSerializationException: Error converting value "" to type 'Ductus.FluentDocker.Model.Containers.HealthState'. Path 'State.Health.Status', line 22, position 32.
 ---> System.ArgumentException: Must specify valid information for parsing in the string.
   at Newtonsoft.Json.Utilities.EnumUtils.ParseEnum(Type enumType, NamingStrategy namingStrategy, String value, Boolean disallowNumber)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.EnsureType(JsonReader reader, Object value, CultureInfo culture, JsonContract contract, Type targetType)
   --- End of inner exception stack trace ---
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.EnsureType(JsonReader reader, Object value, CultureInfo culture, JsonContract contract, Type targetType)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
   at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
   at Newtonsoft.Json.JsonSerializer.Deserialize(JsonReader reader, Type objectType)
   at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings)
   at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings)
   at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value)
   at Ductus.FluentDocker.Executors.Parsers.ClientContainerInspectCommandResponder.Process(ProcessExecutionResult response)
   at Ductus.FluentDocker.Executors.ProcessExecutor`2.Execute()
   at Ductus.FluentDocker.Commands.Client.InspectContainer(DockerUri host, String id, ICertificatePaths certificates)
   at Ductus.FluentDocker.Services.Impl.DockerHostService.Create(String image, Boolean forcePull, ContainerCreateParams prms, Boolean stopOnDispose, Boolean deleteOnDispose, Boolean deleteVolumeOnDispose, Boolean deleteNamedVolumeOnDispose, String command, String[] args, Func`4 customEndpointResolver)
   at Ductus.FluentDocker.Builders.ContainerBuilder.Build()

Anyway, I run containers all the time for local development using jetbrains tooling and CLI. I have the docker and docker-compose commands available, mapped to their fully API compatible podman equivalents (if it matters).

Rory-Reid commented 11 months ago

I've just tried playing around with podman too and hit the same thing, but I'm not convinced it is FluentDocker's fault.

This failure occurs when parsing the result of docker inspect, and it turns out I cannot manually create and inspect a single container on my machine and get a valid health state (I'm on macOS if that's useful), they all look like this:

"Health": {
  "Status": "",
  "FailingStreak": 0,
  "Log": null
}

FluentDocker is trying to parse Status to an enum and not defaulting empty string to unknown, which I guess it could but then the .WaitForHealthy() builder function would fail since it looks like it relies on that? I never actually rely on that command it turns out which would work for me but something still feels off.

Not had much luck finding out why this is the case in podman yet, and don't have docker available to compare with just yet.

atrauzzi commented 11 months ago

Running on Fedora and [podman|docker] inspect works for me.

Your issue might be mac specific. I'd still say this library needs to investigate given that the JetBrains container tooling is working flawlessely.

Rory-Reid commented 11 months ago

[podman|docker] inspect works for me too? It just doesn't give the exact same format output as docker does as far as I can tell, which is why the parsing falls down here. Your exception and stack trace look the same as what I experienced, I could be missing something but it all reads like what I reproduced and observed. Found similar problems on my linux laptop too.

Nonetheless, I've dumped my podman inspect output into a unit test here to demonstrate the failure: https://github.com/Rory-Reid/FluentDocker/tree/reproduce-missing-health-status

There's two issues with it. State.Health.Status and Config.Entrypoint don't parse, I'm going to see if I can suggest a fix for those, and explore if there's other issues.

Rory-Reid commented 11 months ago

Definitely more issues once you get past that, failing on the following command:

docker network ls --no-trunc --format "{{.ID}};{{.Name}};{{.Driver}};{{.Source}};{{.IPv6}};{{.Internal}};{{.CreatedAt}}"

Several of these properties (eg Source, IPv6, CreatedAt) are not available on either machine I've tested with, and the --format fails very ungracefully when that happens. Within FluentDocker, ID and Name are the only ones currently used, so defensively we could avoid trying to request and parse what we don't need here (though as of now I don't know if this class is public / something you could or would depend on in client code).

Next, it crashes when FluentDocker tries to parse the output of docker top {container id}. This is because the TIME column looks something like:

TIME
0s
0s
0s
0s
0s
0s
0s

And docker's output looks like this:

TIME
00:00:00
00:00:04
00:00:00
01:28:20
00:00:00
00:00:12
00:00:00

and it cannot parse 0s to a valid TimeSpan

Once again, it looks like this is parsed by FluentDocker but without any internal usage, so could be removed for a more defensive approach to parsing output. It feels a bit rude to go chopping all of this away to support podman here, and ideally podman output would be truly interchangeable with docker output and we'd have none of these issues, but maybe a case could be made for a more generally defensive approach to FluentDocker which would just so happen to solve the immediate problems - remove unused code, and only parse what is strictly needed today?

After making all of these amendments in a local branch, FluentDocker appears to work for me, with podman v4.6.2, within the scope of my limited use in test projects (3 different containers). I'm sure there's a lot more edge cases beyond this.

Rory-Reid commented 11 months ago

Have published #303 as a hopefully amicable first-pass support for podman, obviously subject to the maintainer actually wanting to bring this in.

mariotoffia commented 10 months ago

Nice work @Rory-Reid - I greatly appreciate it!

I had one comment, could you please check it out?

Cheers

atrauzzi commented 5 months ago

Any chance this can progress?

mariotoffia commented 5 months ago

@atrauzzi sorry, yes, I'll have a look this weekend and make sure to get this going!