DockerDiscoveryTests.cs 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. using RackPeek.Domain.Discovery;
  2. using RackPeek.Domain.Resources.Services;
  3. namespace Tests.Discovery;
  4. /// <summary>
  5. /// A captured <c>GET /containers/json</c> response in, Service resources out.
  6. /// No Docker daemon is needed, so this runs anywhere.
  7. /// </summary>
  8. public class DockerDiscoveryTests {
  9. private const string _hostSeed = "7f3c9a1e5b2d4f6081a3c5e7b9d1f3a5";
  10. private static List<Service> Discover() =>
  11. DockerServiceMapper.ToResources(
  12. DockerContainerParser.Parse(Fixture.Read("docker-containers.json")),
  13. _hostSeed,
  14. "nas01",
  15. "192.168.1.20");
  16. [Fact]
  17. public void Only_containers_reachable_from_outside_the_host_become_services() {
  18. List<Service> services = Discover();
  19. // redis publishes nothing and pgadmin only binds loopback, so nothing outside
  20. // the host can reach either and neither is a service.
  21. Assert.Equal(["jellyfin", "paperless-ngx", "unifi", "wireguard"], services.Select(s => s.Name));
  22. }
  23. [Fact]
  24. public void A_binding_pinned_to_one_interface_is_reported_at_that_address() {
  25. Service unifi = Discover().Single(s => s.Name == "unifi");
  26. // The loopback 8843 binding is ignored; the 8443 binding is only reachable on
  27. // the address it is pinned to, so that address wins over the host's.
  28. Assert.Equal("192.168.1.21", unifi.Network!.Ip);
  29. Assert.Equal(8443, unifi.Network.Port);
  30. }
  31. [Fact]
  32. public void A_dual_stack_publish_is_one_binding_not_two() {
  33. List<DockerContainer> containers = DockerContainerParser.Parse(Fixture.Read("docker-containers.json"));
  34. // Docker reports 0.0.0.0 and :: separately for the same publish.
  35. DockerContainer jellyfin = containers.Single(c => c.Name == "jellyfin");
  36. Assert.Single(jellyfin.PublishedPorts);
  37. Assert.True(jellyfin.PublishedPorts[0].IsWildcard);
  38. }
  39. [Fact]
  40. public void A_service_carries_the_address_it_is_reachable_on() {
  41. Service jellyfin = Discover().Single(s => s.Name == "jellyfin");
  42. Assert.Equal("192.168.1.20", jellyfin.Network!.Ip);
  43. Assert.Equal(8096, jellyfin.Network.Port);
  44. Assert.Equal("TCP", jellyfin.Network.Protocol);
  45. Assert.Equal("jellyfin/jellyfin:10.9.6", jellyfin.Notes);
  46. Assert.Equal(["nas01"], jellyfin.RunsOn);
  47. }
  48. [Fact]
  49. public void Udp_bindings_keep_their_protocol() =>
  50. Assert.Equal("UDP", Discover().Single(s => s.Name == "wireguard").Network!.Protocol);
  51. [Fact]
  52. public void A_compose_project_becomes_a_tag_so_a_stack_stays_grouped() {
  53. Assert.Equal(["media"], Discover().Single(s => s.Name == "jellyfin").Tags);
  54. Assert.Equal(["paperless-stack"], Discover().Single(s => s.Name == "paperless-ngx").Tags);
  55. }
  56. [Fact]
  57. public void A_container_outside_compose_gets_no_tag() =>
  58. Assert.Empty(Discover().Single(s => s.Name == "wireguard").Tags);
  59. [Fact]
  60. public void The_same_container_name_on_two_hosts_is_two_different_resources() {
  61. List<DockerContainer> containers = DockerContainerParser.Parse(Fixture.Read("docker-containers.json"));
  62. List<Service> onNas = DockerServiceMapper.ToResources(containers, "host-a", "nas01", "192.168.1.20");
  63. List<Service> onPi = DockerServiceMapper.ToResources(containers, "host-b", "pi01", "192.168.1.34");
  64. Assert.NotEqual(onNas[0].DiscoveryId, onPi[0].DiscoveryId);
  65. }
  66. [Fact]
  67. public void Rediscovering_the_same_host_produces_the_same_ids() =>
  68. Assert.Equal(
  69. Discover().Select(s => s.DiscoveryId),
  70. Discover().Select(s => s.DiscoveryId));
  71. [Fact]
  72. public void Output_conforms_to_the_published_schema() =>
  73. Fixture.AssertConformsToSchema(DiscoveryDocument.ToYaml(Discover()));
  74. [Fact]
  75. public void An_empty_daemon_yields_nothing_rather_than_failing() =>
  76. Assert.Empty(DockerContainerParser.Parse("[]"));
  77. [Fact]
  78. public void A_socket_endpoint_counts_as_local_but_tcp_does_not() {
  79. // Local is what decides whether the host System rides along in the payload:
  80. // over TCP the locally probed facts describe this machine, not the engine's.
  81. // The no-argument default is not asserted here because it honours DOCKER_HOST,
  82. // which a developer machine may legitimately point anywhere.
  83. using var bySocket = new DockerApiClient("unix:///run/user/1000/podman/podman.sock");
  84. using var byTcp = new DockerApiClient("tcp://nas01:2375");
  85. Assert.True(bySocket.IsLocal);
  86. Assert.False(byTcp.IsLocal);
  87. }
  88. }