Fixed the duplicate portmappings caused by Docker's /containers/json API returning two Ports entries for every bound port
This commit is contained in:
parent
d26b4f284a
commit
c3419524e5
5 changed files with 24 additions and 5 deletions
11
DEVLOG.md
11
DEVLOG.md
|
|
@ -1,5 +1,16 @@
|
||||||
# Tux-Dock Development Log
|
# Tux-Dock Development Log
|
||||||
|
|
||||||
|
## 0.4.1-beta
|
||||||
|
|
||||||
|
### TUI stability
|
||||||
|
|
||||||
|
- Fixed duplicate port mappings shown in the container list TUI. Docker's `/containers/json` API returns separate `Ports` entries for IPv4 (`0.0.0.0`) and IPv6 (`::`) bindings of the same host port; the container parser now deduplicates these into a single `public:private` display string.
|
||||||
|
- Added a container parser regression test covering IPv4/IPv6 duplicate port bindings.
|
||||||
|
|
||||||
|
### Version
|
||||||
|
|
||||||
|
- Bumped version to `0.4.1-beta`.
|
||||||
|
|
||||||
## 0.4-beta
|
## 0.4-beta
|
||||||
|
|
||||||
### ARM64 / tuxreaperd fixes
|
### ARM64 / tuxreaperd fixes
|
||||||
|
|
|
||||||
|
|
@ -251,7 +251,7 @@ ctest --test-dir build --output-on-failure
|
||||||
|
|
||||||
## About / Version
|
## About / Version
|
||||||
|
|
||||||
- Version: `0.4-beta`
|
- Version: `0.4.1-beta`
|
||||||
- Created by: `markmental`
|
- Created by: `markmental`
|
||||||
- GitHub: https://github.com/MARKMENTAL/tuxdock
|
- GitHub: https://github.com/MARKMENTAL/tuxdock
|
||||||
- Forgejo: https://mentalnet.xyz/forgejo-v2/markmental/tuxdock
|
- Forgejo: https://mentalnet.xyz/forgejo-v2/markmental/tuxdock
|
||||||
|
|
|
||||||
2
main.cpp
2
main.cpp
|
|
@ -717,7 +717,7 @@ void TuxDockApp::ActionExecDetachedCommand() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void TuxDockApp::ActionAbout() {
|
void TuxDockApp::ActionAbout() {
|
||||||
OpenMessage("About Tux-Dock", "Tux-Dock 0.4-beta | Created by markmental");
|
OpenMessage("About Tux-Dock", "Tux-Dock 0.4.1-beta | Created by markmental");
|
||||||
}
|
}
|
||||||
|
|
||||||
void TuxDockApp::ExecuteSelectedAction() {
|
void TuxDockApp::ExecuteSelectedAction() {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
|
@ -17,6 +18,7 @@ std::string portsFor(const json& item) {
|
||||||
const auto it = item.find("Ports");
|
const auto it = item.find("Ports");
|
||||||
if (it == item.end() || !it->is_array()) return {};
|
if (it == item.end() || !it->is_array()) return {};
|
||||||
std::ostringstream result;
|
std::ostringstream result;
|
||||||
|
std::unordered_set<std::string> seen;
|
||||||
bool first = true;
|
bool first = true;
|
||||||
for (const auto& port : *it) {
|
for (const auto& port : *it) {
|
||||||
if (!port.is_object()) continue;
|
if (!port.is_object()) continue;
|
||||||
|
|
@ -24,8 +26,11 @@ std::string portsFor(const json& item) {
|
||||||
const auto private_it = port.find("PrivatePort");
|
const auto private_it = port.find("PrivatePort");
|
||||||
if (public_it == port.end() || private_it == port.end() ||
|
if (public_it == port.end() || private_it == port.end() ||
|
||||||
!public_it->is_number() || !private_it->is_number()) continue;
|
!public_it->is_number() || !private_it->is_number()) continue;
|
||||||
|
const std::string mapping = std::to_string(public_it->get<int>()) + ":" +
|
||||||
|
std::to_string(private_it->get<int>());
|
||||||
|
if (!seen.insert(mapping).second) continue;
|
||||||
if (!first) result << ", ";
|
if (!first) result << ", ";
|
||||||
result << *public_it << ":" << *private_it;
|
result << mapping;
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
return result.str();
|
return result.str();
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,18 @@ int main() {
|
||||||
const auto result = parseContainerList(R"([
|
const auto result = parseContainerList(R"([
|
||||||
{"Id":"running-id","Names":["/web"],"State":"running","Status":"Up 2 minutes","Ports":[]},
|
{"Id":"running-id","Names":["/web"],"State":"running","Status":"Up 2 minutes","Ports":[]},
|
||||||
{"Id":"exited-id","Names":["/old"],"State":"exited","Status":"Exited (0) 1 minute ago","Ports":[{"PrivatePort":80,"Type":"tcp"}]},
|
{"Id":"exited-id","Names":["/old"],"State":"exited","Status":"Exited (0) 1 minute ago","Ports":[{"PrivatePort":80,"Type":"tcp"}]},
|
||||||
{"Id":"odd-id","Names":["/odd"],"State":"exited","Ports":"unexpected"}
|
{"Id":"odd-id","Names":["/odd"],"State":"exited","Ports":"unexpected"},
|
||||||
|
{"Id":"mapped-id","Names":["/mapped"],"State":"running","Status":"Up 1 minute","Ports":[{"IP":"0.0.0.0","PrivatePort":80,"PublicPort":8080,"Type":"tcp"},{"IP":"::","PrivatePort":80,"PublicPort":8080,"Type":"tcp"}]}
|
||||||
])");
|
])");
|
||||||
assert(result.ok());
|
assert(result.ok());
|
||||||
assert(result.items.size() == 3);
|
assert(result.items.size() == 4);
|
||||||
assert(result.items[0].running);
|
assert(result.items[0].running);
|
||||||
assert(!result.items[1].running);
|
assert(!result.items[1].running);
|
||||||
assert(result.items[1].name == "old");
|
assert(result.items[1].name == "old");
|
||||||
assert(result.items[1].ports.empty());
|
assert(result.items[1].ports.empty());
|
||||||
assert(result.items[2].name == "odd");
|
assert(result.items[2].name == "odd");
|
||||||
|
assert(result.items[3].name == "mapped");
|
||||||
|
assert(result.items[3].ports == "8080:80");
|
||||||
|
|
||||||
const auto empty = parseContainerList("[]");
|
const auto empty = parseContainerList("[]");
|
||||||
assert(empty.ok());
|
assert(empty.ok());
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue